Android Framework--谈谈bindService

对应用开发者而言,bindService调用肯定是耳熟能详,本文将介绍framework如何表示和组织绑定关系,通过背后原理的分析来加深对Service的理解,除此之外,大家也能领略到框架代码是如何设计并带来一些启发。本文旨在理清主要的设计思路,并不会对所有细节展开描述,代码来自Android N。


bindService基本点

让我们回顾下bindService相关的知识点,这里以在Activity中调用bindService函数为例,函数原型如下:

public boolean bindService(Intent service, ServiceConnection conn,
        int flags)

指定目标Service的Intent,当目标Service的onBind()方法执行后,传进来的ServiceConnection接口就会得到回调,之前Service.onBind()返回的IBinder对象会作为参数传进来。从这个过程可以看到,系统要管理Service以及Service的使用方,维护它们之间的关系并在合适的时机执行某些回调。

bindService的最终实现在ContextImpl.java中,经过一系列处理调用到AMS.bindService方法,函数原型如下:

public int bindService(IApplicationThread caller, IBinder token, Intent service,
        String resolvedType, IServiceConnection connection, int flags, String callingPackage,
        int userId)

ContextImpl封装了很多信息,很容易就可以拿到诸如调用方进程的标识(IApplicationThread),以及调用方Activity的标识(token),这些信息是维护Service绑定关系的一部分材料,后面会再提到。

这里主要提下IServiceConnection参数,看命名好像跟ServiceConnection有关,但ServiceConnection是普通的接口,而IServiceConnection是Binder接口,应该有着某种映射关系,做为Service绑定的一个重要标识,有必要认清这个映射关系。

LoadedApk.java中的如下成员维护了该映射关系:

private final ArrayMap> mServices
    = new ArrayMap>();

LoadedApk.ServiceDispatcher封装了ServiceConnection并包含一个IServiceConnection,可以认为LoadedApk.ServiceDispatcher和IServiceConnection是一对一的关系,Binder代理端通过调用IServiceConnection的接口,找到对应的ServiceConnection。

即:指定一个Context和ServiceConnection,有且只有一个IServiceConnection与之对应。
也就意味着,在不同的Activity中用同一个ServiceConnection调用bindService,最后传给AMS的是不同的IServiceConnection。其它推论可自行推导。


绑定关系的表示:ConnectionRecord

先了解下AMS中几个类的含义,这在后面的内容中要用到:

  • ServiceRecord:表示一个Service
  • ActivityRecord:表示一个Activity
  • ProcessRecord:表示一个应用进程

绑定关系至少包含如下信息:

  • 发起绑定的Intent
  • 绑定到哪个Service(ServiceRecord)
  • 哪个应用进程发起的绑定(ProcessRecord)
  • 哪个Activity发起的绑定(ActivityRecord)
  • 对应的IServiceConnection
  • flags或其它附加信息

ConnectionRecord用来表示绑定关系,即每次调用bindService都会生成该类对象,接下来讨论它是如何包含上述信息。

先看下它的成员变量:

/**
 * Description of a single binding to a service.
 */
final class ConnectionRecord {
    final AppBindRecord binding;    // The application/service binding.
    final ActivityRecord activity;  // If non-null, the owning activity.
    final IServiceConnection conn;  // The client connection.
    final int flags;                // Binding options.
    final int clientLabel;          // String resource labeling this client.
    final PendingIntent clientIntent; // How to launch the client.
    String stringName;              // Caching of toString.
    boolean serviceDead;            // Well is it?
}

关注前面四个成员,activity,conn,flags很好理解:
* activity:发起绑定的Activity
* conn:对应的IServiceConnection
* flags:绑定选项

就这点信息显然不够,别忘了还有个AppBindRecord,那为什么不像其它信息那样逐项列出来,答案是为了方便在不同层面上对ConnectionRecord进行管理。要理解有哪些层面,我们先来看一个例子。

假设有一个Service,配置如下:

<service android:name="MyService">
    <intent-filter>
        <action android:name="com.example.sea.myapplication.MyService" />
    intent-filter>
    <intent-filter>
        <action android:name="com.example.sea.myapplication.MyService2" />
    intent-filter>
service>

然后在Activity中执行如下代码,用不同的Action来调用bindService:

Intent intent = new Intent("com.example.sea.myapplication.MyService");
intent.setPackage("com.example.sea.myapplication");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);

intent.setAction("com.example.sea.myapplication.MyService2");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);

问题:MyService.onBind()会执行几次?
答案是两次,应用可以根据传入Intent的Action不同来返回不同的IBinder。

这就引申出framework关于Service绑定的设计:对同一个Service而言,不同的Intent代表不同的绑定关系,每个Intent对应一个IBinder,Intent是否相同用下面的函数来判断,true表示相同:

public boolean filterEquals(Intent other) {
    if (other == null) {
        return false;
    }
    if (!Objects.equals(this.mAction, other.mAction)) return false;
    if (!Objects.equals(this.mData, other.mData)) return false;
    if (!Objects.equals(this.mType, other.mType)) return false;
    if (!Objects.equals(this.mPackage, other.mPackage)) return false;
    if (!Objects.equals(this.mComponent, other.mComponent)) return false;
    if (!Objects.equals(this.mCategories, other.mCategories)) return false;

    return true;
}

那么ServiceRecord管理ConnectionRecord,首先应该以Intent进行映射,如下:

final ArrayMap<Intent.FilterComparison, IntentBindRecord> bindings
        = new ArrayMap<Intent.FilterComparison, IntentBindRecord>();

Intent.FilterComparison是对Intent的封装,用来判断Intent是否相等以及计算哈希值,可以简单地认为是一对一关系。不同的Intent对应不同的IntentBindRecord,按照上述例子的结论,IntentBindRecord应该保存有IBinder:

/**
 * A particular Intent that has been bound to a Service.
 */
final class IntentBindRecord {
    /** The running service. */
    final ServiceRecord service;
    /** The intent that is bound.*/
    final Intent.FilterComparison intent; // 
    /** All apps that have bound to this Intent. */
    final ArrayMap apps
            = new ArrayMap();

    /** Binder published from service. */
    IBinder binder;
    /** Set when we have initiated a request for this binder. */
    boolean requested;
    /** Set when we have received the requested binder. */
    boolean received;
    /** Set when we still need to tell the service all clients are unbound. */
    boolean hasBound;
    /** Set when the service's onUnbind() has asked to be told about new clients. */
    boolean doRebind;
}

binder成员即Service.onBind()对不同Intent返回的IBinder,和预想的一致。

接下来还需要如何组织ConnectionRecord?注意到IntentBindRecord 的apps成员,类型为:

ArrayMap<ProcessRecord, AppBindRecord>

还记得ConnectionRecord.binding成员吗,就是这里的AppBindRecord,它跟ProcessRecord形成映射关系。也就是说在Intent分类下,以bindService调用方进程(ProcessRecord)再进行分类:

/**
 * An association between a service and one of its client applications.
 */
final class AppBindRecord {
    final ServiceRecord service;    // The running service.
    final IntentBindRecord intent;  // The intent we are bound to.
    final ProcessRecord client;     // Who has started/bound the service.

    final ArraySet connections = new ArraySet<>();
                                    // All ConnectionRecord for this client.
}

经过这样的组织,从ConnectionRecord出发有如下引用链:
ConnectionRecord->AppBindRecord->IntentBindRecord->ServiceRecord
引用链上对象包含的成员均能得到访问,通过上面类定义很容易知道这里面包含了关于绑定关系的所有信息。

通过这样的设计,将ConnectionRecord按不同的层面进行分类,使得结构很清晰;另外,不同层面上的对象可以得到复用,比如AppBindRecord和IntentBindRecord。


ConnectionRecord的组织

到这里为止,我们已经得到了一个ConnectionRecord,它包含了关于绑定的所有信息。接下来还要将其按照一定规则组织起来,以方便遍历和查询。

1、ServiceRecord要知道所有关于它的ConnectionRecord,于是在ServiceRecord.java中有:

final ArrayMap> connections
        = new ArrayMap>();
                        // IBinder -> ConnectionRecord of all bound clients

2、要查询指定Intent下某个ProcessRecord的所有ConnectionRecord,于是在AppBindRecord.java中有:

final ArraySet connections = new ArraySet<>();
                                // All ConnectionRecord for this client.

3、要查询某个应用进程执行了几次绑定,于是在ProcessRecord.java中有:

// All ConnectionRecord this process holds
final ArraySet connections = new ArraySet<>();

4、要查询某个Activity执行了几次绑定,于是在ActivityRecord.java中有:

HashSet connections; // All ConnectionRecord we hold

5、要通过IServiceConnection全局查询某个ConnectionRecord,于是在ActiveServices.java中有:

/**
 * All currently bound service connections.  Keys are the IBinder of
 * the client's IServiceConnection.
 */
final ArrayMap> mServiceConnections = new ArrayMap<>();

既然有绑定,相应的就要有解绑,解绑的过程自然是要解除上述的组织关系,代码位于ActiveServices.java:

boolean unbindServiceLocked(IServiceConnection connection) {
    IBinder binder = connection.asBinder();
    if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "unbindService: conn=" + binder);
    ArrayList clist = mServiceConnections.get(binder);
    if (clist == null) {
        Slog.w(TAG, "Unbind failed: could not find connection for "
              + connection.asBinder());
        return false;
    }

    final long origId = Binder.clearCallingIdentity();
    try {
        while (clist.size() > 0) {
            ConnectionRecord r = clist.get(0);
            removeConnectionLocked(r, null, null);
            if (clist.size() > 0 && clist.get(0) == r) {
                // In case it didn't get removed above, do it now.
                Slog.wtf(TAG, "Connection " + r + " not removed for binder " + binder);
                clist.remove(0);
            }

            if (r.binding.service.app != null) {
                if (r.binding.service.app.whitelistManager) {
                    updateWhitelistManagerLocked(r.binding.service.app);
                }
                // This could have made the service less important.
                if ((r.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
                    r.binding.service.app.treatLikeActivity = true;
                    mAm.updateLruProcessLocked(r.binding.service.app,
                            r.binding.service.app.hasClientActivities
                            || r.binding.service.app.treatLikeActivity, null);
                }
                mAm.updateOomAdjLocked(r.binding.service.app);
            }
        }
    } finally {
        Binder.restoreCallingIdentity(origId);
    }

    return true;
}

首先以connection为键在mServiceConnections中查询对应的ConnectionRecord列表,然后对于每个ConnectionRecord,调用removeConnectionLocked进行移除:

void removeConnectionLocked(
    ConnectionRecord c, ProcessRecord skipApp, ActivityRecord skipAct) {
    IBinder binder = c.conn.asBinder();
    AppBindRecord b = c.binding;
    ServiceRecord s = b.service;
    ArrayList clist = s.connections.get(binder);
    if (clist != null) {
        // 从ServiceRecord.connections中移除
        clist.remove(c);
        if (clist.size() == 0) {
            s.connections.remove(binder);
        }
    }
    b.connections.remove(c);  // 从AppBindRecord.connections中移除
    if (c.activity != null && c.activity != skipAct) {
        if (c.activity.connections != null) {
            // 从ActivityRecord.connections中移除
            c.activity.connections.remove(c);
        }
    }
    if (b.client != skipApp) {
        // 从ProcessRecord.connections中移除
        b.client.connections.remove(c);
        ...
    }
    clist = mServiceConnections.get(binder);
    if (clist != null) {
        // 从ActiveServices.mServiceConnections中移除
        clist.remove(c);
        if (clist.size() == 0) {
            mServiceConnections.remove(binder);
        }
    }

    mAm.stopAssociationLocked(b.client.uid, b.client.processName, s.appInfo.uid, s.name);

    if (b.connections.size() == 0) {
        b.intent.apps.remove(b.client);
    }
    ...
}

代码仅关注绑定关系移除部分,详见上面的注释。

你可能感兴趣的:(android,framework)