详细剖析IntentService的运作机理
来源:互联网 发布:mysql设置primary key 编辑:程序博客网 时间:2024/06/04 05:17
1 概述
在讲述Service机制的文章里,我们曾经稍微提起过IntentService,今天再来详细剖析一下它。说起来,IntentService只是一个处理异步请求的服务基类而已。当人们通过调用startService()启动IntentService时,实质上是向其发送了一个请求。而如果有多个地方同时向同一个IntentService发送请求的话,那么这些请求会被串行化处理。所以,IntentService常常用于执行那种“一次性处理”的工作。IntentService的运作机理相对比较简单,而且从Android2.3(我手头最早的版本)到Android5.1,IntentService的实现代码一直就没怎么变动过,可见其稳定程度。
当然,我们既然说要详细剖析IntentService,就不可能仅仅讲这么点儿东西。我们可以挖得再深一点儿,看看IntentService到底是如何实现的,又是如何保证串行处理工作的。现在我们就开始吧。
2 先简单说说IntentService的运作
2.1 onCreate()中启动一个消息泵线程
我们先看一下IntentService的onCreate()函数:【frameworks/base/core/java/android/app/IntentService.java】
@Overridepublic void onCreate() { super.onCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper);}可以看到,IntentService创建伊始就会启动一个消息泵线程HandlerThread,然后又创建了一个可以向这个消息泵线程传递消息的Handler(即ServiceHandler)。
ServiceHandler是IntentService的一个内嵌类,其定义如下:
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()! }}
2.2 onStartCommand()利用ServiceHandler向消息泵打入特殊消息
在紧随onCreate()函数之后,系统会调用到onStartCommand()函数,这个大家都很熟悉了。对于startService()动作而言,系统只会在service尚不存在的情况下,创建相应的service,并回调其onCreate()函数。以后,只要这个service没有被销毁,就不会重复再调用onCreate()了。不过,每次调用startService()都会导致走到onStartCommand()函数。IntentService的onStartCommand()函数的代码如下:
@Overridepublic int onStartCommand(Intent intent, int flags, int startId) { onStart(intent, startId); return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;}
@Overridepublic 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)); // 注意这里的r.makeNextStartId() . . . . . . . . . . . . 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消息,画出示意图如下,图中以msg1为基准,绘制了逻辑上的执行路径,请大家注意图中的逻辑序号。
既然消息队列里的消息已经和AMS里的StartItem对应上了,那么在处理完消息后,调用stopSelf()时,应该也必须考虑到这种匹配关系。所以stopSelf()的参数就是startId。前文我们已经看到,stopSelf()内部是在调用:
mActivityManager.stopServiceToken( new ComponentName(this, mClassName), mToken, startId);
【frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java】
@Overridepublic 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;}
那么,为什么要用一个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的知识,我们就先说这么多,有兴趣的同学不妨对比代码看看。
- 详细剖析IntentService的运作机理
- php的运作机理
- [Python]剖析类的机理
- Qt creator 生成的GUI程序运作机理探秘
- MapReduce的运行机理 很详细。
- 深入剖析神经网络的运行机理及实现
- IntentService详细解读
- 详细剖析二进制文件的读写
- 详细剖析二进制文件的读写
- 详细剖析二进制文件的读写
- 详细剖析二进制文件的读写
- 详细剖析二进制文件的读写
- 散列表的详细剖析
- IronPython 源码剖析系列(2):IronPython 引擎的运作流程
- vector的增长机理
- 拜普洛的作用机理
- 防盗链的机理
- 断舍离的机理
- HDU 5976 Detachment
- Ubuntu系统去除带锁标志
- 面试中的 10 大排序算法总结
- CentOS中vsftp安装、配置、卸载
- C++学习笔记一
- 详细剖析IntentService的运作机理
- 神经网络简介-防止过拟合
- Matlab图形修饰函数
- 输入输出流 I/O
- HDU 5976 Detachment
- enum枚举类
- HTML 实现注册小案例
- 洛谷1613跑路
- 交换a和b的方法讲解