上一篇我們分析了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)注mServiceLooper和mServiceHandler即可。 - 2.首先我們看
onCreate方法,它創(chuàng)建了一個HandlerThread并向ServiceHandler傳遞了一個Looper對象。 - 3.
ServiceHandler的handleMessage內(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)用了。ActivityManager的stopServiceToken來停止服務(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 Binder和StartId,前面沒有講到,這里補充一下,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就是ActivityManagerNativeonTransact方法里的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中,這里牽扯到了AMS與ActivityManager通信流程,我們后續(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);
}
}
這里的mServices是ActiveServices,我們跟進去看。
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線程池。
本文參考:
- AndroidFramework官方源碼
- 《Android開發(fā)藝術(shù)探索》
如何下次找到我?
- 關(guān)注我的簡書
- 本篇同步Github倉庫:https://github.com/BolexLiu/DevNote (可以關(guān)注)
