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

根据《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, 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变量赋值,就是为了便于提醒,要切回到主线程。
虽然这里先开了一个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, 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);

注意,这里的connection参数就是IServiceConnection。在bindServiceLocked内部会调用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方法里会调用realStartServiceLocked(r, app, execInFg)方法(注意,这个r就是刚刚的s参数)。

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方法在Binder工作线程执行连接成功后,被H切换到了主线程。
终于绑定成功了~

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了。
第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接口通知客户端。

诗云:

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,104评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,816评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,697评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,836评论 1 298
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,851评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,441评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,992评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,899评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,457评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,529评论 3 341
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,664评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,346评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,025评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,511评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,611评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,081评论 3 377
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,675评论 2 359

推荐阅读更多精彩内容

  • 2.1 Activity 2.1.1 Activity的生命周期全面分析 典型情况下的生命周期:在用户参与的情况下...
    AndroidMaster阅读 3,048评论 0 8
  • bindService的过程要比startService的过程复杂一些,因为bingService之后,发起者可以...
    小爨阅读 6,106评论 1 21
  • Zygote是什么?有什么作用? Android系统底层基于Linux Kernel, 当Kernel启动过程会创...
    Mr槑阅读 2,813评论 4 18
  • 好久没回家了,晚上乘着T95火车回赤壁,此车的终点站是深圳,很熟悉的名称啊,有个人已经不在了,不过记忆还残余着,停...
    这回吃饱了阅读 155评论 0 0
  • 一次偶然的机会,在豆瓣上认识了远在美利坚的Daniel.他是一名在读硕士,对中国文化很感兴趣。而且有计划来中国实地...
    维枷阅读 853评论 0 1