(4.4.16)use a ContentProviderClient obtained using acquireUnstableContentProviderClient

在项目的组件化方案中,我们使用了以下方式,获取ContentProvider实例,该实例承载跨组件通信的接口实现

context.getContentResolver().acquireContentProviderClient(authority)
 if (client != null) {
            ContentProvider provider = client.getLocalContentProvider();
            return provider;
 }

但是在上线发布之后,我们收到了一些异常上报,该函数返回为null,由此引发了具体原因的排查

一、ContentProviderClient

从ContentResolver中访问Provider的各个操作方法中可以看出,由于Component可能会访问不同的Provider的数据,因此在ContentResolver设计各个操作方法时,均会首先根据操作指定的Uri去acquire provider,但是acquire provider的过程会比较耗费资源,比如Client和AMS之间的IPC通信等。

如果想要避免每次对Provider进行访问时都会acquire provider,Android提供了一个ContentProviderClient类型,它的成员变量包含了Provider的IContentProvider接口,Client可以请求ContentResolver为某一个请求的Provide创建一个ContentProviderClient对象,那么下次Client再次对该Provider进行操作时,可以不用再执行acquire provider操作,ContentProviderClient适用于频繁访问某一个Provide的情况。

Client通过调用acquireContentProviderClient()方法获取ContentProviderClient。

    public final @Nullable ContentProviderClient acquireContentProviderClient(
            @NonNull String name) {
        Preconditions.checkNotNull(name, "name");
        IContentProvider provider = acquireProvider(name);
        if (provider != null) {
            return new ContentProviderClient(this, provider, true);
        }

        return null;
    }

使用ContentProviderClient需要注意一点就是一定在不需要访问Provider时主动释放Provider。

Andromeda:首个适用于多进程架构的组件通信框架中有这样一段话:

在于ContentProviderClient兼容性较差,在有些手机上第一次运行时会crash,这样显然无法接受

1.1 回顾ContentProvider

  • handleBindApplication
    1. data.info.makeApplication
      1. 反射构造Applicaiton
      2. 调用attach
    2. installContentProviders(app, data.providers);
      1. 遍历cpi:ProviderInfos
      2. installProvider(context, null, cpi, false /noisy/, true /noReleaseNeeded/, true /stable/); 并用返回值填充List<IActivityManager.ContentProviderHolder> results
        1. // holder为null表示还没有install过 :if (holder == null || holder.provider == null) {
          1. localProvider = (ContentProvider)cl.loadClass(info.name).newInstance();反射构建实例对象
          2. provider = localProvider.getIContentProvider();获取Binder驱动
          3. localProvider.attachInfo(c, info); 上下文和onCreate生命周期
        2. if (localProvider != null) {
          1. if (pr != null) { // 不为空代表install过provider = pr.mProvider;
          2. else ProviderClientRecord client = installProviderAuthoritiesLocked(provider, localProvider, holder);
            1. ProviderClientRecord pcr = new ProviderClientRecord(auths, provider, localProvider, holder); binder驱动 + 实例对象 封装进入PCR
            2. PCR对象进入 ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
        3. localProvider == null
          1. 还是封装PCR
        4. 返回 client.holder
      3. ActivityManagerNative.getDefault().publishContentProviders(getApplicationThread(), results); install完成之后,要告诉AMS,传递过去Holders,AMS也有一个MAP
    3. mInstrumentation.callApplicationOnCreate(app);
  • ContentResolver.query
    • ApplicationContentResolver.query
      • 获取驱动:IContentProvider binder = ApplicationContentResolver#acquireUnstableProvider(uri) 或者 acquireProvider(uri)
        • 最终:IContentProvider stableProvider = ActivityThread#acquireProvider(Context c, String auth, int userId, boolean stable)
          1. IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
            1. ProviderClientRecord pr = mProviderMap.get(key); 从map中获取PCR
            2. IContentProvider provider = pr.mProvider; 从PCR中获取binderq驱动
            3. IBinder jBinder = provider.asBinder();
          2. IActivityManager.ContentProviderHolder holder = ActivityManagerNative.getDefault().getContentProvider(getApplicationThread(), auth, userId, stable);
            1. 如果进程B不存在则先启动进程B并installprovider,告诉AMS之后,由AMS返回给进程A对方的provider信息(此过程中由进程A发起的请求provider的线程会一直等待)
            2. 如果进程B存在则AMS直接返回给进程A对方的provider信息
          3. holder = installProvider(c, holder, holder.info,true /noisy/, holder.noReleaseNeeded, stable);相对于上文的过程
            1. 由于已经有holder对象,不会创建实例localProvider
            2. localProvider == null 因此仅installProviderAuthoritiesLocked:记录到map中相关binder对象
          4. 返货Binder驱动 holder.provider
      • 驱动操作IContentProvider.query

AMS充当一个中间管理员的角色

  1. 每个进程在启动之后需要把自己应该install的provider, install之后封装PCR进Map,并将PCR的holder告诉AMS,这样后面有其他进程请求这个provider的话,AMS可以告诉你所请求的对端的信息。
  2. AMS持有Holders, Holder持有Binder
  3. 客户端请求时,先找自己map中的pcr,有的话则返回PCR里的Binder驱动;没有,找AMS要Holder,并将Holder中的对象封装PCR进Map,返回holder中的驱动
    也就是说,客户端总会找自己的map的pcr,但是这个pcr里的驱动可能是来自自己搞出来的,也有可能是AMS给的Holder的

二、acquireContentProviderClient 与 acquireUnstableContentProviderClient 的差别

How and when to use a ContentProviderClient obtained using acquireUnstableContentProviderClient?

If you use acquireContentProviderClient then your process will be killed if the Content provider dies.

If you use acquireUnstableContentProviderClient then your process will not be killed if the content provider dies - instead you will get a DeadObjectException - which you need to handle in your code.

You would need to write extra code with the unstable version to deal with recovery when you get a DeadObjectException. You can see default android implementation of query method in ContentResolver.java

As far as I understood there would be no leak in your application caused by use of the unstable version.

猜你喜欢

转载自blog.csdn.net/fei20121106/article/details/86645594