欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~

作者:汪毅雄

导语: 本文描述了ContentProvider发布者和调用者这两在Framework层是如何实现的。

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

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

ContentProvider发布

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

  1. if (!data.restrictedBackupMode) {
  2. if (!ArrayUtils.isEmpty(data.providers)) {
  3. installContentProviders(app, data.providers);
  4. }
  5. }

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

  1. final ArrayList<IActivityManager.ContentProviderHolder> results =
  2. new ArrayList<IActivityManager.ContentProviderHolder>();
  3. for (ProviderInfo cpi : providers) {
  4. IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
  5. false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
  6. if (cph != null) {
  7. cph.noReleaseNeeded = true;
  8. results.add(cph);
  9. }
  10. }
  11. try {
  12. ActivityManagerNative.getDefault().publishContentProviders(
  13. getApplicationThread(), results);
  14. } catch (RemoteException ex) {
  15. }

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

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

@installProviderAuthoritiesLocked

  1. for (String auth : auths) {
  2. final ProviderKey key = new ProviderKey(auth, userId);
  3. final ProviderClientRecord existing = mProviderMap.get(key);
  4. if (existing != null) {
  5. } else {
  6. mProviderMap.put(key, pcr);
  7. }
  8. }

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

AMS @publishContentProviders

  1. final int N = providers.size();
  2. for (int i = ; i < N; i++) {
  3. ContentProviderHolder src = providers.get(i);
  4. ...
  5. ContentProviderRecord dst = r.pubProviders.get(src.info.name);
  6. if (dst != null) {
  7. ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
  8. mProviderMap.putProviderByClass(comp, dst);
  9. String names[] = dst.info.authority.split(";");
  10. for (int j = ; j < names.length; j++) {
  11. mProviderMap.putProviderByName(names[j], dst);
  12. }
  13. int launchingCount = mLaunchingProviders.size();
  14. int j;
  15. boolean wasInLaunchingProviders = false;
  16. for (j = ; j < launchingCount; j++) {
  17. if (mLaunchingProviders.get(j) == dst) {
  18. mLaunchingProviders.remove(j);
  19. wasInLaunchingProviders = true;
  20. j--;
  21. launchingCount--;
  22. }
  23. }
  24. if (wasInLaunchingProviders) {
  25. mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
  26. }
  27. ...
  28. }
  29. }

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

ProviderMap@putProviderByClass

  1. if (record.singleton) {
  2. mSingletonByClass.put(name, record);
  3. } else {
  4. final int userId = UserHandle.getUserId(record.appInfo.uid);
  5. getProvidersByClass(userId).put(name, record);
  6. }

ProviderMap@putProviderByName

  1. if (record.singleton) {
  2. mSingletonByName.put(name, record);
  3. } else {
  4. final int userId = UserHandle.getUserId(record.appInfo.uid);
  5. getProvidersByName(userId).put(name, record);
  6. }

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

ContentReslover跨进程数据操作

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

  1. context.getContentResolver().query(uri, ...);
  2. public ContentResolver getContentResolver() {
  3. return mContentResolver;
  4. }

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

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

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

ContentResolver@insert

  1. IContentProvider provider = acquireProvider(url);
  2. if (provider == null) {
  3. throw new IllegalArgumentException("Unknown URL " + url);
  4. }
  5. try {
  6. long startTime = SystemClock.uptimeMillis();
  7. Uri createdRow = provider.insert(mPackageName, url, values);
  8. ...
  9. return createdRow;
  10. } catch (RemoteException e) {
  11. return null;
  12. } finally {
  13. releaseProvider(provider);
  14. }

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

ContentResolver@acquireProvider

  1. if (!SCHEME_CONTENT.equals(uri.getScheme())) {
  2. return null;
  3. }
  4. final String auth = uri.getAuthority();
  5. if (auth != null) {
  6. return acquireProvider(mContext, auth);
  7. }

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

ApplicationContentResolver@acquireProvider

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

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

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

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

继续往下。

ActivityThread@acquireProvider

  1. final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
  2. if (provider != null) {
  3. return provider;
  4. }
  5.  
  6. IActivityManager.ContentProviderHolder holder = null;
  7. try {
  8. holder = ActivityManagerNative.getDefault().getContentProvider(
  9. getApplicationThread(), auth, userId, stable);
  10. } catch (RemoteException ex) {
  11. }
  12. if (holder == null) {
  13. return null;
  14. }
  15.  
  16. holder = installProvider(c, holder, holder.info,
  17. true /*noisy*/, holder.noReleaseNeeded, stable);
  18. return holder.provider;

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

@acquireExistingProvider (寻找自身缓存)

  1. synchronized (mProviderMap) {
  2. final ProviderKey key = new ProviderKey(auth, userId);
  3. final ProviderClientRecord pr = mProviderMap.get(key);
  4. if (pr == null) {
  5. return null;
  6. }
  7. IContentProvider provider = pr.mProvider;
  8. IBinder jBinder = provider.asBinder();
  9. ...
  10. ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
  11. if (prc != null) {
  12. incProviderRefLocked(prc, stable);
  13. }
  14. return provider;

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

缓存不存在,则去AMS中找

AMS@getContentProviderImpl

  1. ContentProviderRecord cpr;
  2. cpr = mProviderMap.getProviderByName(name, userId);
  3. if (providerRunning){
  4. if (r != null && cpr.canRunHere(r)) {
  5. ContentProviderHolder holder = cpr.newHolder(null);
  6. holder.provider = null;
  7. return holder;
  8. }
  9. }
  10. public boolean canRunHere(ProcessRecord app) {
  11. return (info.multiprocess || info.processName.equals(app.processName))
  12. && uid == app.info.uid;
  13. }

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

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

  1. if (!providerRunning) {
  2. cpi = AppGlobals.getPackageManager().resolveContentProvider(name,
  3. STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
  4. ...
  5. ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
  6. cpr = mProviderMap.getProviderByClass(comp, userId);
  7. if (r != null && cpr.canRunHere(r)) {
  8. return cpr.newHolder(null);
  9. }
  10. ProcessRecord proc = getProcessRecordLocked(
  11. cpi.processName, cpr.appInfo.uid, false);
  12.  
  13. if (proc != null && proc.thread != null) {
  14. if (!proc.pubProviders.containsKey(cpi.name)) {
  15. proc.pubProviders.put(cpi.name, cpr);
  16. proc.thread.scheduleInstallProvider(cpi);
  17. }
  18. } else {
  19. proc = startProcessLocked(cpi.processName,
  20. cpr.appInfo, false, , "content provider",
  21. new ComponentName(cpi.applicationInfo.packageName,
  22. cpi.name), false, false, false);
  23. }
  24. }
  25. }
  26. mProviderMap.putProviderByName(name, cpr);
  27. }

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

@InstallProvider

回到第三步中的installProvider

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

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

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

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

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

  1. ContentProvider localProvider = null;
  2. IContentProvider provider;
  3. if (holder == null || holder.provider == null) {
  4. try {
  5. final java.lang.ClassLoader cl = c.getClassLoader();
  6. localProvider = (ContentProvider)cl.
  7. loadClass(info.name).newInstance();
  8. provider = localProvider.getIContentProvider();
  9. if (provider == null) {
  10. return null;
  11. }
  12. localProvider.attachInfo(c, info);
  13. } catch (java.lang.Exception e) {
  14. }
  15. } else {
  16. provider = holder.provider;
  17. }

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

  1. IActivityManager.ContentProviderHolder retHolder;
  2. synchronized (mProviderMap) {
  3. IBinder jBinder = provider.asBinder();
  4. if (localProvider != null) {
  5. ComponentName cname = new ComponentName(info.packageName, info.name);
  6. ProviderClientRecord pr = mLocalProvidersByName.get(cname);
  7. if (pr != null) {
  8. provider = pr.mProvider;
  9. } else {
  10. holder = new IActivityManager.ContentProviderHolder(info);
  11. holder.provider = provider;
  12. holder.noReleaseNeeded = true;
  13. pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
  14. mLocalProviders.put(jBinder, pr);
  15. mLocalProvidersByName.put(cname, pr);
  16. }
  17. retHolder = pr.mHolder;
  18. } else {
  19. ...
  20. }

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

  1. IActivityManager.ContentProviderHolder retHolder;
  2. synchronized (mProviderMap) {
  3. ...
  4. } else {
  5. ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
  6. if (prc != null) {
  7. if (!noReleaseNeeded) {
  8. incProviderRefLocked(prc, stable);
  9. try {
  10. ActivityManagerNative.getDefault().removeContentProvider(
  11. holder.connection, stable);
  12. }
  13. }
  14. } else {
  15. ProviderClientRecord client = installProviderAuthoritiesLocked(
  16. provider, localProvider, holder);
  17. if (noReleaseNeeded) {
  18. prc = new ProviderRefCount(holder, client, , );
  19. } else {
  20. prc = stable
  21. ? new ProviderRefCount(holder, client, , )
  22. : new ProviderRefCount(holder, client, , );
  23. }
  24. mProviderRefCountMap.put(jBinder, prc);
  25. }
  26. retHolder = prc.holder;
  27. }

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

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

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

相关阅读

Android 7.0 中 ContentProvider 实现原理的更多相关文章

  1. Android 7.0 启动篇 — init原理(二)(转 Android 9.0 分析)

    ========================================================          ================================== ...

  2. Android 6.0 中的 Wifi 连接

    Android 6.0 中的 Wifi 连接 这几天在写一个软件,结果被其中的 wifi 连接问题困扰了 3 天. 先描述下需求: usb 接口接了一根 usb2serial,通过这个接口接收命令 当 ...

  3. 在Android 5.0中使用JobScheduler

    在Android 5.0中使用JobScheduler 原文链接 : using-the-jobscheduler-api-on-android-lollipop 译者 : Mr.Simple 校对者 ...

  4. 我的Android进阶之旅------>如何解决Android 5.0中出现的警告: Service Intent must be explicit:

    我的Android进阶之旅-->如何解决Android 5.0中出现的警告: java.lang.IllegalArgumentException: Service Intent must be ...

  5. 我的Android进阶之旅------&gt;怎样解决Android 5.0中出现的警告: Service Intent must be explicit:

    我的Android进阶之旅-->怎样解决Android 5.0中出现的警告: java.lang.IllegalArgumentException: Service Intent must be ...

  6. Android 5.0中使用JobScheduler

    在这篇文章中,你会学习到在Android 5.0中怎样使用JobScheduler API. JobScheduler API同意开发人员在符合某些条件时创建运行在后台的任务. 介绍 在Android ...

  7. Android 7.0 启动篇 — init原理(一)(转 Android 9.0 分析)

    ========================================================          ================================== ...

  8. android 4.0 中出错 java.lang.UnsupportedOperationException

    在android4.0中  画图的时候使用: canvas.clipPath(path, Region.Op.XOR); 报错 java.lang.UnsupportedOperationExcept ...

  9. Android Studio3.0中dependencies依赖由compile变为implementation的区别

    前言 Android Studio版本更新至3.0了,更新后,连带着com.android.tools.build:gradle 工具也升级到了3.0.0,在3.0.0中使用了最新的Gralde 4. ...

随机推荐

  1. Hadoop(十七)之MapReduce作业配置与Mapper和Reducer类

    前言 前面一篇博文写的是Combiner优化MapReduce执行,也就是使用Combiner在map端执行减少reduce端的计算量. 一.作业的默认配置 MapReduce程序的默认配置 1)概述 ...

  2. Asp.net Api中使用OAuth2.0实现“客户端验证”

    一.实现继承自OAuthAuthorizationServerProvider的类,实现以"客户端验证"方式传入的相关认证和access_token发放. public class ...

  3. dp百题大过关(第一场)

    好吧,这名字真是让我想起了某段被某教科书支配的历史.....各种DP题层出不穷,不过终于做完了orz 虽然各种手糊加乱搞,但还是要总结一下. T1 Monkey Banana Problem    这 ...

  4. 爆炸快求1~n有多少素数

    这个求一千亿以内的素数大约用6780ms #include <stdio.h> #include <iostream> #include <string.h> #i ...

  5. ssh密钥创建分发(端口号非22)&脚本实现自动创建分发密钥

    1.1 服务端端口号变化了,如何基于秘钥连接 1.1.1 环境准备 实验环境: [root@test ~]# cat /etc/redhat-release CentOS release 6.9 (F ...

  6. java爬虫--jsoup简单的表单抓取案例

    分析需求: 某农产品网站的农产品价格抓取 网站链接:点击打开链接 页面展示如上: 标签展示如上: 分析发现每日价格行情包括了蔬菜,水果,肉等所有的信息,所以直接抓每日行情的内容就可以实现抓取全部数据. ...

  7. 基于python3.x,使用Tornado中的torndb模块操作数据库

    目前Tornado中的torndb模块是不支持python3.x,所以需要修改部分torndb源码即可正常使用 1.开发环境介绍 操作系统:win8(64位),python版本:python3.6(3 ...

  8. JavaScript面向对象(收集整理)

    (1)封装 首先理解构造函数:所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量.对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上. f ...

  9. [原创]阿里云RocketMQ踩过的哪些坑

    由于公司的最近开始使用RocketMQ来做支付业务处理, 便开启了学习阿里云RocketMQ的学习与实践之路, 其中踩了不少的坑, 大部份是由于没有仔细查看阿里云的技术文档而踩的坑. 但是有一个非常大 ...

  10. jQuery学习笔记之Ajax用法详解

    这篇文章主要介绍了jQuery学习笔记之Ajax用法,结合实例形式较为详细的分析总结了jQuery中ajax的相关使用技巧,包括ajax请求.载入.处理.传递等,需要的朋友可以参考下 本文实例讲述了j ...