Android四大元件之Service篇

HFW發表於2019-03-14

前言

上篇文章複習總結了Android的啟動模式,現在開始複習Service相關的知識,Service是一種可以在後臺執行長時間執行操作而沒有使用者介面的應用元件。首先從Service的生命週期開始

一、Service的生命週期

Android四大元件之Service篇

  • onCreate() 當例項建立時呼叫,一個例項只會被呼叫一次該方法
  • onBind(Intent) 當通過bindService啟動服務時,會呼叫該方法,該方法會返回一個IBinder例項
  • onStartCommand() 當通過startService啟動服務時,會呼叫該方法,不停的呼叫startService會重複呼叫該方法
  • onUnbind(Intent) 當所有的連線都斷開後會回撥該方法,預設該方法返回false,如果返回true,再有新的連線會回撥onRebind()
  • onRebind(Intent) 當onUnbind返回true後如果服務還在執行,這時又有新的連線會回撥該方法
  • onDestory() 當Service將要被銷燬時呼叫,用於資源的回收

二、Service的啟動方式

1. startService(Intent)

單獨通過該方式啟動服務,當目標服務還沒執行會呼叫服務的onCreateonStartCommand,如果已經執行了就只會呼叫onStartCommand,當服務開啟後就與啟動方沒有任何關係了,只有當自己呼叫stopSelf,或者外界呼叫stopService才會停止,並且無論啟動多少次都只需要停止一次就行了

  • 例一 Activity A連續呼叫startService10次啟動Service S,則最後S會呼叫1次onCreate、10次onStartCommand,並且當A被退出後S會繼續執行

2. BindService()

單獨通過該方式啟動服務擁有以下特性

  • 當目標服務還沒執行會呼叫服務的onCreateonBind,如果已經執行了就只會呼叫onBind,重複bindService不會重複呼叫onBind
  • 我們可以在onServiceConnected獲取到一個IBinder物件(注意Service的onBind方法返回的必須不是null,不然不會呼叫該方法),如果Service與啟動的Activity同屬於一個程式那麼返回的就是Service的onBind返回的那個物件,不然返回的是一個BinderProxy物件,如果是相同程式我們可以直接將其強轉然後呼叫其方法。不同程式則需要通過AIDL生成的類將其上轉型成一個介面,然後才能呼叫。關於AIDL知識以後單獨總結
  • 當通過該方式啟動它的Activity退出時,如果沒有手動呼叫unBindService,系統會給我們呼叫該方法並列印出一段錯誤,當Service的所有的連線都斷開了以後會呼叫Service的onUnbind,然後呼叫onDestroy

3. 結合呼叫bindService和startService

  • 先呼叫startService然後呼叫bindService,會呼叫Service的onCreateonStartCommandonBind
  • 先呼叫bindService然後呼叫startService, 會呼叫Service的onCreateonBindonStartCommand

當Service的所有連線都斷了並且呼叫了stopService,這種混合啟動的Service才會被銷燬

三、IntentService

由於Service中的各個回撥方法都是執行在主執行緒的所以沒法做耗時任務,不然會導致ANR,因此IntentService就因運而生了

IntentService是Service的子類,客戶端通過呼叫context.startService(Intent)傳送請求,它根據需要啟動,在工作執行緒中處理每個Intent,執行完所有任務後關閉自己

1. IntentService.onCreate

// IntentService
public void onCreate() {
    super.onCreate();
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); // 1
    thread.start(); // 2
    mServiceLooper = thread.getLooper(); // 3
    mServiceHandler = new ServiceHandler(mServiceLooper); // 4
}
複製程式碼
  1. 建立了一個HandlerThread例項
  2. 啟動了該執行緒
  3. 獲取HandlerThread在run方法中建立的Looper物件
  4. 使用該Looper物件建立了ServiceHandler物件

裡面用到了HandlerThread,那麼就先來看看HandlerThread其繼承了Thread,其run方法如下所示

// HandlerThread
public void run() {
    mTid = Process.myTid(); //1
    Looper.prepare(); //2
    synchronized (this) {
        mLooper = Looper.myLooper(); //3
        notifyAll(); //4
    }
    Process.setThreadPriority(mPriority); //5
    onLooperPrepared();//6
    Looper.loop();//7
    mTid = -1;
}
複製程式碼
  1. 獲取到了當前執行緒的tid將其賦值給mTid
  2. 呼叫Looper.myLooper為當前執行緒建立了一個Looper物件(關於Looper會在Handler的文章中總結)
  3. 獲取當前執行緒的Looper物件並將其賦值給mLooper
  4. 喚醒getLooper方法中呼叫wait而阻塞的執行緒
  5. 設定執行緒優先順序
  6. 死迴圈不停的從訊息佇列中取資料

再來看看其getLooper方法

public Looper getLooper() {
    if (!isAlive()) {
        return null;
    }
    synchronized (this) {
        while (isAlive() && mLooper == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}
複製程式碼
  • 如果執行緒已經死了,那麼返回null
  • 執行緒活著但是mLooper還等於null,就呼叫wait()將鎖讓給執行在子執行緒的run方法,讓其建立Looper。
  • 執行緒活著並且mLooper不等於null,那麼就返回這個子執行緒的Looper物件

思考:這裡有個問題,為什麼getLooper裡面需要使用while迴圈?照道理當呼叫getLooper的執行緒被喚醒的時候mLooper = Looper.myLooper();已經被執行了,而synchronized又能夠提供可見性,強制呼叫getLooper的執行緒去主存裡面讀取mLooper值。

2. IntentService.onStart

// IntentService
public void onStart(@Nullable Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}
複製程式碼

這裡將接收到intent和startId組裝成訊息傳送給ServiceHandler,來看看ServiceHandler的handleMessage方法

// ServiceHandler
public void handleMessage(Message msg) {
    onHandleIntent((Intent)msg.obj);
    stopSelf(msg.arg1);
}
複製程式碼

注意由於該Handler建立時傳入的Looper是在子執行緒建立的,所以handleMessage方法是執行在子執行緒的,而onHandleIntent是個抽象方法由子類實現當其呼叫完後會呼叫stopSelf(startId)該方法會判斷給定的startId是不是最近一次啟動的startId,如果不是那麼什麼都不做,如果是那麼就會關閉服務

3. IntentService.onDestroy

public void onDestroy() {
    mServiceLooper.quit();
}
複製程式碼

呼叫了Looper.quit,那麼HandlerThread的run方法退出Looper.loop阻塞繼續向下執行mTid = -1; 然後HandlerThread執行緒就執行完畢了

4. 總結

IntentService的原理其實就是在子執行緒建立了一個Looper,然後根據該Looper建立一個Handler,當每次呼叫context.startService的時候呼叫handler.sendMessage在子執行緒中完成工作,完成以後看看有沒有新任務到來,如果沒有就把自己關閉,否則就繼續執行下一個任務

相關文章