Android7.0中关于ContentProvider组件详解

作者:laozhang 时间:2023-10-30 19:48:29 

作为Android的四大组件之一,ContentProvider作为进程之间静态数据传递的重要手段,其在系统级别的应用中起了重大的作用。毫无疑问,ContentProvider核心机制之一也是Binder,但是和其它3大组件又有区别。因为ContentProvider涉及数据的增删查改,当数据量比较大的时候,继续用Parcel做容器效率会比较低,因此它还使用了匿名共享内存的方式。

但是有一个问题是,ContentProvider的提供者进程不再存活时,其他进程通过Provider读一个非常简单的数据时,都需要先把提供者进程启动起来(除非指定multiprocess=true),这对用户是相当不友好的。又因为其是间接通过db进行数据操作,所以效率也远不如直接操作db。因此在用户app中,不是很建议经常使用ContentProvider。不过对于系统级的app,它统一了数据操作的规范,利是远大于弊的。

ContentProvider发布

当进程第一次启动时候会调用handleBindApplication


if (!data.restrictedBackupMode) {
       if (!ArrayUtils.isEmpty(data.providers)) {
         installContentProviders(app, data.providers);
       }
     }

当xml中有provider时,进行provider的发布


final ArrayList<IActivityManager.ContentProviderHolder> results =
     new ArrayList<IActivityManager.ContentProviderHolder>();
   for (ProviderInfo cpi : providers) {
     IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
         false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
     if (cph != null) {
       cph.noReleaseNeeded = true;
       results.add(cph);
     }
   }
   try {
     ActivityManagerNative.getDefault().publishContentProviders(
       getApplicationThread(), results);
   } catch (RemoteException ex) {
   }

@installProvider(这个方法先简单过一下,后面会继续说)


final java.lang.ClassLoader cl = c.getClassLoader();
       localProvider = (ContentProvider)cl.
         loadClass(info.name).newInstance();
       provider = localProvider.getIContentProvider();

@installProviderAuthoritiesLocked


for (String auth : auths) {
     final ProviderKey key = new ProviderKey(auth, userId);
     final ProviderClientRecord existing = mProviderMap.get(key);
     if (existing != null) {
     } else {
       mProviderMap.put(key, pcr);
     }
   }

这里两步把ProviderInfo通过installProvider转换成ContentProvider的Binder对象IContentProvider,并放于ContentProviderHolder中。并根据auth的不同,把发布进程的ProviderClientRecord保存在一个叫mProviderMap的成员变量中,方便第二次调用同一个ContentProvider时,无需重新到AMS中去查询。

AMS @publishContentProviders


final int N = providers.size();
     for (int i = 0; i < N; i++) {
       ContentProviderHolder src = providers.get(i);
       ...
       ContentProviderRecord dst = r.pubProviders.get(src.info.name);
       if (dst != null) {
         ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
         mProviderMap.putProviderByClass(comp, dst);
         String names[] = dst.info.authority.split(";");
         for (int j = 0; j < names.length; j++) {
           mProviderMap.putProviderByName(names[j], dst);
         }
         int launchingCount = mLaunchingProviders.size();
         int j;
         boolean wasInLaunchingProviders = false;
         for (j = 0; j < launchingCount; j++) {
           if (mLaunchingProviders.get(j) == dst) {
             mLaunchingProviders.remove(j);
             wasInLaunchingProviders = true;
             j--;
             launchingCount--;
           }
         }
         if (wasInLaunchingProviders) {
           mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
         }
         ...
       }
     }

可以看到,AMS会遍历所有的ContentProviderHolder,然后调用mProviderMap把信息保存起来,这块接下来说。保存好之后,先去看看之前是不是已经有launch过的,如果已经有launch过的,不再重复launch。再说说这个mProviderMap,这个和ActivityThread中的mProviderMap不太一样,这个是一个成员实例,非真正的map。看看putProviderByClass和putProviderByName。

ProviderMap@putProviderByClass


if (record.singleton) {
     mSingletonByClass.put(name, record);
   } else {
     final int userId = UserHandle.getUserId(record.appInfo.uid);
     getProvidersByClass(userId).put(name, record);
   }

ProviderMap@putProviderByName


if (record.singleton) {
     mSingletonByName.put(name, record);
   } else {
     final int userId = UserHandle.getUserId(record.appInfo.uid);
     getProvidersByName(userId).put(name, record);
   }

可以看到,发布的Provider实际会根据class或authority存在不同的map中。如果是单例,则分别存到相应的mSingleton map中,否则就根据userId存到相应的map中。这样发布的过程就完成了,其他进程需要使用的时候将会在AMS按需读取。

ContentReslover跨进程数据操作

当我们跨进程调用数据时候,会先调用获取用户进程的ContentResolver


context.getContentResolver().query(uri, ...);
public ContentResolver getContentResolver() {
   return mContentResolver;
 }

而这个ContentResolver在每个进程中都存在有且唯一的实例,其在ContextImpl构造函数中就已经初始化了,其初始化的实际对象是ApplicationContentResolver。


mContentResolver = new ApplicationContentResolver(this, mainThread, user);

这个ContentResolver是活在调用者进程中的,它是作为一个类似桥梁的作用。以插入为例:

ContentResolver@insert


IContentProvider provider = acquireProvider(url);
   if (provider == null) {
     throw new IllegalArgumentException("Unknown URL " + url);
   }
   try {
     long startTime = SystemClock.uptimeMillis();
     Uri createdRow = provider.insert(mPackageName, url, values);
     ...
     return createdRow;
   } catch (RemoteException e) {
     return null;
   } finally {
     releaseProvider(provider);
   }

问题就转化成了,拿到其他进程的ContentProvider的Binder对象,有了binder对象就可以跨进程调用其方法了。

ContentResolver@acquireProvider


if (!SCHEME_CONTENT.equals(uri.getScheme())) {
     return null;
   }
   final String auth = uri.getAuthority();
   if (auth != null) {
     return acquireProvider(mContext, auth);
   }

校验其URI,其scheme必须为content。

ApplicationContentResolver@acquireProvider


protected IContentProvider acquireProvider(Context context, String auth) {
     return mMainThread.acquireProvider(context,
         ContentProvider.getAuthorityWithoutUserId(auth),
         resolveUserIdFromAuthority(auth), true);
   }

这里面有个特别的函数会传递一个true的参数给ActivityThread,这意味本次连接是stable的。那stable和非stable的区别是什么呢?这么说吧:

Stable provider:若使用过程中,provider要是挂了,你的进程也必挂。

Unstable provider:若使用过程中,provider要是挂了,你的进程不会挂。但你会收到一个DeadObjectException的异常,可进行容错处理。

继续往下。

ActivityThread@acquireProvider


final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
   if (provider != null) {
     return provider;
   }
IActivityManager.ContentProviderHolder holder = null;
   try {
     holder = ActivityManagerNative.getDefault().getContentProvider(
         getApplicationThread(), auth, userId, stable);
   } catch (RemoteException ex) {
   }
   if (holder == null) {
     return null;
   }
holder = installProvider(c, holder, holder.info,
       true /*noisy*/, holder.noReleaseNeeded, stable);
   return holder.provider;

这里面分了三步,1、寻找自身进程的缓存,有直接返回。 2、缓存没有的话,寻找AMS中的Provider。3、InstallProvider,又到了这个方法。怎么个install法?还是一会儿再说。

@acquireExistingProvider (寻找自身缓存)


synchronized (mProviderMap) {
     final ProviderKey key = new ProviderKey(auth, userId);
     final ProviderClientRecord pr = mProviderMap.get(key);
     if (pr == null) {
       return null;
     }
     IContentProvider provider = pr.mProvider;
     IBinder jBinder = provider.asBinder();
     ...
     ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
     if (prc != null) {
       incProviderRefLocked(prc, stable);
     }
     return provider;

这一步就是读取我们发布时提到的mProviderMap中的缓存。当provider记录存在,且进程存活的情况下,则在provider引用计数不为空时则继续增加引用计数。

缓存不存在,则去AMS中找

AMS@getContentProviderImpl


ContentProviderRecord cpr;
cpr = mProviderMap.getProviderByName(name, userId);
if (providerRunning){
 if (r != null && cpr.canRunHere(r)) {
         ContentProviderHolder holder = cpr.newHolder(null);
         holder.provider = null;
         return holder;
       }
}

public boolean canRunHere(ProcessRecord app) {
   return (info.multiprocess || info.processName.equals(app.processName))
       && uid == app.info.uid;
 }

Provider是提供保护数据的接入访问的。一般情况下,不同进程的访问只能通过IPC来进行,但那是有些情况是可以允许访问者在自己的进程中创建本地Provider来进行访问的。

这种情况是在UID必须相同的前提下,要么同一进程,要么provider设定了multiprocess为true。


if (!providerRunning) {
     cpi = AppGlobals.getPackageManager().resolveContentProvider(name,
         STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
     ...
     ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
     cpr = mProviderMap.getProviderByClass(comp, userId);
     if (r != null && cpr.canRunHere(r)) {
       return cpr.newHolder(null);
     }
     ProcessRecord proc = getProcessRecordLocked(
             cpi.processName, cpr.appInfo.uid, false);
if (proc != null && proc.thread != null) {
           if (!proc.pubProviders.containsKey(cpi.name)) {
             proc.pubProviders.put(cpi.name, cpr);
             proc.thread.scheduleInstallProvider(cpi);
           }
         } else {
           proc = startProcessLocked(cpi.processName,
               cpr.appInfo, false, 0, "content provider",
               new ComponentName(cpi.applicationInfo.packageName,
                   cpi.name), false, false, false);
         }
       }
     }
     mProviderMap.putProviderByName(name, cpr);
   }

这块步骤比较多,挑重点就是,先从AMS的ProviderMap对象中获取AMS缓存。获得后如果Provider没有launch,则AMS通知其进程install其provider。如果进程不存在,则新孵化一个进程。

@InstallProvider

回到第三步中的installProvider


private IActivityManager.ContentProviderHolder installProvider(Context context,
     IActivityManager.ContentProviderHolder holder, ProviderInfo info,
     boolean noisy, boolean noReleaseNeeded, boolean stable)

可以看到,这个方法里面有6个参数,其中包含ContentProviderHolder、ProviderInfo、noReleaseNeeded,这几个很重要的参数。

ContentProviderHolder:当参数为空的时候,说明缓存为空,也就意味着是进程启动的时候调用发布provider。当缓存不为空的时候,还得做一些处理。

ProviderInfo:包含Provider的一些信息,不能为空。

noReleaseNeeded:为true的时候Provider对于自身进程来说或系统的Provider,是永久install的,也就是不会被destory的。


ContentProvider localProvider = null;
   IContentProvider provider;
   if (holder == null || holder.provider == null) {
     try {
       final java.lang.ClassLoader cl = c.getClassLoader();
       localProvider = (ContentProvider)cl.
         loadClass(info.name).newInstance();
       provider = localProvider.getIContentProvider();
       if (provider == null) {
         return null;
       }
       localProvider.attachInfo(c, info);
     } catch (java.lang.Exception e) {
     }
   } else {
     provider = holder.provider;
   }

这部分在发布的时候已经说了,缓存holder为null的时候,new一个实例。


IActivityManager.ContentProviderHolder retHolder;
   synchronized (mProviderMap) {
     IBinder jBinder = provider.asBinder();
     if (localProvider != null) {
       ComponentName cname = new ComponentName(info.packageName, info.name);
       ProviderClientRecord pr = mLocalProvidersByName.get(cname);
       if (pr != null) {
         provider = pr.mProvider;
       } else {
         holder = new IActivityManager.ContentProviderHolder(info);
         holder.provider = provider;
         holder.noReleaseNeeded = true;
         pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
         mLocalProviders.put(jBinder, pr);
         mLocalProvidersByName.put(cname, pr);
       }
       retHolder = pr.mHolder;
     } else {
       ...
     }

如果localProvider不等于null,则意味着是new一个实例的情况,这时候还是先去获取缓存,没有的话再真正地new一个ContentProviderHolder实例,并把通过installProviderAuthoritiesLocked方法把相关信息存入mProviderMap中,这个就是对应发布Provider提的那个方法。


IActivityManager.ContentProviderHolder retHolder;
   synchronized (mProviderMap) {
     ...
     } else {
       ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
       if (prc != null) {
         if (!noReleaseNeeded) {
           incProviderRefLocked(prc, stable);
           try {
             ActivityManagerNative.getDefault().removeContentProvider(
                 holder.connection, stable);
           }
         }
       } else {
         ProviderClientRecord client = installProviderAuthoritiesLocked(
             provider, localProvider, holder);
         if (noReleaseNeeded) {
           prc = new ProviderRefCount(holder, client, 1000, 1000);
         } else {
           prc = stable
               ? new ProviderRefCount(holder, client, 1, 0)
               : new ProviderRefCount(holder, client, 0, 1);
         }
         mProviderRefCountMap.put(jBinder, prc);
       }
       retHolder = prc.holder;
     }

如果localProvider等于空,也就意味着有holder缓存或者new时候出现的异常。那先从计数map中取缓存,如果缓存不为空(之前有过计数了),这时候如果设置了noReleaseNeeded,那就说明不需要计数。如果noReleaseNeeded为false,则把计数器数据转移到一个新引用上,同时销毁旧的。

如果缓存为空,说明之前没有计数过。那还是先通过installProviderAuthoritiesLocked把信息保存到mProviderMap中。这时候如果noReleaseNeeded为true,把stable和非stable的数据都瞎设置了一个1000,反正用不到。。。否则就相应的+1,并把计数器放入相应的缓存中。最后再把holder返回。

再回到ContentResolver方法中,我们拿到了Provider的binder引用,就可以执行相应的方法了。

标签:Android7.0,ContentProvider
0
投稿

猜你喜欢

  • Spring boot整合log4j2过程解析

    2023-11-29 10:47:53
  • Java实现按行读取大文件

    2022-11-05 13:56:57
  • java数据结构与算法数组模拟队列示例详解

    2021-07-23 16:50:24
  • Java Scala实现数据库增删查改操作详解

    2022-02-03 05:09:22
  • springboot html调用js无效400问题及解决

    2023-06-24 02:11:54
  • Apache Commons fileUpload文件上传多个示例分享

    2021-11-13 07:23:51
  • .net后台获取html控件值的2种方法

    2023-01-07 09:12:30
  • C#使用委托实现的快速排序算法实例

    2022-01-22 18:29:52
  • 条件数据库Android:sqllite的简单使用

    2023-07-11 15:39:24
  • Android实现购物车添加商品特效

    2021-06-25 22:59:45
  • RocketMQ NameServer架构设计启动流程

    2023-12-13 07:40:01
  • Android滚轮选择时间控件使用详解

    2022-06-07 21:38:47
  • Activiti7整合Springboot使用记录

    2022-11-11 06:17:24
  • Android SDK Manager国内无法更新的解决方案

    2021-06-23 11:02:19
  • Java中的接口回调实例

    2023-11-29 08:05:43
  • 一个简单的Spring容器初始化流程详解

    2023-11-13 02:10:55
  • springboot大文件上传、分片上传、断点续传、秒传的实现

    2023-06-16 02:18:30
  • C#实现winform渐变效果的方法

    2023-03-14 00:26:06
  • C#中使用闭包与意想不到的坑详解

    2022-08-15 00:13:58
  • java线程之join方法的使用介绍

    2023-01-15 12:18:40
  • asp之家 软件编程 m.aspxhome.com