相关文章链接:
IntentService源码解析
上一篇我们分析了Android中的线程间通信HandlerThread的原理.HandlerThread充分的利用Handler的通信机制和消息队列。本篇将分析IntentService的作用和原理。
**警告:**本篇的源码可能过于枯燥和乏味,中间涉及到一次采坑,错误的分析。最后纠正回来了。我觉得此处是比较有意义的。读者对着源码的同时细读本篇可能更好一点。
目录
IntentService简单介绍
源码分析
续错误纠正
IntentService
IntentService继承自Service本质上就是一个服务。但它内部拥有一个完整的HandlerThread。可以这样说IntentService=Service+HandlerThread。
我们先来说下它的常见用法。将复杂耗时操作交由IntentService来处理,你可以通过Intent的方式启动它,然后实现onHandleIntent方法,在onHandleIntent的任何操作将属于内部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的特性会执行完任务后自动销毁。但有一种情况,如果我们快速调用两次startService会如何?下面是快速调用两次的日志。
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
显然,并不是说每次执行都会销毁掉,当第二条消息过来的时候它并没有销毁,而是做完后才销毁。但是这是为什么呢?先卖个关子,我们带着疑问去看源码吧。
源码分析:
注:此处源码删除了一些不影响阅读的注释和方法
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内部总共有四个成员变量,我们只需要关注mServiceLooper和mServiceHandler即可。2.首先我们看
onCreate方法,它创建了一个HandlerThread并向ServiceHandler传递了一个Looper对象。3.
ServiceHandler的handleMessage内逻辑很简单,它调用了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调用了。ActivityManager的stopServiceToken来停止服务,我们接着来看一下源码。
源码路径**/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;
}可以看到入参里携带了ComponentName Binder和StartId,前面没有讲到,这里补充一下,StartId 是每次启动服务时都会携带过来的一个标记,它用来表示该服务在终止以前被启动了多少次。而后stopServiceToken方法将startId和和Binder一并写入了Parcel对象内。很抱歉,分析到这里翻车了,我没法再根据调试跟进去,断点下了是一把红叉。如果有大神知道这里如何动态调这块,还请告诉我一声,感激不尽。(或者我弄错了这里根本不是这样调的。)
不过这里已经大致能说明通过Binder传递了消息 mRemote.transact(STOP_SERVICE_TOKEN_TRANSACTION, data, reply, 0);来暂停服务。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;
}~~非常有意思的是我们可以发现stopServiceToken方法是一个递归调用。此时我更加懵了。没有找到return的点,这将会是死循环。~~是不是思路错误了。
但是通过上层日志看出来,当IntentService里有消息存在时它不会结束掉。一定是IntentService内部消息全部被消耗后才会结束。
今天先到这里,我们来日弄明白了底层如何实现的后再战。
续错误纠正
前面的问题我们今天找到答案了。实际上面讲到的mRemote并非ActivityManagerNative,而是代理对象。以至于我误认为他们在递归调用。这是不对的。mRemote实际是ActivityManagerProxy,他是一个本地代理对象,而经过transact调用实际运行的是远端的ActivityManagerService中,这里牵扯到了AMS与ActivityManager通信流程,我们后续再单独分析。先把IntentService给弄明白。 源码地址(需要翻墙):ActivityManagerService
我们发现在ActivityManagerService里调用的stopServiceToken调用了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;
}我们可以看到最关键的if (r.getLastStartId() != startId)不是最后一个startId就直接return false。否则将执行r.stats.stopRunningLocked();来终止。自此这个问题终于真相大白了。
回过头了再理一遍。我粗略的画了一张图。顺序从上往下看。
IntentService的源码本身并不复杂,读者不要被我深究stopSelf方法牵扯到AMS里给绕晕了。AMS这块后续我会单独做文章分析,这一块特别重要。
如果本篇看得比较云雾头疼,可以先去熟悉下面两篇。 HandlerThread线程间通信 源码解析 Handler消息源码流程分析(含手写笔记)
下一篇,我们将分析ThreadPoolExecutor线程池。
本文参考:
《Android开发艺术探索》
如何下次找到我?
关注我的简书
本篇同步Github仓库:https://github.com/BolexLiu/MyNote (可以关注)
共同學習,寫下你的評論
評論加載中...
作者其他優質文章

