Android7.0中关于ContentProvider组件详解

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

(0)

相关推荐

  • android基础总结篇之八:创建及调用自己的ContentProvider

    今天我们来讲解一下如何创建及调用自己的ContentProvider. 在前面两篇文章中我们分别讲了如何读写联系人和短消息,相信大家对于ContentProvider的操作方法已经有了一定程度的了解.在有些场合,除了操作ContentProvider之外,我们还有可能需要创建自己的ContentProvider,来提供信息共享的服务,这就要求我们很好的掌握ContentProvider的创建及使用技巧.下面我们就由表及里的逐步讲解每个步骤. 在正式开始实例演示之前,我们先来了解以下两个知识点:

  • Android学习笔记之ContentProvider和Uri详解

    本文介绍了自定义Content Provider的相关内容,完全解析内容提供者的用法.Content Provider,内容提供者,相信大家对这个组件的名字都不陌生,可能是自己平时做的都是一些简单的App,所以对于Content Provider的使用并不是很多,也不是特别熟悉.但是这里还是对Content Provider作个简单的总结,不是很深入,但是希望能给包括我在内的初学者一点帮助,看完这篇能对这个组件有个总体上的了解. 一.使用ContentProvider(内容提供者)共享数据 Co

  • Android 中自定义ContentProvider与ContentObserver的使用简单实例

    Android 中自定义ContentProvider与ContentObserver的使用简单实例 示例说明: 该示例中一共包含两个工程.其中一个工程完成了自定义ContentProvider,另外一个工程用于测试该自定义ContentProvider且在该工程中使用了ContentObserver监听自定义ContentProvider的数据变化 以下代码为工程TestContentProvider ContentProviderTest如下: package cn.testcontentp

  • Android数据持久化之ContentProvider机制详解

    本文实例讲述了Android数据持久化之ContentProvider机制.分享给大家供大家参考,具体如下: 一般而言,android操作系统的应用程序所建立的数据只允许自己使用,应用程序彼此间无法借助公用存储器来共享数据,android系统提供了一个机制,即内容提供器(ContentProvider),来公开自己私有的数据到数据内容器,通过该机制,可以供其他应用程序来读取自己内部的数据,当然也可以访问其他应用程序的数据.通常,内容提供器背后都有SQLite数据库的支持,用以存储内容提供内部数据

  • Android开发教程之ContentProvider数据存储

    一.ContentProvider保存数据介绍 一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据完全暴露出去,而且ContentProvider是以类似数据库中表的方式将数据暴露的.那么外界获取其提供的数据,也就应该与从数据库中获取数据的操作基本一样,只不过是采用URL来表示外界需要访问的"数据库". ContentProvider提供了一种多应用间数据共享的方式. ContentProvider是个实现了一组用于提供其他应用程序存取数据的标准方法的类.应用

  • 实例解析Android系统中的ContentProvider组件用法

    ContentProvider为Android四大组件之一,主要用来应用程序之间的数据共享,也就是说一个应用程序用ContentProvider将自己的数据暴露出来,其他应用程序通过ContentResolver来对其暴露出来的数据进行增删改查. ContenProvider与ContentResolver之间的对话同过Uri(通用资源标识符),一个不恰当的比喻就好像浏览器要显示一个网页要有一个东西发送请求,这相当于ContentResolver,你要拿东西就要知道去哪里拿,你就得知道服务器的域

  • Android ContentProvider实现获取手机联系人功能

    在之前项目中有用到关于获取手机联系人的部分,闲置就想和大家分享一下,话不多说,上代码: java部分: package com.example.content; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.support.v7.app.AppCompatActivity; import android.os.Bundle

  • Android中ContentProvider和ContentResolver详解

    Android中ContentProvider和ContentResolver详解 在Android中,我们的应用有的时候需要对外提供数据接口,可以有如下几种方法: 1)AIDL 2)Broadcast 3)ContentProvider. 使用AIDL需要我们编写AIDL接口以及实现,而且对方也要有相应的接口描述,有点麻烦:使用Broadcast,我们不需要任何接口描述,只要协议文档就可以了,但是有点不好就是,这种方式不直接而且是异步的:使用ContentProvider我们不需要接口描述,只

  • Android 中ContentProvider的实例详解

    Android 中ContentProvider的实例详解 Content Provider 的简单介绍: * Android中的Content Provider 机制可支持在多个应用中存储和读取数据.这也是跨应用 共享数据的唯一方式.在Android系统中,没有一个公共的内存区域,供多个应用共享存储数据: * Android 提供了一些主要数据类型的ContentProvider ,比如:音频.视频.图片和私人通讯录等: 在android.provider 包下面找到一些android提供的C

  • Android7.0中关于ContentProvider组件详解

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

  • OpenStack 中的Nova组件详解

    Open Stack Compute Infrastructure (Nova) Nova是OpenStack云中的计算组织控制器.支持OpenStack云中实例(instances)生命周期的所有活动都由Nova处理.这样使得Nova成为一个负责管理计算资源.网络.认证.所需可扩展性的平台.但是,Nova自身并没有提供任何虚拟化能力,相反它使用libvirt API来与被支持的Hypervisors交互.Nova 通过一个与Amazon Web Services(AWS)EC2 API兼容的w

  • 微信小程序中的swiper组件详解

    微信小程序中的swiper组件 微信小程序中的swiper组件真的是简单方便 提供了页面中图片文字等滑动的效果 <swiper> <swiper-item></swiper-item> <swiper-item></swiper-item> <swiper-item></swiper-item> </swiper> 这里的就是一个滑块视图容器:而就是你希望滑动的东西,可以是文字也可以是image 其中swipe

  • Android7.0 工具类:DiffUtil详解

    一 概述 DiffUtil是support-v7:24.2.0中的新工具类,它用来比较两个数据集,寻找出旧数据集->新数据集的最小变化量. 说到数据集,相信大家知道它是和谁相关的了,就是我的最爱,RecyclerView. 就我使用的这几天来看,它最大的用处就是在RecyclerView刷新时,不再无脑mAdapter.notifyDataSetChanged(). 以前无脑mAdapter.notifyDataSetChanged()有两个缺点: 1.不会触发RecyclerView的动画(删

  • Android开发应用中Broadcast Receiver组件详解

    BroadcastReceiver(广播接收器)是Android中的四大组件之一.下面就具体介绍一下Broadcast Receiver组件的用法. 下面是Android Doc中关于BroadcastReceiver的概述: ①广播接收器是一个专注于接收广播通知信息,并做出对应处理的组件.很多广播是源自于系统代码的──比如,通知时区改变.电池电量低.拍摄了一张照片或者用户改变了语言选项.应用程序也可以进行广播──比如说,通知其它应用程序一些数据下载完成并处于可用状态. ②应用程序可以拥有任意数

  • JavaScript中自定义swiper组件详解

    目录 效果展示 组件设置 步骤1 步骤2 步骤3 使用组件 步骤1 步骤2 步骤3 总结 效果展示 组件设置 步骤1 在pages目录下,新建文件夹components 步骤2 在components下建立新文件夹swiper 在swiper目录下,新建4个文件,分别为 swiper. jsswiper. jsonswiper.wxml swiper.wxss 各文件位置示意图如下: 注:此时编译会报错 错误显示在json那里 先不用管 后面把代码复制粘贴上去再编译就好了 步骤3 分别把下面代码

  • COM组件中调用JavaScript函数详解及实例

    COM组件中调用JavaScript函数详解及实例 要求是很简单的,即有COM组件A在IE中运行,使用JavaScript(JS)调用A的方法longCalc(),该方法是一个耗时的操作,要求通知IE当前的进度.这就要求使用回调函数,设其名称为scriptCallbackFunc.实现这个技术很简单: 1 .组件方(C++) 组件A 的方法在IDL中定义: [id(2)] HRESULT longCalc([in] DOUBLE v1, [in] DOUBLE v2, [in, optional

  • Vue3中使用defineCustomElement 定义组件详解

    目录 使用 Vue 构建自定义元素 跳过组件解析 传递 DOM 属性 defineCustomElement() 生命周期 Props 事件 插槽 依赖注入 将 SFC 编译为自定义元素 基于 Vue 构建自定义元素库 defineComponent() defineAsyncComponent() 使用 Vue 构建自定义元素 Web Components 是一组 web 原生 API 的统称,允许开发者创建可复用的自定义元素 (custom elements). 自定义元素的主要好处是,它们

  • 用C++实现求N!中末尾0的个数的方法详解

    题目描述: 输入一个正整数n,求n!(即阶乘)末尾有多少个0? 比如: n = 10; n! = 3628800,所以答案为2 输入描述: 输入为1行,n(1≤n≤1000) 输出描述: 输出一个整数 样例: 输入:10 输出:2 看到这个题,常规思路就是先把阶乘算出来,再用算出来的结果求余,余数为0则个数加1,代码如下: #include<iostream> using namespace std; int main(void) { int n, m = 1; cin >> n;

  • vue中的scope使用详解

    我们都知道vue slot插槽可以传递任何属性或html元素,但是在调用组件的页面中我们可以使用 template scope="props"来获取插槽上的属性值,获取到的值是一个对象. 注意:scope="它可以取任意字符串"; 上面已经说了 scope获取到的是一个对象,是什么意思呢?我们先来看一个简单的demo就可以明白了~ 如下模板页面: <!DOCTYPE html> <html> <head> <title>

随机推荐