根据《Activity跨进程启动流程源码探究》我们可以清楚以下几点:
1)Context的通用实现是在ContextIml这个类中
2)Activity的启动过程需要借助ActivityManagerService(AMS)这个服务端来完成,其本质是通过Binder通信。
3)Binder通信使用了2次,第一次Context作为客户端向AMS发起start请求,第二次AMS作为客户端向IApplicationThread发起最终的启动请求,我们暂且称为“双Binder切换机制”。
4)第二次Binder通信后,通过H这个Handler进行线程切换,并且切回了主线程。Application、Activity、ContextImpl等实例创建都在主线程中,那些耗时操作其实是在Binder线程完成的。
鉴于此,我们分析Service的绑定启动过程从ContextImpl的bindService方法开始分析。

1.第一次Binder机制

在ContextImpl中提供了bindService和bindServiceAsUser两种方法启动Service,后者提供了两个重载方法,他们最终都会调用bindServiceCommon方法。通过对比bindService和bindServiceAsUser方法的时候不难发现,他们都同时传了mMainThread.getHandler()这个Handler,追踪代码会发现它就是我们ActivityThread中的H,最终传给 LoadedApk.ServiceDispatcher的构造方法,用于切换到主线程。先来看一下bindServiceCommon这个方法,它完成了两个任务:
1)将H这个handler对象传递给LoadedApk.ServiceDispatcher的构造方法,创建IServiceConnection实例,其实是其内部的ServiceDispatcher实例。IServiceConnection是Binder代理接口,IServiceConnection.Stub存根的派生类的具体实现是LoadedApk.ServiceDispatcher的静态内部类InnerConnection。这一调用流程的核心代码如下:

private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handlerhandler, UserHandle user) {// Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser.IServiceConnection sd;if (conn == null) {throw new IllegalArgumentException("connection is null");}if (mPackageInfo != null) {sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags);} else {throw new RuntimeException("Not supported in system context");}

这段代码的意图其实很明显,就是为后期调用ServiceConnection#onServiceConnected方法后切回主线程做准备。点击进入getServiceDispatcher的实现会看到,传入的参数被用来构建ServiceDispatcher,核心代码如下:

public final IServiceConnection getServiceDispatcher(ServiceConnection c,Context context, Handler handler, int flags) {synchronized (mServices) {LoadedApk.ServiceDispatcher sd = null;...if (sd == null) {sd = new ServiceDispatcher(c, context, handler, flags);if (DEBUG) Slog.d(TAG, "Creating new dispatcher " + sd + " for conn " + c)} ...return sd.getIServiceConnection();}}

来看一下ServiceDispatcher的构造方法如下:

        ServiceDispatcher(ServiceConnection conn,Context context, Handler activityThread, int flags) {mIServiceConnection = new InnerConnection(this);mConnection = conn;mContext = context;mActivityThread = activityThread;mLocation = new ServiceConnectionLeaked(null);mLocation.fillInStackTrace();mFlags = flags;}

mIServiceConnection = new InnerConnection(this)说明IServiceConnection的最终实现是ServiceDispatcher.InnerConnection,然后在getServiceDispatcher返回了sd.getIServiceConnection()这个方法,它当然就是mIServiceConnection这个InnerConnection的实例。注意,Handler被命名为mActivityThread,并通过activityThread这个Handler变量赋值,就是为了提醒用户,要切回到主线程。
虽然这里先开了一个Binder线程,其实它什么都没干,就是在为Service绑定成功后的调用ServiceConnection#onServiceConnected方法做准备工作。当然,ServiceConnection#onServiceConnected方法在主线程执行,需要通过mActivityThread这个主线程Handler切回到主线程。
2)调用ActivityManager.getService().bindService方法,开启第二个Binder线程。当然,这里就顺畅多了,跟我们之前的分析一致,ContextImpl这个代理或者叫客户端开始通知AMS这个服务端发起执行绑定任务了。

2.第二次Binder机制

直接来看bindServiceCommon方法中的核心代码部分,如下:

private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handlerhandler, UserHandle user) {// Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser.IServiceConnection sd;...if (mPackageInfo != null) {sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); } ...try {...int res = ActivityManager.getService().bindService( mMainThread.getApplicationThread(), getActivityToken(), service, service.resolveTypeIfNeeded(getContentResolver()), sd, flags, getOpPackageName(), user.getIdentifier());...return res != 0;} }

其实只有一行,调用了ActivityManager.getService().bindService方法,并且把sd这个IServiceConnection也传了进去,这里ContextImpl这个客户端通过bindService这个方法向AMS这个服务端发起了启动请求。第二次Binder正式启动,接下来就是AMS的表演时间了~
AMS#bindService并无特殊之处,将任务交给了ActiveServices的bindServiceLocked方法,代码如下:

return mServices.bindServiceLocked(caller, token, service, resolvedType, connection, flags, callingPackage, userId);

注意,这里的service跟启动Activity时一致,就是Intent的实例,用于从ProcessRecord中查询是否有目标服务记录。ActiveServices.bindServiceLocked方法的记录查询逻辑如下:

    int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,String resolvedType, final IServiceConnection connection, int flags,String callingPackage, final int userId) throws TransactionTooLargeException {......// 这里是为了检查当前发起绑定任务的进程是否还在运行,如果停止运行,通过抛异常来停止发起绑定任务final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);if (callerApp == null) {throw new SecurityException("Unable to find app for caller " + caller+ " (pid=" + Binder.getCallingPid()+ ") when binding service " + service);}// 这里说明绑定工作是由Activity发起的,其他组件不行。ActivityRecord activity = null;if (token != null) {activity = ActivityRecord.isInStackLocked(token);if (activity == null) {Slog.w(TAG, "Binding with unknown activity: " + token);return 0;}}......ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, callingPackage, Binder.getCallingPid(),Binder.getCallingUid(), userId, true, callerFg, isBindExternal);if (res == null) {return 0;}if (res.record == null) {return -1;}ServiceRecord s = res.record;

通过调用内部方法retrieveServiceLocked返回目标进程的结果信息,service参数就是我们启动时传入的Intent,这里作为一个键获得目标服务信息,res.record则清晰地表明我们获取了服务记录ServiceRecord实例,ServiceRecord内部有1个ProcessRecord字段app,由于我们是假设目标进程未启动的状态下发起绑定服务的,所以该实例为空。
bindServiceLocked方法中的connection参数就是IServiceConnection的实例,它作为参数传给了bringUpServiceLocked方法唤起服务,调用核心代码如下:

ConnectionRecord c = new ConnectionRecord(b, activity, connection, flags, clientLabel, clientIntent);...if ((flags&Context.BIND_AUTO_CREATE) != 0) {s.lastActivity = SystemClock.uptimeMillis();if (bringUpServiceLocked(s, service.getFlags(), callerFg, false, permissionsReviewRequired) != null) {return 0;}}

因为是绑定创建,所以系统会自动添加BIND_AUTO_CREATE标记,注意
ConnectionRecord c = new ConnectionRecord(b, activity, connection, flags, clientLabel, clientIntent);
这行代码已经将IServiceConnection存入ConnectionRecord中,变量c又被存入ServiceRecord s这个变量里。在bringUpServiceLocked方法的参数s既是。在bringUpServiceLocked方法里首先会判断目标进程的是否启动,代码如下:

    private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,boolean whileRestarting, boolean permissionsReviewRequired)throws TransactionTooLargeException {//Slog.i(TAG, "Bring up service:");//r.dump("  ");if (r.app != null && r.app.thread != null) { //代码2-0sendServiceArgsLocked(r, execInFg, false);return null;}......final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0; final String procName = r.processName;String hostingType = "service";ProcessRecord app;if (!isolated) {app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false); // 代码2-1if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid+ " app=" + app);if (app != null && app.thread != null) {try {app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);realStartServiceLocked(r, app, execInFg);return null;} catch (TransactionTooLargeException e) {throw e;} catch (RemoteException e) {Slog.w(TAG, "Exception when starting service " + r.shortName, e);}...  }...}// Not running -- get it started, and enqueue this service record// to be executed when the app comes up.if (app == null && !permissionsReviewRequired) { // 代码2-2if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,hostingType, r.name, false, isolated, false)) == null) {...}...}

根据前面的分析,我们知道r.app是空的,条件不成立。继续向下执行,由于目标进程是普通进程,不会存在FLAG_ISOLATED_PROCESS标记,代码2-1所在区域代码逻辑会被执行,由于app为null,系统需要先去启动目标进程,执行代码2-2所在区域逻辑。点击进入mAm.startProcessLocked方法,在AMS中将启动任务交给了其重载方法startProcessLocked,在该方法中调用了Process.start启动目标进程,其核心代码如下:

    private final void startProcessLocked(ProcessRecord app, String hostingType,String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {...if (hostingType.equals("webview_service")) {...} else {startResult = Process.start(entryPoint,app.processName, uid, uid, gids, debugFlags, mountExternal,app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,app.info.dataDir, invokeWith, entryPointArgs);}

其中entryPoint就是ActivityThread.main方法的入口,直接进入ActivityThread.main方法,它将具体任务交给了ActivityThread的attach方法,attach又把启动任务转交给AMS#attachApplication方法,它的内部又调用了attachApplicationLocked方法,这个方法有一个代码块至关重要:

    private final boolean attachApplicationLocked(IApplicationThread thread,int pid) {// Find the application record that is being attached...  either via// the pid if we are running in multiple processes, or just pull the// next app record if we are emulating process with anonymous threads.ProcessRecord app;if (pid != MY_PID && pid >= 0) {synchronized (mPidsSelfLocked) {app = mPidsSelfLocked.get(pid);}} else {app = null;}...// Find any services that should be running in this process...if (!badApp) {try {didSomething |= mServices.attachApplicationLocked(app, processName);checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked");} catch (Exception e) {...}}

mPidsSelfLocked存储了所有当前正在运行的进程信息,app是根据我们刚刚启动的进程记录ProcessRecord,它内部最终调用了mServices.attachApplicationLocked(app, processName)方法,进入mServices是ActiveServices实例,该方法内部会调用realStartServiceLocked(r, app, execInFg)方法,调用流程如下:

    boolean attachApplicationLocked(ProcessRecord proc, String processName)throws RemoteException {// Collect any services that are waiting for this process to come up.if (mPendingServices.size() > 0) {ServiceRecord sr = null;try {for (int i=0; i<mPendingServices.size(); i++) {sr = mPendingServices.get(i);...realStartServiceLocked(sr, proc, sr.createdFromFg);...}}} catch (RemoteException e) {...}}

这样process作为一个参数传入到realStartServiceLocked完成真正的绑定服务任务。此处引起极度舒适~

3.第三次Binder机制

realStartServiceLocked(r, app, execInFg)方法首先会通过调用app.thread.scheduleCreateService方法创建Service,这个逻辑跟《Service启动流程源码探究
》 的创建逻辑一致。ActiveServices是客户端向ApplicationThread这个服务端发起创建Service请求。这是第三次Binder机制。

4.第四次Binder机制

接着,bringUpServiceLocked内部会执行requestServiceBindingsLocked(r, execInFg)方法,该方法又会调用requestServiceBindingLocked(r, ibr, execInFg, false)方法,内部有一句核心代码如下:

 //i.requested这个条件是为了标记,如果已经bind过就不会再次绑定,也就是Service.onBind方法只执行一次if ((!i.requested || rebind) && i.apps.size() > 0) {r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind, r.app.repProcState);

这就是第四次Binder机制,这样一级一级的将r往下传递。r.app.thread仍然是IApplicationThread.stub的具体实现,也就是ActivityThread.ApplicationThread,ActiveServices作为客户端向服务端ApplicationThread发起bind请求,ApplicationThread的处理代码如下:

public final void scheduleBindService(IBinder token, Intent intent,boolean rebind, int processState) {updateProcessState(processState, false);BindServiceData s = new BindServiceData();s.token = token;s.intent = intent;s.rebind = rebind;if (DEBUG_SERVICE)Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid="+ Binder.getCallingUid() + " pid=" + Binder.getCallingPid());sendMessage(H.BIND_SERVICE, s);}

跟ActivityThread.ApplicationThread#scheduleCreateService一致,通过一个静态内部类BindServiceData初始化绑定数据,通过H.BIND_SERVICE切换到主线程完成具体的绑定工作。

5.BIND_SERVICE消息处理

在主线程中处理H.BIND_SERVICE的方法是handleBindService处理也很简单,它首先执行s.onBind(data.intent)获得了IBinder实例,这里的s就是app.thread.scheduleCreateService方法创建的Service,注意:Service的onBind方法是在主线程调用的,所以不可以执行耗时任务。
然后将绑定任务又交给了AMS的publishService,核心代码如下:

if (!data.rebind) {IBinder binder = s.onBind(data.intent); ActivityManager.getService().publishService( data.token, data.intent, binder);
} else {s.onRebind(data.intent);ActivityManager.getService().serviceDoneExecuting(data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
}

刚切到主线程,立马又开启了Binder线程,这次ActivityThread.ApplicationThread又担当起了客户端的角色,向服务端AMS发起publishService请求,既然选择在Binder线程里面做,必然是相当耗时啊!第五次Binder机制的开始了。

6.第五次Binder机制

看一下最后的狂欢吧,代码如下:

mServices.publishServiceLocked((ServiceRecord)token, intent, service)

AMS通过桥接方式,将任务交给了ActiveServices的publishServiceLocked方法,注意token就是s。该方法仍然只有一行关键代码如下:

try {c.conn.connected(r.name, service, false);}

这里的c就是上面存储的ConnectionRecord c变量,它内部的c.conn就是我们传入IServiceConnection实例,也就是InnerConnection的实例。这样我们进入LoadedApk.ServiceDispatcher.InnerConnection#connected(r.name, service, false)方法,看到如下代码:

public void connected(ComponentName name, IBinder service, boolean dead) throws RemoteException {LoadedApk.ServiceDispatcher sd = mDispatcher.get();if (sd != null) {sd.connected(name, service, dead);}}

点击进入ServiceDispatcher的connected方法,会发现如下代码:

public void connected(ComponentName name, IBinder service, boolean dead) {if (mActivityThread != null) {mActivityThread.post(new RunConnection(name, service, 0, dead));} else {doConnected(name, service, dead);}}

这里的mActivityThread只有一个赋值在ServiceDispatcher的构造方法中,它是一个Handler,而且是我们在第1节谈到构造ServiceDispatcher实例时传入的H,mActivityThread.post清晰地告诉我们它将RunConnection切换到了主线程。

ServiceDispatcher(ServiceConnection conn,Context context, Handler activityThread, int flags) {mIServiceConnection = new InnerConnection(this);mConnection = conn;mContext = context;mActivityThread = activityThread;mLocation = new ServiceConnectionLeaked(null);mLocation.fillInStackTrace();mFlags = flags;}

当然,我们还是要看一下RunConnection内部是如何执行的。代码如下:

public void run() {if (mCommand == 0) {doConnected(mName, mService, mDead);} else if (mCommand == 1) {doDeath(mName, mService);}}

它的run方法调用了doConnected方法,看看下面这行代码吧:

            old = mActiveConnections.get(name);// 这里表示重复绑定没用,不会向下执行调用if (old != null && old.binder == service) {// Huh, already have this one.  Oh well!return;}// If there is a new service, it is now connected.if (service != null) {mConnection.onServiceConnected(name, service);}

是的,服务被绑定成功了,并且通知了客户端。注意:这里的意思是ServiceConnection.onServiceConnected方法被H切换到了主线程,在主线程通知客户端连接成功,run方法内的所有任务都是在主线程工作,不能执行耗时任务。
值得注意的是,这次Binder通信跟第一次Binder机制属于同一个Binder机制,前一个完成初始化工作,这里接着调用onServiceConnected方法,完成通知客户端工作,行程完整的闭环。
终于绑定成功了~

7.总结

很显然,相对于Activity的启动和Service的启动,Service的绑定任务相对复杂,回顾《Service启动流程源码探究》 可以发现Service绑定任务多了2次Binder机制。分别是ContextImpl作为客户端LoadedApk作为服务端时的Binder机制,和AMS作为客户端ApplicationThread作为服务端的Binder机制。那么,整个流程就很明朗了,就是5次Binder机制。
第1次Binder机制,ContextImpl作为客户端LoadedApk.ServiceDispather.InnerConnection作为服务端创建IServiceConnection实例,其本质就是将H这个handler和ServiceConnection封装在ServiceDispatcher中。
第2次Binder机制,ContextImpl作为客户端向AMS这个服务端发起绑定请求,并将IServiceConnection实例传给AMS。这样AMS就具备了切换回主线程的H了。并且向根据ApplicationThread这个键查询ProcessRecord中是否有相关Service记录,由于我们分析的是跨进程通信的首次启动,自然是查不到相关记录的。需要通过Intent携带的进程信息,创建ProcessRecord实例,完成绑定任务。
第3次Binder机制,AMS作为客户端向ActivityThread.ApplicationThread这个服务端发起CreateService的请求,ActivityThread.ApplicationThread接到请求后,通过H这个Handler切回主线程处理H.CREATE_SERVICE这个消息。处理过程包括:创建ContextImpl、Application、Service等任务。
第4次Binder机制,创建完任务之后,AMS作为客户端向ActivityThread.ApplicationThread这个服务端发起BindService的请求,ApplicationThread接到请求后,通过H这个Handler切回主线程处理H.BIND_SERVICE这个消息。处理过程相当简单,调用Service的onBind方法返回IBinder实例,然后将该参数传给AMS#publishService方法。注意:这个IBinder其实是ServiceRecord的一个实例,记录了我们要绑定的服务的所有信息。这样就进入第5次Binder机制了。
第5次Binder机制,一旦接到绑定任务,AMS#ActiveServices这个服务端就会提取ServiceRecord的服务信息,ConnectionRecord和IServiceConnection实例,执行绑定任务,最后通过第2次Binder机制传入的H这个Handler将连接任务切回主线程。如果绑定成功,系统会调用客户端的mConnection.onServiceConnected接口通知客户端。onServiceConnected在主线程工作,不可以进行耗时操作。

诗云:

早岁那知世事艰,中原北望气如山。楼船夜雪瓜洲渡,铁马秋风大散关。 塞上长城空自许,镜中双鬓已先斑。出师一表真名世,千载谁堪伯仲间!

笔者在写这篇文章的时候,参考了任玉刚老师《Android开发艺术探索》第9章中四大组件的启动相关内容,请知悉~

Service通过onBind跨进程启动流程源码探究相关推荐

  1. Android音频框架之二 用户录音启动流程源码走读

    前言 此篇是对<Android音频框架之一 详解audioPolicy流程及HAL驱动加载>的延续,此系列博文是记录在Android7.1系统即以后版本实现 内录音功能. 当用户使用 Au ...

  2. Activity启动流程源码分析(基于Android N)

    Activity启动流程源码分析 一个Activity启动分为两种启动方式,一种是从Launcher界面上的图标点击启动,另一种是从一个Activity中设置按钮点击启动另外一个Activity.这里 ...

  3. Activity启动流程源码分析-浅析生命周期函数

    源码分析 接着上一篇 Activity启动流程源码分析-setContentView源码阅读 的讲解,本节介绍一下Activity的生命周期函数何时被调用 要看Activity的生命周期函数何时被调用 ...

  4. Doris FE启动流程源码详细解析

    Doris FE启动流程源码详细解析 一.简介 Apache Doris是一个现代化的MPP分析型数据库产品.仅需亚秒级响应时间即可获得查询结果,有效地支持实时数据分析.Apache Doris的分布 ...

  5. SpringBoot2 | SpringBoot启动流程源码分析(一)

    首页 博客 专栏·视频 下载 论坛 问答 代码 直播 能力认证 高校 会员中心 收藏 动态 消息 创作中心 SpringBoot2 | SpringBoot启动流程源码分析(一) 置顶 张书康 201 ...

  6. DataNode启动流程源码分析

    我们都知道在Hadoop hdfs文件系统中,Datanode是负责hdfs文件对应的数据块存储管理的组件,其会在启动时向NameNode汇报其上拥有的数据块,以及周期性心跳并接收来自NameNode ...

  7. Kernel启动流程源码解析 1 head.S

    bootloader在跳转到kernel前,需要确保如下设置: MMU = off, D-cache = off, I-cache = on or off x0 = physical address ...

  8. SpringBoot配置外部Tomcat项目启动流程源码分析(下)

    前言 SpringBoot应用默认以Jar包方式并且使用内置Servlet容器(默认Tomcat),该种方式虽然简单但是默认不支持JSP并且优化容器比较复杂.故而我们可以使用习惯的外置Tomcat方式 ...

  9. Spark On YARN启动流程源码分析

    1.spark-submit入口介绍 一般的spark作业都是通过命令行spark-submit相关的指令来进行提交,使用--master yarn来指定提交到对应的yarn集群上,如下: ./bin ...

最新文章

  1. 用C#的Raw Socket实现网络封包监视
  2. LeetCode 369. Plus One Linked List--链表--C++,Python解法
  3. Eclipse常用设置
  4. session监听器统计在线人数存入数值后不能取出其值
  5. ObjectStateManager 中已存在具有同一键的对象。ObjectStateManager 无法跟踪具有相同键的多个对象...
  6. 7 Statistical estimation
  7. php的excel源码下载,PHPExcel-5 - 源码下载|Windows编程|其他小程序|源代码 - 源码中国...
  8. ubuntu命令行登录
  9. Visual Studio下使用jQuery的10个技巧
  10. 链路聚合_配置EthTrunk链路聚合
  11. linux 脚本 数字运算符,Shell 基本运算符
  12. 专题九:Simulink系统仿真
  13. UnityWebPlayer打开文件
  14. axure图表组件。echarts。 axure !important 二维码logo嵌入axure汉化美化定制。图表背景透明
  15. 安卓手机运行springboot 应用,做java 服务器
  16. UFS SCSI Inquiry Cmd
  17. html required 无效,html5的input的required使用中遇到的问题及解决方法
  18. 计算机抠图知识,计算机PS抠图方法.doc
  19. 科技云报道:服务数字中国,青云十周年迎来“成年礼”
  20. android使用Vitamio实现视频播放大小屏流畅切换

热门文章

  1. 重磅!教育部再次审批 179 所高校新增本科 AI 专业
  2. Linux Centos中卸载 安装Mysql
  3. 分布式系统原理-世界上只有一种共识算法,那就是Paxos
  4. 交换机vlan配置实训心得_计算机网络实践实验报告基本交换机使用及VLAN配置
  5. GC8548双通道H桥电机驱动芯片
  6. java基础之单元测试
  7. 大学图书馆计算机房,高校图书馆数据中心机房管理研究
  8. netty框架的学习
  9. 谷晟阳:包容父母,就是最好的孝顺
  10. js如何获取json数组的长度