剖析IntentService的運做機理

剖析IntentService的運做機理java

(本文以Android 5.1爲準)android

侯 亮app


1 概述

在講述Service機制的文章裏,咱們曾經稍微提起過IntentService,今天再來詳細剖析一下它。提及來,IntentService只是一個處理異步請求的服務基類而已。當人們經過調用startService()啓動IntentService時,實質上是向其發送了一個請求。而若是有多個地方同時向同一個IntentService發送請求的話,那麼這些請求會被串行化處理。因此,IntentService經常用於執行那種「一次性處理」的工做。異步

IntentService的運做機理相對比較簡單,並且從Android2.3(我手頭最先的版本)到Android5.1,IntentService的實現代碼一直就沒怎麼變更過,可見其穩定程度。ide

固然,咱們既然說要詳細剖析IntentService,就不可能僅僅講這麼點兒東西。咱們能夠挖得再深一點兒,看看IntentService究竟是如何實現的,又是如何保證串行處理工做的。如今咱們就開始吧。函數


2 先簡單說說IntentService的運做

2.1 onCreate()中啓動一個消息泵線程

咱們先看一下IntentService的onCreate()函數:
【frameworks/base/core/java/android/app/IntentService.java】oop

@Override
public void onCreate() {
    super.onCreate();
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();

    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
}

能夠看到,IntentService建立伊始就會啓動一個消息泵線程HandlerThread,而後又建立了一個能夠向這個消息泵線程傳遞消息的Handler(即ServiceHandler)。this

ServiceHandler是IntentService的一個內嵌類,其定義以下:spa

private final class ServiceHandler extends Handler {
    public ServiceHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        onHandleIntent((Intent)msg.obj);
        stopSelf(msg.arg1);  // 注意,不是stopService()!
    }
}

因此,上面的代碼的邏輯意義就很明確了,那就是構造一個消息泵線程以及一個能夠向這個泵傳遞消息的Handler。然後,當這個消息泵線程最終處理這個消息時,其實就是在回調IntentService的onHandleIntent()函數而已,而且在該回調函數返回後,handleMessage()會進一步調用stopSelf()結束service。注意,這裏說「結束service」只是一種簡略說法,實際的狀況會稍微複雜一點兒,這個咱們後文再細說。線程

2.2 onStartCommand()利用ServiceHandler向消息泵打入特殊消息

在緊隨onCreate()函數以後,系統會調用到onStartCommand()函數,這個你們都很熟悉了。對於startService()動做而言,系統只會在service尚不存在的狀況下,建立相應的service,並回調其onCreate()函數。之後,只要這個service沒有被銷燬,就不會重複再調用onCreate()了。不過,每次調用startService()都會致使走到onStartCommand()函數。IntentService的onStartCommand()函數的代碼以下:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onStart(Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}

看起來,每當用戶調用startService()啓動IntentService,就會自動向其內部消息泵線程打入一條消息,該消息的.obj域記錄着用戶傳來的intent,這個intent在handleMessage()裏會進一步傳遞給onHandleIntent()。另外,請你們注意那個startId參數,這裏咱們先打個伏筆,後文還會細說。

2.3 實現你的onHandleIntent()函數

在IntentService.java文件裏,onHandleIntent()只是作了簡單的聲明:

protected abstract void onHandleIntent(Intent intent);

其具體行爲要看咱們寫的IntentService派生類怎麼定義這個函數啦。

消息泵裏的消息隊列在先天上就維護了一條串行化的執行鏈。消息泵線程逐個摘取消息節點,回調每一個節點的onHandleIntent()函數,一切都顯得那麼天然。如今咱們能夠畫出以下示意圖:

3 再深挖一下

有了以上這些基礎知識,如今咱們要再深挖一下了。

在消息隊列中,消息的確被順序排列了,但是在處理前一個消息時,調用的stopSelf()不是把service結束了嗎?那它還怎麼保證繼續處理後續消息呢?可見,前文咱們說的「IntentService還會自動執行stopSelf()關閉本身」的說法並不許確。問題的關鍵在於,ServiceHandler的handleMessage()裏調用的是stopSelf(),而不是stopService()!它們是不同的。

3.1 stopSelf()和stopService()是不同的!

stopSelft()的函數定義以下:
【frameworks/base/core/java/android/app/Service.java】

public final void stopSelf(int startId) {
    if (mActivityManager == null) {
        return;
    }
    try {
        mActivityManager.stopServiceToken(
                new ComponentName(this, mClassName), mToken, startId);
    } catch (RemoteException ex) {
    }
}

請你們注意那個startId參數,這個參數是系統經過onStartCommand()傳遞給service的,也就是前文咱們打伏筆的地方。要了解這個startId參數,咱們得看一下AMS裏的相關代碼。

AMS中啓動service的函數是startService,其代碼截選以下:
【frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java】

public ComponentName startService(IApplicationThread caller, Intent service,
        String resolvedType, int userId) {
    . . . . . .
        . . . . . .
        ComponentName res = mServices.startServiceLocked(caller, service,
                resolvedType, callingPid, callingUid, userId);
        . . . . . .
    . . . . . .
}

【frameworks/base/services/core/java/com/android/server/am/ActiveServices.java】

ComponentName startServiceLocked(IApplicationThread caller,
        Intent service, String resolvedType,
        int callingPid, int callingUid, int userId) {
    . . . . . .
    ServiceRecord r = res.record;
    . . . . . .
    r.lastActivity = SystemClock.uptimeMillis();
    r.startRequested = true;
    r.delayedStop = false;
    r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
            service, neededGrants));
    . . . . . .
    . . . . . .
    return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
}

也就是說,每當咱們調用startService()啓動一個服務時,不但會在其對應的ServiceRecord節點的pendingStarts裏插入一個新的StartItem節點,並且會爲這個StartItem節點生成一個新的id號,這個id號就是往後的startId啦。

生成id號時,採用的辦法是最簡單的加1操做,代碼以下:
【frameworks/base/services/core/java/com/android/server/am/ServiceRecord.java】

public int makeNextStartId() {
    lastStartId++;
    if (lastStartId < 1) {
        lastStartId = 1;
    }
    return lastStartId;
}

接下來,咱們繪製一張調用關係圖:

圖中的sendServiceArgsLocked()會將r.pendingStarts列表中的StartItem節點轉移到r.deliveredStarts列表中。這主要是由於啓動service的動做自己是比較複雜的,有時候甚至會遇到目標service寄身的進程還沒有啓動的狀況,此時就得先啓動一個用戶進程,然後再進一步啓動service。要規劃好這些異步的動做,咱們經常須要把信息先記錄進一個pending列表裏,然後再在合適的時機將信息從pending列表裏取出。

上圖中最後調用的r.app.thread.scheduleServiceArgs()實際上是向service所在的進程發出SERVICE_ARGS語義,語義中攜帶着剛取出的StartItem節點的id和intent信息。該語義最終致使執行到目標service的onStartCommand()。這個就和前文介紹onStartCommand()的地方契合起來了。

當onStartCommand()向消息泵線程打入消息時,startId就被記錄進message裏了。咱們如今舉個例子,假設幾乎在同時有兩個地方都調用startService()來啓動同一個IntentService,此時極可能會造成兩個StartItem和兩個IntentService消息,畫出示意圖以下:

既然消息隊列裏的消息已經和AMS裏的StartItem對應上了,那麼在處理完消息後,調用stopSelf()時,應該也必須考慮到這種匹配關係。因此stopSelf()的參數就是startId。前文咱們已經看到,stopSelf()內部是在調用:

        mActivityManager.stopServiceToken(
                new ComponentName(this, mClassName), mToken, startId);

stopServiceToken()的代碼以下:
【frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java】

@Override
public boolean stopServiceToken(ComponentName className, IBinder token,
        int startId) {
    synchronized(this) {
        return mServices.stopServiceTokenLocked(className, token, startId);
    }
}

【frameworks/base/services/core/java/com/android/server/am/ActiveServices.java】

boolean stopServiceTokenLocked(ComponentName className, IBinder token, int startId) {
    . . . . . .
    ServiceRecord r = findServiceLocked(className, token, UserHandle.getCallingUserId());
    if (r != null) {
        if (startId >= 0) {
            ServiceRecord.StartItem si = r.findDeliveredStart(startId, false);
            if (si != null) {
                // 用while循環刪除位於startId對應的StartItem節點以及其以前的全部節點
                while (r.deliveredStarts.size() > 0) {
                    ServiceRecord.StartItem cur = r.deliveredStarts.remove(0);
                    cur.removeUriPermissionsLocked();
                    if (cur == si) {
                        break;
                    }
                }
            }

            if (r.getLastStartId() != startId) {
                return false;    // 若是不是最後一個startItem節點,則直接return false了
            }

            if (r.deliveredStarts.size() > 0) {
                Slog.w(TAG, "stopServiceToken startId " + startId
                        + " is last, but have " + r.deliveredStarts.size()
                        + " remaining args");
            }
        }
        . . . . . .
        // 當最後一個startItem摘掉後,才真正結束service
        bringDownServiceIfNeededLocked(r, false, false);  
        . . . . . .
        return true;
    }
    return false;
}

這段代碼的主要意思仍是比較明確的,那就是按序從ServiceRecord的deliveredStarts列表中刪除StartItem節點,直到所刪除的是startId參數對應的StartItem節點,若是此時還沒有抵達ServiceRecord內部記錄的最後一個start Id號,則說明這次stopSelf()操做不必進一步結束service,那麼直接return false就能夠了。只有在所刪除的startItem節點的確是最後一個startItem節點時,纔會調用bringDownServiceIfNeededLocked()去結束service。這就是爲何IntentService的ServiceHandler在處理完消息後,能夠放心調用stopSelf()的緣由。

那麼,爲何要用一個while循環來刪除位於startId對應的StartItem節點以前的全部節點呢?你們能夠設想一下,若是service進程被異常kill了,那麼它裏面的消息隊列確定也就銷燬了。但是在AMS一側的ServiceRecord裏,那些對應的StartItem節點仍是存在的,就好像一個個孤兒同樣。此時,若是用戶再一次調用startService()啓動了這個IntentService,那麼系統最好能在處理完這次message後,一併將那些孤兒StartItem銷燬。不過這只是我目前的一點兒猜測,實際上可能不大容易遇到這種狀況。

3.2 說說setIntentRedelivery()

如今咱們再來看看IntentService裏其餘一些細節。好比setIntentRedelivery()函數:
【frameworks/base/core/java/android/app/IntentService.java】

public void setIntentRedelivery(boolean enabled) {
    mRedelivery = enabled;
}

通常,咱們能夠在實現IntentService派生類時,在構造函數裏調用這個函數。這裏設置的mRedelivery成員會在onStartCommand()函數裏影響最終的返回值,從而影響service的「重遞送intent」的行爲。

咱們再列一次onStartCommand()的代碼:

public int onStartCommand(Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

當mRedelivery爲true,會返回START_REDELIVER_INTENT,這個值的意思是說,若是在onStartCommand()以後的某個時刻,該service對應的進程被kill了,那麼系統會自動重啓該service,並向onStartCommand()從新傳入當初startService()時指定的intent。另外,在極端狀況下,可能已經有多個地方startService()了,那麼系統在重啓service以後,應該會將本身記錄的多個intent逐條傳遞迴service,也就是說,有可能會執行屢次onStartCommand()了。而當mRedelivery爲false時,會返回START_NOT_STICKY,它表示若是在後續某個時刻,該service對應的進程被kill了,系統是不會自動重啓該service的。


4 小結

有關IntentService的知識,咱們就先說這麼多,有興趣的同窗不妨對比代碼看看。

相關文章
相關標籤/搜索