Service通过onBind跨进程启动流程源码探究

根据《Activity跨进程启动流程源码探究》我们可以清楚以下几点:
1)Context的通用实现是在ContextIml这个类中
2)Activity的启动过程需要借助ActivityManagerService(AMS)这个服务端来完成,其本质是通过Binder通信。
3)Binder通信很重要,Context作为客户端向AMS发起start请求,AMS作为服务端接收到请求后启动目标进程用于跨进程通信。AMS借助IApplicationThread引用发起目标进程Activity启动请求,注意IApplicationThread是新进程的主线程的子类ApplicationThread的代理接口,但是这些耗时操作是在Binder线程完成的。
4)在新进程中通过H这个Handler进行线程切换,并且切回了主线程。Instrumentation、Application、Activity、ContextImpl、Window、DecorView、WindowManager、ViewRoot等实例创建都在主线程中完成
鉴于此,我们分析Service的绑定启动过程从ContextImpl的bindService方法开始分析。

1.源进程发起绑定任务

在员进程的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, Handler
            handler, 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变量赋值,就是为了提醒用户,要切回到源主线程。这是在为Service绑定成功后,调用源进程的ServiceConnection#onServiceConnected方法做准备。当然,ServiceConnection#onServiceConnected方法在主线程执行,需要通过mActivityThread这个主线程Handler切回到主线程。
2)调用ActivityManager.getService().bindService方法,将sd这个Binder实例作为参数传入,开启Binder线程。跟我们之前的分析一致,ContextImpl这个代理或者叫客户端开始通知AMS这个服务端发起执行绑定任务了。

2.目标进程启动

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

private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handler
            handler, 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既是。在ActiveServices.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-0
            sendServiceArgsLocked(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-1
            if (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-2
            if ((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方法,发现enterPoint被赋值"android.app.ActivityThread"。说明明白进程也是先启动主线程ActivityThread。主线程赋值如下:
ActivityThread线程
并且在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实例,该方法内部会调用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. 执行真正的目标进程启动工作

realStartServiceLocked(r, app, execInFg)方法首先会通过调用app.thread.scheduleCreateService方法创建Service,这个逻辑跟《Service启动流程源码探究
的创建逻辑一致。

4. 执行绑定任务

服务启动后,realStartServiceLocked内部会执行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);

r.app.thread仍然是IApplicationThread.stub的具体实现,也就是ActivityThread.ApplicationThread,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线程里面做,必然是相当耗时啊!

6.

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

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方法内的所有任务都是在主线程工作,不能执行耗时任务。
值得注意的是,这里调用onServiceConnected方法,完成通知客户端工作,行程完整的闭环。
终于绑定成功了~

7.总结

很显然,相对于Activity的启动和Service的启动,Service的绑定任务相对复杂,回顾《Service启动流程源码探究》 发现,主要是增加了将客户端的绑定到服务端的环节。
1)Service的绑定仍然基于Binder的C/S模型,ContextImpl作为客户端向AMS发起绑定任务请求。在此之前要做好预处理工作,
对LoadedApk.ServiceDispather实例化,其本质就是将客户端H这个handler和ServiceConnection的实例封装在ServiceDispatcher中,传递给AMS,这样AMS就具备了切换回主线程的能力了。
2)同时,同时还携带了userID、ApplicationThread等客户端信息及service、flags等目标进程信息。AMS根据ApplicationThread这个键查询ProcessRecord中是否有相关Service记录,由于我们分析的是跨进程通信的首次启动,自然是查不到相关记录的。需要通过service(Intent)携带的进程信息,创建ProcessRecord实例,执行目标进程启动任务。
3)目标进程仍然是Android应用,所以先启动主线程ActivityThread,AMS通过ActivityThread.ApplicationThread这个引用执行CreateService的工作,通过H这个Handler切回主线程处理H.CREATE_SERVICE这个消息。处理过程包括:创建ContextImpl、Application、Service实例等任务。
4)目标进程启动完成,源进程客户端继续向AMS发起BindService的请求。AMS仍然通过ApplicationThread这个引用,借助H这个Handler向目标进程主线程发送H.BIND_SERVICE消息。同时切回到目标进程的主线程ActivityThread处理这个绑定消息。处理过程也很简单,调用Service的onBind方法,并返回IBinder实例用于发布服务绑定成功通知,然后将该参数传给AMS#publishService方法。注意:这个IBinder其实是ServiceRecord的一个实例,记录了我们要绑定的服务的所有信息。
5)一旦接到绑定任务,AMS#ActiveServices这个服务端就会提取ServiceRecord的服务记录信息,ConnectionRecord和IServiceConnection实例,执行绑定任务。通过Binder机制传入的H这个Handler将连接任务切回主线程。如果绑定成功,系统会调用客户端的mConnection.onServiceConnected接口通知客户端。onServiceConnected在主线程工作,不可以进行耗时操作。

诗云:

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


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值