IntentService源码解析

上一篇我们分析了Android中的线程间通信HandlerThread的原理.HandlerThread充分的利用Handler的通信机制和消息队列。本篇将分析IntentService的作用和原理。

警告:本篇的源码可能过于枯燥和乏味,中间涉及到一次采坑,错误的分析。最后纠正回来了。我觉得此处是比较有意义的。读者对着源码的同时细读本篇可能更好一点。

目录

  • IntentService简单介绍
  • 源码分析
  • 续错误纠正

IntentService

IntentService继承自Service本质上就是一个服务。但它内部拥有一个完整的HandlerThread。可以这样说IntentService=Service+HandlerThread。

我们先来说下它的常见用法。将复杂耗时操作交由IntentService来处理,你可以通过Intent的方式启动它,然后实现onHandleIntent方法,在onHandleIntent的任何操作将属于内部HandlerThread的子线程。

代码如下:

继承一个IntentService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

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

启动它,并附带一个消息

1
2
3
4

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

这里我们启动了CustomIntentService并附带了一个hello过去。此时onHandleIntent方法会接受到我们这个Intent,并模拟耗时后打印日志。
注意两点

  • 1.这里的Intent是间接性传递过去的,按通常的思路这个Intent是在主线程中,但是这里并不是主线程。后面我们会从源码上来分析。
  • 2.我们知道IntentService的特性会执行完任务后自动销毁。但有一种情况,如果我们快速调用两次startService会如何?下面是快速调用两次的日志。
1
2
3
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

显然,并不是说每次执行都会销毁掉,当第二条消息过来的时候它并没有销毁,而是做完后才销毁。但是这是为什么呢?先卖个关子,我们带着疑问去看源码吧。

源码分析:

注:此处源码删除了一些不影响阅读的注释和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

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 内部总共有四个成员变量,我们只需要关注mServiceLoopermServiceHandler即可。
  • 2.首先我们看onCreate方法,它创建了一个HandlerThread并向ServiceHandler传递了一个Looper对象。
  • 3.ServiceHandlerhandleMessage内逻辑很简单,它调用了onHandleIntent这个虚拟方法(抽象方法)。并从中取出msg.obj。可以看到它就是一个Intent,接着调用了stopSelf方法携带了msg.arg1(starId),来终止服务。
  • 4.onStart方法会率先接受到我们启动服务的Intent对象,他将该对象最终使用mServiceHandler发送给HandlerThread内部的Looper,交由子线程来处理这个消息,所以我们需要重写onHandleIntent来实现自己的需求。
    HandlerThread原理可参考:HandlerThread线程间通信 源码解析

自此流程就梳理完了,现在我们回到前面提到的问题,当快速两次启动IntentService时,他发生了什么。

1.由内部的 hander接受到第一条消息,在onHandleIntent里阻塞,立刻第二条消息进入。
2.第一条消息的StopSelf方法被调用。此时第二条消息还在处理中。
3.StopSelf方法携带了startId调用了。ActivityManagerstopServiceToken来停止服务,我们接着来看一下源码。

源码路径/core/android/app/ActivityManagerNative.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

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

可以看到入参里携带了ComponentName BinderStartId,前面没有讲到,这里补充一下,StartId 是每次启动服务时都会携带过来的一个标记,它用来表示该服务在终止以前被启动了多少次。而后stopServiceToken方法将startId和和Binder一并写入了Parcel对象内。很抱歉,分析到这里翻车了,我没法再根据调试跟进去,断点下了是一把红叉。如果有大神知道这里如何动态调这块,还请告诉我一声,感激不尽。(或者我弄错了这里根本不是这样调的。)

不过这里已经大致能说明通过Binder传递了消息 mRemote.transact(STOP_SERVICE_TOKEN_TRANSACTION, data, reply, 0);来暂停服务。mRemote就是ActivityManagerNative(错误)我们再顺着思路找到了onTransact方法里的case语句

1
2
3
4
5
6
7
8
9
10
11

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

非常有意思的是我们可以发现stopServiceToken方法是一个递归调用。此时我更加懵了。没有找到return的点,这将会是死循环。是不是思路错误了。

但是通过上层日志看出来,当IntentService里有消息存在时它不会结束掉。一定是IntentService内部消息全部被消耗后才会结束。

今天先到这里,我们来日弄明白了底层如何实现的后再战。


续错误纠正

前面的问题我们今天找到答案了。实际上面讲到的mRemote并非ActivityManagerNative,而是代理对象。以至于我误认为他们在递归调用。这是不对的。mRemote实际是ActivityManagerProxy,他是一个本地代理对象,而经过transact调用实际运行的是远端的ActivityManagerService中,这里牵扯到了AMSActivityManager通信流程,我们后续再单独分析。先把IntentService给弄明白。
源码地址(需要翻墙):ActivityManagerService

我们发现在ActivityManagerService里调用的stopServiceToken调用了mServices.stopServiceTokenLocked方法。

1
2
3
4
5
6
7
8

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

这里的mServicesActiveServices,我们跟进去看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

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

我们可以看到最关键的if (r.getLastStartId() != startId)不是最后一个startId就直接return false。否则将执行 r.stats.stopRunningLocked();来终止。自此这个问题终于真相大白了。

回过头了再理一遍。我粗略的画了一张图。顺序从上往下看。

IntentService的源码本身并不复杂,读者不要被我深究stopSelf方法牵扯到AMS里给绕晕了。AMS这块后续我会单独做文章分析,这一块特别重要。

如果本篇看得比较云雾头疼,可以先去熟悉下面两篇。
HandlerThread线程间通信 源码解析
Handler消息源码流程分析(含手写笔记)

下一篇,我们将分析ThreadPoolExecutor线程池。

本文参考:


如何下次找到我?

随缘打赏!