热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

Android7.0中关于ContentProvider组件详解

本文描述了Android7.0中关于ContentProvider组件实现原理以及ContentProvider发布者和调用者这两在Framework层是如何实现的。

作为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 results =
      new ArrayList();
    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 

可以看到,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引用,就可以执行相应的方法了。


推荐阅读
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文讲述了如何通过代码在Android中更改Recycler视图项的背景颜色。通过在onBindViewHolder方法中设置条件判断,可以实现根据条件改变背景颜色的效果。同时,还介绍了如何修改底部边框颜色以及提供了RecyclerView Fragment layout.xml和项目布局文件的示例代码。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 本文是关于自学Android的笔记,包括查看类的源码的方法,活动注册的必要性以及布局练习的重要性。通过学习本文,读者可以了解到在自学Android过程中的一些关键点和注意事项。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 本文介绍了在Mac上安装Xamarin并使用Windows上的VS开发iOS app的方法,包括所需的安装环境和软件,以及使用Xamarin.iOS进行开发的步骤。通过这种方法,即使没有Mac或者安装苹果系统,程序员们也能轻松开发iOS app。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
author-avatar
梦魇漂鸟的心之一方老窝
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有