IntentService源碼解析

上一篇我們分析了Android中的線程間通信HandlerThread的原理.HandlerThread充分的利用Handler的通信機制和消息隊列。本篇將分析IntentService的作用和原理。

警告:本篇的源碼可能過于枯燥和乏味,中間涉及到一次采坑,錯誤的分析。最后糾正回來了。我覺得此處是比較有意義的。讀者對著源碼的同時細讀本篇可能更好一點。

目錄

  • IntentService簡單介紹
  • 源碼分析
  • 續(xù)錯誤糾正

IntentService

IntentService繼承自Service本質(zhì)上就是一個服務(wù)。但它內(nèi)部擁有一個完整的HandlerThread??梢赃@樣說IntentService=Service+HandlerThread。

我們先來說下它的常見用法。將復雜耗時操作交由IntentService來處理,你可以通過Intent的方式啟動它,然后實現(xiàn)onHandleIntent方法,在onHandleIntent的任何操作將屬于內(nèi)部HandlerThread的子線程。

代碼如下:

繼承一個IntentService

public class CustomIntentService extends IntentService {
 public CustomIntentService() {  super("CustomIntentService"); }

    @Override
    protected void onHandleIntent(Intent intent) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String msg = intent.getStringExtra("msg");
        Log.d("IntentService", "耗時操作" + msg);
    }

    @Override
    public void onDestroy() {
        Log.d("IntentService", "onDestroy" );
        super.onDestroy();
    }
}

啟動它,并附帶一個消息

 Intent intent = new Intent(MainActivity.this, CustomIntentService.class);
                intent.putExtra("msg","hello");
                startService(intent);

這里我們啟動了CustomIntentService并附帶了一個hello過去。此時onHandleIntent方法會接受到我們這個Intent,并模擬耗時后打印日志。
注意兩點

  • 1.這里的Intent是間接性傳遞過去的,按通常的思路這個Intent是在主線程中,但是這里并不是主線程。后面我們會從源碼上來分析。
  • 2.我們知道IntentService的特性會執(zhí)行完任務(wù)后自動銷毀。但有一種情況,如果我們快速調(diào)用兩次startService會如何?下面是快速調(diào)用兩次的日志。
07-20 21:10:57.490 5895-5977IntentService: 耗時操作hello
07-20 21:11:02.480 5895-5977IntentService: 耗時操作hello
07-20 21:11:02.480 5895-5895IntentService: onDestroy

顯然,并不是說每次執(zhí)行都會銷毀掉,當?shù)诙l消息過來的時候它并沒有銷毀,而是做完后才銷毀。但是這是為什么呢?先賣個關(guān)子,我們帶著疑問去看源碼吧。

源碼分析:

注:此處源碼刪除了一些不影響閱讀的注釋和方法

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    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);
        }
    }

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

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

  • 1.IntentService 內(nèi)部總共有四個成員變量,我們只需要關(guān)注mServiceLoopermServiceHandler即可。
  • 2.首先我們看onCreate方法,它創(chuàng)建了一個HandlerThread并向ServiceHandler傳遞了一個Looper對象。
  • 3.ServiceHandlerhandleMessage內(nèi)邏輯很簡單,它調(diào)用了onHandleIntent這個虛擬方法(抽象方法)。并從中取出msg.obj。可以看到它就是一個Intent,接著調(diào)用了stopSelf方法攜帶了msg.arg1(starId),來終止服務(wù)。
  • 4.onStart方法會率先接受到我們啟動服務(wù)的Intent對象,他將該對象最終使用mServiceHandler發(fā)送給HandlerThread內(nèi)部的Looper,交由子線程來處理這個消息,所以我們需要重寫onHandleIntent來實現(xiàn)自己的需求。
    HandlerThread原理可參考:HandlerThread線程間通信 源碼解析

自此流程就梳理完了,現(xiàn)在我們回到前面提到的問題,當快速兩次啟動IntentService時,他發(fā)生了什么。

1.由內(nèi)部的 hander接受到第一條消息,在onHandleIntent里阻塞,立刻第二條消息進入。
2.第一條消息的StopSelf方法被調(diào)用。此時第二條消息還在處理中。
3.StopSelf方法攜帶了startId調(diào)用了。ActivityManagerstopServiceToken來停止服務(wù),我們接著來看一下源碼。

源碼路徑/core/android/app/ActivityManagerNative.java

   public boolean stopServiceToken(ComponentName className, IBinder token,
            int startId) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IActivityManager.descriptor);
        ComponentName.writeToParcel(className, data);
        data.writeStrongBinder(token);
        data.writeInt(startId);
        mRemote.transact(STOP_SERVICE_TOKEN_TRANSACTION, data, reply, 0);
        reply.readException();
        boolean res = reply.readInt() != 0;
        data.recycle();
        reply.recycle();
        return res;
    }

可以看到入?yún)⒗飻y帶了ComponentName BinderStartId,前面沒有講到,這里補充一下,StartId 是每次啟動服務(wù)時都會攜帶過來的一個標記,它用來表示該服務(wù)在終止以前被啟動了多少次。而后stopServiceToken方法將startId和和Binder一并寫入了Parcel對象內(nèi)。很抱歉,分析到這里翻車了,我沒法再根據(jù)調(diào)試跟進去,斷點下了是一把紅叉。如果有大神知道這里如何動態(tài)調(diào)這塊,還請告訴我一聲,感激不盡。(或者我弄錯了這里根本不是這樣調(diào)的。)

不過這里已經(jīng)大致能說明通過Binder傳遞了消息 mRemote.transact(STOP_SERVICE_TOKEN_TRANSACTION, data, reply, 0);來暫停服務(wù)。mRemote就是ActivityManagerNative(錯誤)我們再順著思路找到了onTransact方法里的case語句

    case STOP_SERVICE_TOKEN_TRANSACTION: {
            data.enforceInterface(IActivityManager.descriptor);
            ComponentName className = ComponentName.readFromParcel(data);
            IBinder token = data.readStrongBinder();
            int startId = data.readInt();
            boolean res = stopServiceToken(className, token, startId);
            reply.writeNoException();
            reply.writeInt(res ? 1 : 0);
            return true;
        }

非常有意思的是我們可以發(fā)現(xiàn)stopServiceToken方法是一個遞歸調(diào)用。此時我更加懵了。沒有找到return的點,這將會是死循環(huán)。是不是思路錯誤了。

但是通過上層日志看出來,當IntentService里有消息存在時它不會結(jié)束掉。一定是IntentService內(nèi)部消息全部被消耗后才會結(jié)束。

今天先到這里,我們來日弄明白了底層如何實現(xiàn)的后再戰(zhàn)。


續(xù)錯誤糾正

前面的問題我們今天找到答案了。實際上面講到的mRemote并非ActivityManagerNative,而是代理對象。以至于我誤認為他們在遞歸調(diào)用。這是不對的。mRemote實際是ActivityManagerProxy,他是一個本地代理對象,而經(jīng)過transact調(diào)用實際運行的是遠端的ActivityManagerService中,這里牽扯到了AMSActivityManager通信流程,我們后續(xù)再單獨分析。先把IntentService給弄明白。
源碼地址(需要翻墻):ActivityManagerService

我們發(fā)現(xiàn)在ActivityManagerService里調(diào)用的stopServiceToken調(diào)用了mServices.stopServiceTokenLocked方法。

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

這里的mServicesActiveServices,我們跟進去看。

 ServiceLookupResult res =retrieveServiceLocked(service, resolvedType, callingPackage,
                    callingPid, callingUid, userId, true, callerFg, false);

ServiceRecord r = res.record;

 boolean stopServiceTokenLocked(ComponentName className, IBinder token,
            int startId) {
        if (r != null) {
            if (startId >= 0) {
                ServiceRecord.StartItem si = r.findDeliveredStart(startId, false);
                if (si != null) {
                    while (r.deliveredStarts.size() > 0) {
                        ServiceRecord.StartItem cur = r.deliveredStarts.remove(0);
                        cur.removeUriPermissionsLocked();
                        if (cur == si) {
                            break;
                        }
                    }
                }

                if (r.getLastStartId() != startId) {
                    return false;
                }
                if (r.deliveredStarts.size() > 0) {
                    Slog.w(TAG, "stopServiceToken startId " + startId
                            + " is last, but have " + r.deliveredStarts.size()
                            + " remaining args");
                }
            }

            synchronized (r.stats.getBatteryStats()) {
                r.stats.stopRunningLocked();
            }
            r.startRequested = false;
            if (r.tracker != null) {
                r.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
                        SystemClock.uptimeMillis());
            }
            r.callStart = false;
            final long origId = Binder.clearCallingIdentity();
            bringDownServiceIfNeededLocked(r, false, false);
            Binder.restoreCallingIdentity(origId);
            return true;
        }
        return false;
    }

我們可以看到最關(guān)鍵的if (r.getLastStartId() != startId)不是最后一個startId就直接return false。否則將執(zhí)行 r.stats.stopRunningLocked();來終止。自此這個問題終于真相大白了。

回過頭了再理一遍。我粗略的畫了一張圖。順序從上往下看。

IntentService的源碼本身并不復雜,讀者不要被我深究stopSelf方法牽扯到AMS里給繞暈了。AMS這塊后續(xù)我會單獨做文章分析,這一塊特別重要。

如果本篇看得比較云霧頭疼,可以先去熟悉下面兩篇。
HandlerThread線程間通信 源碼解析
Handler消息源碼流程分析(含手寫筆記)

下一篇,我們將分析ThreadPoolExecutor線程池。

本文參考:


如何下次找到我?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容