Android Framework源码阅读之BindService

这篇主要讲BindService的主要逻辑 Api-27

bindService相比startService无非就是多了个可以持有Service的引用,那我们就针对这个功能作为切入点.

先看下BindService的使用流程

假设一个实现了AIDL的接口IDoSth.aidl
interface IDoSth {  
    void doSth();
}

调用流程:
Context.bindService(intent, connection, Service.BIND_AUTO_CREATE);

private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        IDoSth currentService = IDownloadService.Stub.asInterface(service);
        try {
            currentService.doSth();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        ...
    }
};

开始源码走起.

bindservice的入口为ContextImpl.bindService(Intent service, ServiceConnection conn, int flags);
然后ContextImpl.bindServiceCommon().
bindService和startService使用区别在于启动者是可以持有Service的渠道的,这个渠道就是IServiceConnection

ContextImpl:
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user) {
    IServiceConnection sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags);
    // 拒绝隐式Intent
    validateServiceIntent(service);
    try {
        ...
        //3.调用服务端bindService
        int res = ActivityManager.getService().bindService(..., sd, ...);
        ...
    }
}

在bindServiceCommon这个方法里先是根据conn去获得IServiceConnection sd.sd最终获得的是LoadedApk的一个静态内部类InnerConnection.这个InnerConnection继承了一个Stub,理解过AIDL都知道这是一个用于进程间通信的Binder.

private static class InnerConnection extends IServiceConnection.Stub{
    final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher;

    InnerConnection(LoadedApk.ServiceDispatcher sd) {
        mDispatcher = new WeakReference<LoadedApk.ServiceDispatcher>(sd);
    }

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

InnerConnection的connected方法是去调用ServiceDispatcher的doConnected()方法,然后就调用ServiceConnection.onServiceConnected(),这样一个正常的绑定Service的流程就结束了.先得出一个结论,那就是IServiceConnection是用于服务端回调客户端的接口,最终服务端会把IBinder传回给客户端让客户端持有服务端Binder的句柄,即上述代码:

    IDoSth currentService = IDownloadService.Stub.asInterface(service);//service就是IBinder

准备好IServiceConnection之后,就去调用服务端AMS的bindService()
服务端:

ActivityManagerService:
    public int bindService(IApplicationThread caller, IBinder token, Intent service,
            String resolvedType, IServiceConnection connection, int flags, String callingPackage,
            int userId) throws TransactionTooLargeException {
            ...
            synchronized(this) {
            return mServices.bindServiceLocked(caller, token, service,
                    resolvedType, connection, flags, callingPackage, userId);
            }
            ...
    }
    

这里的mServices就是AMS中用于管理Service启动的ActiveServices

ActiveServices:

    int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
            String resolvedType, final IServiceConnection connection, int flags,
            String callingPackage, final int userId) throws TransactionTooLargeException {
        ...
        //1.获得要启动的Service信息
        ServiceLookupResult res =
            retrieveServiceLocked(service, resolvedType, callingPackage, Binder.getCallingPid(),
                    Binder.getCallingUid(), userId, true, callerFg, isBindExternal);
        ...
        ServiceRecord s = res.record;
        ...
        //2.得到AppBindRecord,AppBindRecord描述的作用是连接Service和他的绑定者(IntentBindRecord)
        AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
        ConnectionRecord c = new ConnectionRecord(b, activity, connection, flags, clientLabel, clientIntent);
        //3.启动Service
        if ((flags&Context.BIND_AUTO_CREATE) != 0) {
            if (bringUpServiceLocked(s, service.getFlags(), callerFg, false, permissionsReviewRequired) != null) {
                return 0;
            }
        }
        ...
        //4.service如果准备好之后就可以调用ServiceConnection.connected()了
        if (s.app != null && b.intent.received) {
          //Service已经运行, 我们可以立即发布connection.
          try {
              c.conn.connected(s.name, b.intent.binder, false);
          } catch (Exception e) {
              ...
          }
          
          if (b.intent.apps.size() == 1 && b.intent.doRebind) {
              requestServiceBindingLocked(s, b.intent, callerFg, true);
          }
        } else if (!b.intent.requested) {
            //5.
            requestServiceBindingLocked(s, b.intent, callerFg, false);
        }
    }

bindServiceLocked()这个方法里大致是这么个逻辑:

  1. 首先通过retrieveServiceLocked()获得将要启动的Service信息
  2. 然后去找IntentBindRecord,AppBindRecord.这里的操作是去做一系列绑定信息的添加.
  3. 这里如果目标Service还没启动,就会调用bringUpServiceLocked()去启动Service并且返回非空,那么就直接return. 0,下面就不用执行了,原因是启动Service是个异步操作,这样代码就不好继续下去.
  4. 这里有两个判断,一个是s.app != null && b.intent.received.这个received是在publishServiceLocked()之后才会设为true.所以第一次发布会跳过4,进入5
  5. requestServiceBindingLocked()会先调r.app.thread.scheduleBindService()进入客户端调用ActivityThread.handleBindService()然后又会回到服务端,最终回到ActiveServices.publishServiceLocked().在这个方法里才把received设为true并调用c.conn.connected()最终触发ServiceConnection.onServiceConnected()这个开头有分析过.
ActiveServices:
  void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
    try {
        ...
        b.requested = true;
        b.received = true;
        for (int conni=r.connections.size()-1; conni>=0; conni--) {
            ArrayList<ConnectionRecord> clist = r.connections.valueAt(conni);
            for (int i=0; i<clist.size(); i++) {
                ...
                try {
                    c.conn.connected(r.name, service, false);
                } catch (Exception e) {
                }
                ...
            }
        }
    ...
    r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind, r.app.repProcState);
    ...
    return true;
  }

至此,这就是BindService的持有引用的过程.

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

推荐阅读更多精彩内容