Android 高效能執行緒

Rick 三竹資訊

今天會說

Thread

Handler

HandlerThread

Thread Pool

AsyncTask

Service

如果有時間會偷渡一下

 Binder

AIDL

Messenager

為什麼要用執行緒

情境-排隊買早餐

B先生:燒餅1000個 豆漿1000個

A小姐:饅頭1個 無糖豆漿1杯

C歐巴桑:蛋餅2份 奶茶大杯 快點好嗎? 

由於B先生買太多早餐

所以C歐巴桑只好改吃午餐 ㄏㄏ

使用一個Thread可以這樣寫

public class MyTask implements Runnable {
   public  void  run() {
      //execute  your  task
   }
}
//...
public static void main(){
    new Thread(new MyTask()).start();
}

所以你可以這樣安排買早餐

public class Task1 implements Runnable {
   public  void  run() {
      //蛋餅*1000  豆漿*1000   
   }
}
public class Task2 implements Runnable {
   public  void  run() {
      //饅頭*1 豆漿*1  
   }
}
//歐巴桑去隔壁買惹...

多執行緒優缺點

優點

同時進行多個任務, 效率高

缺點

程式複雜度變高

增加資源消耗(context switch、memory)。

資料不一致(執行緒不安全)。

執行緒不安全

int mVar = 0;

public class Task1 implements Runnable{
    public void run(){
        mVar++;
    }
}

public class Task1 implements Runnable{
    public void run(){
        mVar--;
    }
}

資料不一致

mVar 會因為共同存取的關係

最終結果可能會出現 -1, 0, 1

你需要一把鎖(Lock)

Java的關鍵字synchronized可以幫你建立一個臨界空間, 防堵同時共用相同記憶體區段,

所造成的資料不一致。

可是...

卻會產生一些問題

情境-排隊上廁所

popo

pipi

Blocking

Deadlock

paper?

toilet?

解法:當PM

等待另一個Task的結果

我們必須採取非同步(Asynchronous)的方式來處理

一般會有兩種處理方式

回呼(callback)

輪詢(polling)

物件如何交換資料

傳遞給另一個實體引用

class A{
    
    private String name;
    
    public String getName(){
        
        return name;
    
    }
    
    public void setName(String name){
        
        this.name = name;
    
    }

}



class B{
    
    private A a;
    
    public B(A a){
        
        this.a = a;
    
    }

}
class A{
    
    private C c;
    
    public A(C c){
        
        this.c = c;
    
    }

}



class B{
    
    private C c;
    
    public B(C c){
        
        this.c = c;
    
    }

}

透過第三方協力實體存取

Activity怎麼交換資料?

由於Activity是Framework幫我們建立的, 因此取不到實體。

Android提供了兩種方式讓開發者來交換資料

  • Application

  • Intent

Android如何傳遞訊息?

  • Handler

  • AsyncTask

  • Binder

  • Service

  • IntentService

  • 其他還有一些邪魔歪道非官方的方法(EventBus、Otto、AndroidBus、RxJava…等)

Android一開始會建立一個Main Thread,

專門用來處理UI,

因此必須把處理非UI的任務,

丟到其他Thread去執行,

否則會造成UI Thread在循序處理時, 

後面任務在等待前面任務完成才能繼續往下執行。

Android Thread的運作

UI沒有及時處理會怎樣?

如果需要更新畫面怎麼辦?

如果Background Thread執行完畢, 需要畫面進行更新,

則會透過Handler來通知Main Thread。

Handler機制

Handler是Android特有的機制,

透過他可以跟Main Thread進行溝通

new Thread(new Runnable() {
    
    public void run() {
        
        //這邊是背景thread在運作, 這邊可以處理比較長時間或大量的運算

        
        ((Activity) mContext).runOnUiThread(new Runnable() {

            public void run() {
                
                //這邊是呼叫main thread handler幫我們處理UI部分   
             }
        
        });
    
    }

}).start();

new Handler(mContext.getMainLooper()).post(new Runnable(){
   
    public void run(){
       
        //處理少量資訊或UI

   
    }

});

view.post(new Runnable(){
    
    public void run(){
        
        //更新畫面

    
    }

});


Runnable

Handler

Handler

Message Queue

Message

Message

Message

Looper

UI Thread

Runnable

自行定義Looper機制

new Thread(new Runnable() {
    
    public void run() {
        
        Log.e(TAG, "A");
        
        Looper.prepare();
        
        new Handler().post(new Runnable(){

            
            @Override
            
            public void run() {
                
                Log.e(TAG, "B1");
            
            }
        
        });
        
        new Handler().post(new Runnable(){

            
            @Override
            
            public void run() {
                
                Log.e(TAG, "B2");
                
                Looper.myLooper().quit();
            
            }
        
        });


        Looper.loop();

        
        Log.e(TAG, "C");
        
        ((Activity) mContext).runOnUiThread(new Runnable() {
            
            public void run() {
                
                Log.e(TAG, "D");
            
        }
        
    });
    
}
}).start();

輸出為:

  • A
  • B1
  • B2
  • C
  • D

HandlerThread輕鬆使用Looper機制

HandlerThread handlerThread = new HandlerThread("HandlerThread");

handlerThread.start();


Handler mHandler = new Hanlder(handlerThread.getLooper()){
    
    public void handleMessage(Message msg){
        
        super.handleMessage(msg);
        
        switch(msg.what){
        
        case 1:
            
            Logger.e("message receive");
        
        break;
                
    }
    
}
}

handler.sendEmptyMessage(1);



//或者

new Hanlder(handlerThread.getLooper())
    .post(new Runnable(){
         
        public void run(){
             
            //長時間任務1
            
            //長時間任務2
         
        }
    
});

HandlerThread優缺點(範例)

優點                  

執行緒安全(循序執行)。

實作簡單

程式碼乾淨

缺點

它就只是一個執行緒 無法並行處理任務

 (os: 一個不夠快 那你有沒有開第二個啊?)

任務要循序執行更新畫面

new Thread(new Runnable(){
    @Override
    public void run(){
        //Task 1
        runOnUiThread(new Runnable(){
            @Override
            public void run(){
                //update ui
                new Thread(new Runnable(){
                    @Override
                    public void run(){
                        //Task 2
                        runOnUiThread(new Runnable(){
                            @Override
                            public void run(){
                                //update ui
                            }
                        });
                    }
                }).start();
            }
        });
    }
}).start();

如果改用HandlerThread來做

HandlerThread mHandlerThread = new HandlerThread("my handler thread");
mHandlerThread.start();
Handler mHandler = new Handler(mHandlerThread.getLooper());
mHandler.post(task1);
// ... n-1 task
mHandler.post(taskN);

並行Thread可以使用ThreadPool

ThreadPool的好處

Java提供了ThreadPoolExecutor讓我們使用, 有幾個優點:

  • Thread能保持存活, 等待新任務, 不會隨著任務建立再銷毀。

  • Thread Pool限制最大Thread數量, 避免系統浪費。

  • Thread的生命週期被Thread Pool控制。

(OS: 你的需求我們都聽到了)

ThreadPoolExecutor自行定義

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    
    int corePoolSize,
    
    int maxPoolSize,
    
    long keepAliveTime,
    
    TimeUnit unit,
    
    BlockingQueue<Runnable> workQueue
);

core pool size(核心緩衝池數量):

Thread數量不會低於這個數字。

maxumum pool size(最大緩衝池數量):

Thread pool的Thread最大數量

 可根據底層硬體來決定數量。

int N = Runtime.getRuntime().availableProcessors();

keep-alive time(最大閒置時間): 

超過閒置時間, 系統會回收core Thread數量以上的Thread。

task queue type(任務佇列類型):

根據策略不同, 所用的演算法也會不同。

內建Executor

固定尺寸執行器 : 固定大小。

Executors.newFixedThreadPool(2);

動態尺寸執行器 : 動態調整, 超過60秒沒做事就被刪掉。

Executors.newCachedThreadPool();

單一執行緒執行器 : 就一個Thread, 後來的任務要排隊。

Executors.newSingleThreadExecutor();

開發處理圖片的方法給內部使用

public Bitmap getRemoteBmp(String url){
    final Data result = fetchData(url);
    byte[] image = downloadAvatar(data.getUrl());
    Bitmap bmp = decodeImg(img);
    //to do something
    return bmp;
}

你可以用這樣寫

但是會造成一些缺點

不易重新使用

資源無法控制

容易造成callback hell

呼叫者不知道是否要自己開Thread執行

Callable搭配Future

Future未來可能會回傳結果,

透過blocking直到long task完成, 回傳物件。

ExecutorService executor = Executors.newSingleThreadExecutor();

Future<Object> future = executor.submit(new Callable<Object>(){
    
    public Object call() throws Exception{
        
        Object obj = doLongTask();
        
        return obj;
    
    }

});

Object result = future.get();

InvokeAllInvokeAny的操作(範例

Executor提供同時多個Thread並行的操作。

InvokeAll : 同時並行多個Thread, 並且透過blocking來取回每一個Task的結果

InvokeAny: 同時並行多個Thread, 只要有一個回傳成功, 則終止剩下的Task。

ExecutorCompletionService的使用(範例)

ExecutorCompletionService可以包裹Callable的Task, 

當任務結束以後, 可回傳單一結果,

直到所有任務都結束, 則進行關閉。

UI Thread溝通的AsyncTask

想執行長時間運行的任務,

最後再跟UI Thread進行溝通,

Android將這些特性全部封裝在AsyncTask。

如何使用AsyncTask ?

class MyTask extends AsyncTask<Params, Progress, Result>{
    
    protected void onPreExecute(){
       
        // in main thread

    
    }
   
    protected Result doInBackground(Params... params){
       
        // in background thread

    
    }
  
    protected void onProgressUpdate(Progress... progress){
       
        // in main thread

    
    }
        
    protected void onPostExecute(Result result){
       
        // in main thread

    
    }
    
    protected void onCancelled(Result result){
       
        // in main thread

    
    }


}

參數解說

  • Params 輸入背景任務的資料。

  • Progress 由Background Thread跟UI Thread報告進度。

  • 如果要跟UI Thread報告進度, 則可呼叫publishProgress方法。

  • Result 由Background Thread跟UI Thread報告結果。

AsyncTask的狀態

PENDING : 實體化但未執行execute。

RUNNING : execute已經呼叫。

FINISHED : onPostExecute或onCancelled已經被呼叫。

以上三個狀態都是不可逆的, 一旦進入了RUNNING狀態, 就無法再啟動新的執行,必須重新產生新的實體

可透過AsyncTask.getStatus()取得狀態資訊。

AsyncTask執行順序

API Level execute executeOnExecutor
1-3 循序
4-10 並行
11-12 並行 循序/並行(可自訂)
13+ 循序 循序/並行(可自訂)

AsyncTask自訂Executor(範例)

AsyncTask允許你自訂執行器, 可參考前面所講的內建執行器

//循序

executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, Object... objs);

//並行

executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Object... objs);
//自訂

executeOnExecutor(CustomExecutor, Object... objs);

沈默的AsyncTask

AsyncTask<Void, Void, Void>

背景執行緒無法跟UI執行緒溝通, 沒有任何回傳結果。

毫無反應就只是個NPC。

不溝通的AsyncTask

只實做doInBackground

沒有提供報告進度或結果, 就只是個背景任務,

就像初音只是個軟體一樣。

Process的生命週期

根據官網所提供的資訊, Android在運行多個行程, 會將行程區分成五種等級, 依據等級來判斷該行程是否可進行回收。

Foreground Process

Visible Process

Service Process

Background Process

Empty Process

Foreground Process!

  • 當Process內的Activity進入了onResume

  • 某個Service綁定了某個Activity且是處於onResume的狀態。

  • Service使用了startForeground方法

  • 正在執行onCreateonStartonDestroy

  • BroadcastReceiver正在執行onReceive

Visible Process!

  • 某Activity進入了onPause, 但是User仍看的到(Dialog)。

  • 綁定某個Visible Activity的service

Service Process!

  • 已經使用startService或者bindService(通常是音樂撥放或者網路下載)

Background Process!

  • Activity進入onStop, 且已經不可見, 系統會根據LRU演算法, 對Background Process進行回收。

 

Empty Process!

  • 沒有任何活動的Process, 系統優先回收的Process。

為什麼要使用Service?

  • 將元件的生命週期跟Thread的生命週期分開(在Thread結束前無法釋放物件導致Memory leak)

  • 當一個Process內只剩下Thread在執行, 避免Process被系統意外回收, 導致Thread被提前結束

Service的生命週期

Service分成兩種形式

  • 被啟動的Service(範例): 由第一個元件啟動請求建立, 第一個元件停止請求銷毀。

  • 被聯繫的Service(範例): 第一個元件Bind, 該Service此時被建立, 直到所有元件Unbind才銷毀。

啟動(Start)的Service

public class ServiceDemo extends Service {
    
    @Override
    
    public int onStartCommand(Intent intent, int flags, int startId) {
        
        return super.onStartCommand(intent, flags, startId);
    
    }
    
    @Override
    
    public IBinder onBind(Intent intent) {
        
        return null;
    
    }

}

start service解說

  • 啟動的service非常簡單, 只要覆寫onBind並且回傳null, 接著再多覆寫onStartCommand, 將Thread寫進此方法即可。

  • 透過startService以及stopService控制啟動即結束, 也可以透過Service.stopSelf自行結束。

Service的重啟

有時候在系統記憶體吃緊的時候, 會將系統某些Service收起來, 這時候有幾個參數可以讓系統替你重新啟動Service。

  • START_STICKY : Service被殺掉, 系統會重啟, 但是Intent會是null。

  • START_NOT_STICKY : Service被系統殺掉, 不會重啟。

  • START_REDELIVER_INTENT : Service被系統殺掉, 重啟且Intent會重傳。

連繫(Bound)的Service

private LoaclServiceConnection mLoaclServiceConnection = new LoaclServiceConnection();

public class ServiceDemo extends Service {
    
    private MyBinder mBinder = new MyBinder();
    private class MyBinder extends Binder{
        
        public ServiceDemo getService(){
            
            return ServiceDemo.this;
        
        }
    
    }

    public IBinder onBind(Intent intent) {
        
        return mBinder;
    
    }
    
    @Override
    
    public boolean onUnbind(Intent intent) {
        
        return super.onUnbind(intent);
    
    }

}




連繫(Bound)的Service(續)

private class LoaclServiceConnection implements ServiceConnection{
    
    @Override
    
    public void onServiceConnected(ComponentName name, IBinder service) {
        
        //透過Binder調用Service內的方法
    
    }
    
    @Override
    
    public void onServiceDisconnected(ComponentName name) {
        
        //service 物件設為null
    
    }

}
public class MainActivity extends Activity {
    
    protected void onCreate(Bundle savedInstanceState) {
        
        bindService(new Intent(this, ServiceDemo.class), 
            mLoaclServiceConnection, Context.BIND_AUTO_CREATE);
            
    }

}


IntentService(範例)

public class IntentServiceDemo extends IntentService {
    
    public IntentServiceDemo(String name) {
        
        super(name);
    
    }
    
    @Override
    
    protected void onHandleIntent(Intent intent) {
        
        //背景執行緒
    
    }

}

IntentService解說

如果你需要Service循序執行任務, 可以透過IntentService來完成, 背景是透過一個HandlerThread來進行排程。

不需要使用的時候, 系統會自動幫你回收

IntentService vs.  Service

Service

由Client端控制 : User自行控制啟動跟結束

並行任務執行 : 啟動多個Thread

循序且可以重新安排的任務 : 任務可以被賦予優先權 e.g. 音樂服務

IntentService

循序執行

比較

執行緒安全 平行處理 可與UI溝通
Thread no yes no
HandlerThread yes no no
Thread Pool no yes ​no
AsyncTask no yes yes
Service no yes no
IntentService yes no no

Q & A

跨行程

Binder解說

Binder能讓應用程式在不同行程裡的執行緒之間傳遞函式與資料(方法呼叫)。

客戶端行程呼叫transact方法, 伺服端則會在onTransact接收。

交易資料由Parcel物件所組成, 

根據下面網站效能比較, Parcelable比Serializable和GSON更有效率。

http://blog.prolificinteractive.com/2014/07/18/why-we-love-parcelable/

透過下列網站可以輕鬆將物件轉換成Parcelable。

http://www.parcelabler.com/

AIDL(Android Interface Definition Language)

AIDL(Android介面定義語言): 當行程想開放功能性給其他行程時, 可透過AIDL來進行定義, 編譯後會產生支援行程間溝通(IPC, Interprocess communication)的java檔案。

同步RPC(Remote Procedure Call)

如果你在aidl內定義

interface IMyAidlInterface {
    
    String getThreadName();

}

同步RPC

server端以實作方式實現

private IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub(){
    
    @Override
    
    public String getThreadName() throws RemoteException {
      
        return MyActivity.class.getName();
    
    }

};

AIDL客戶端接收

優點: 單純、好實做、執行緒安全

缺點: 用戶端會被Blocking

try {
    
    IMyAidlInterface mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(binder);
    
    String threadName = mIMyAidlInterface.getThreadName();

} catch (RemoteException e) {
    
    e.printStackTrace();

}

非同步RPC(範例)

如果要實做非同步, 方法宣告為oneway且回傳值就都是void

取而代之的是Callback。

Server

定義AIDL

import personal.givemepass.myapplication.IMyAidlInterfaceCallback;

interface IMyAidlInterface {
    
    oneway void getThreadName(IMyAidlInterfaceCallback callback);

}

Server端(續)

實做Server端

private final IMyAidlInterface.Stub server = new IMyAidlInterface.Stub(){
    
    @Override
    
    public void getThreadName(IMyAidlInterfaceCallback callback) throws RemoteException {
        
        String name = "aaa";
            
        callback.handleMessage(name);
      
    }

};

ClientRPC

定義AIDL

interface IMyAidlInterfaceCallback {
    
    void handleMessage(String name);

}

ClientRPC(續)

實做Client端

private IMyAidlInterfaceCallback.Stub mClient = new IMyAidlInterfaceCallback.Stub(){
        
    @Override
        
    public void handleMessage(String name) throws RemoteException {
             
        //client 接收
        
    }

};

Messenger(範例)

Messenger是Android提供處理IPC最簡單的方式, 實際上它底層也是以AIDL為架構,

透過Messenger會在內部使用Handler將任務排列到Thread的Message Queue執行,

是屬於執行緒安全的機制, 也可以透過Message來進行雙向溝通。

Messenger 架構

Binder vs. AIDL vs. Messenger

Binder主要是Android為了IPC而開發出來的。

AIDL跟Messenger最終還是會透過Binder進行IPC。

如果想要同時執行多個執行緒, 那麼AIDL是比較適合的。

如果只是想要傳遞訊息, 並且實做簡單且執行緒安全, 則可以採用Messenger。

Thank you.

Android 高效能執行緒

By givemepass

Android 高效能執行緒

  • 5,537