理解 ContentProvider 原理(一)

基于Android 6.0源码剖析,本文涉及的相关源码:

一、概述

ContentProvider(内容提供者)用于提供数据的统一访问格式,封装底层的具体实现。对于数据的使用者来说,无需知晓数据的来源是数据库、文件,或者网络,只需简单地使用ContentProvider提供的数据操作接口,也就是增(insert)、删(delete)、改(update)、查(query)四个过程。

1.1 ContentProvider

ContentProvider作为Android四大组件之一,并没有Activity那样复杂的生命周期,只有简单地onCreate过程。ContentProvider是一个抽象类,当实现自己的ContentProvider类,只需继承于ContentProvider,并且实现以下六个abstract方法即可:

  • onCreate():执行初始化工作;
  • insert(Uri, ContentValues):插入新数据;
  • delete(Uri, String, String[]):删除已有数据;
  • update(Uri, ContentValues, String, String[]):更新数据;
  • query(Uri, String[], String, String[], String):查询数据;
  • getType(Uri):获取数据MIME类型。

1.2 Uri

从ContentProvider的数据操作方法可以看出都依赖于Uri,对于Uri有其固定的数据格式,例如:content://com.gityuan.articles/android/3

  • 前缀:默认开头content://;
  • 授权:唯一标识com.gityuan.articles;
  • 路径:指定数据类别以及数据项/android/3;

1.3 ContentResolver

其他app或者进程想要操作ContentProvider,则需要先获取其相应的ContentResolver,再利用ContentResolver类来完成对数据的增删改查操作,下面列举一个查询操作,查询得到的是一个Cursor结果集,再通过操作该Cursor便可获取想要查询的结果。

1.4 类图

content_provider

二、流程分析

接下来,从源码角度来说说,以数据查询query的为例来说说ContentProvider的整个完整流程。

2.1 相关成员变量

在开始源码分析之前,先说说涉及到的几个关于contentProvider的重要的成员变量。

2.1.1 AMS

  • CONTENT_PROVIDER_PUBLISH_TIMEOUT: 对于attached进程,用于publish该进程中的ContentProvider的超时时长为10s,超过10s则会被hung住。
  • CONTENT_PROVIDER_RETAIN_TIME: 保持ContentProvider所在进程的上次活动状态的持续时长为20s,当超过20s则运行其下降到正常的 cached LRU列表, 这样做的目的是为了避免在低内存情况下,ContentProvider所在进程发生波动。
  • mProviderMap:记录所有的contentProvider
  • mLaunchingProviders:记录所有的存在client等待其发布完成的contentProvider列表,一旦发布完成则相应的contentProvider便会从该列表移除;

2.1.2 ProcessRecord

  • pubProviders:记录进程中所有创建的ContentProvider;
  • conProviders:记录进程中所有使用的ContentProvider;

2.1.3 ActivityThread

  • mProviderMap:记录所有本地和引用对象;
  • mProviderRefCountMap:记录所有对其他进程中的ContentProvider的引用计数;
  • mLocalProviders:记录所有本地的ContentProvider,以IBinder以key;
  • mLocalProvidersByName:记录所有本地的ContentProvider,以组件名为key。

2.2 getContentResolver

Context中调用getContentResolver,经过层层调用(过程省略),最后调用到ContextImpl类。

[-> ContextImpl.java]

该方法获取的mContentResolver赋值操作是在ContextImpl对象实例化过程完成的:

mContentResolver的真实类型为ApplicationContentResolver;该类继承于ContentResolver,位于ContextImpl的内部类。获取到ContentResolver(简称CR)。接下来,再来看看query操作。

2.3 CR.query

[-> ContentResolver.java]

该方法涉及到ContentProvider的stable与unstable之分,一般地query的流程:

  • 调用acquireUnstableProvider(),尝试获取unstable的ContentProvider;
  • 然后执行query操作;

但是当在执行query过程抛出DeadObjectException,即代表ContentProvider所在进程死亡,则开始尝试获取stable的ContentProvider,执行流程:

  • 先调用unstableProviderDied来处理刚才创建的unstable的ContentProvider;
  • 调用acquireProvider(),尝试获取stable的ContentProvider;
  • 然后执行query操作;

如果再次发生ContentProvider进程死亡,则会杀掉该ContentProvider所对应的客户端进程。

不论是acquireUnstableProvider还是acquireProvider方法,最终都会调用ActivityThread的同一个方法acquireProvider()。先用一句话说说stable与unstable的区别,采用unstable类型的ContentProvider的app不会因为远程ContentProvider进程的死亡而被杀,stable则恰恰相反。这便是ContentProvider坑爹之处,对于app无法事先决定创建的ContentProvider是stable,还是unstable 类型的,也便无法得知自己的进程是否会依赖于远程ContentProvider的生死。

2.4 CR.acquireUnstableProvider

[-> ContentResolver.java]

2.4.1 ACR.acquireUnstableProvider

[-> ContextImpl.java]

getAuthorityWithoutUserId()的过程是字符截断过程,即去掉auth中的UserId信息,比如com.gityuan.articles@123,经过该方法处理后就变成了com.gityuan.articles

2.4.2 AT.acquireProvider

[-> ActivityThread.java]

该方法的主要功能:

  • 首先,尝试获取已存储的provider,当成功获取则直接返回,否则继续执行;
  • 通过AMS来获取provider,当无法获取auth所对应的provider则直接返回,否则继续执行;
  • 采用installProvider安装provider,并该provider的增加引用计数。

2.4.3 AT.acquireExistingProvider

[-> ActivityThread.java]

  • 首先从mProviderMap查询是否存在相对应的provider,若不存在则直接返回,否则继续执行;
  • 当provider所在进程已经死亡,则回调死亡处理方法handleUnstableProviderDiedLocked后返回,否则继续执行;
  • 当provider已经存在引用计数,则继续增加引用计数,否则不增加。

2.4.4 AMS.getContentProvider

ActivityManagerNative.getDefault()返回的是AMP,AMP经过binder IPC通信传递给AMS来完成相应工作,关于这个调用过程前面的文章已经讲过多次这里就不再介绍了。

[-> ActivityManagerService.java]

2.4.5 AMS.getContentProviderImpl

该方法比较长,简单总结整个过程:

  1. 调用getRecordForAppLocked获取调用者的进程记录ProcessRecord;
  2. 根据authority,从mProviderMap中查询相应的ContentProviderRecord;
  3. 当ContentProvider已发布时:
    • 权限检查
    • 当允许运行在调用者进程且已发布,则直接返回
    • 增加引用计数
    • 更新进程LRU队列
    • 更新进程adj
    • 当provider进程被杀时,则减少引用计数并调用appDiedLocked,且设置ContentProvider为没有发布的状态
  4. 当ContentProvider没有发布时:
    • 根据authority,获取ProviderInfo对象;
    • 权限检查
    • 当provider不是运行在system进程,且系统未准备好,则抛出IllegalArgumentException
    • 当拥有该provider的用户并没有运行,则直接返回
    • 根据ComponentName,从mProviderMap中查询相应的ContentProviderRecord;
    • 当首次调用,则创建对象ContentProviderRecord
    • 当允许运行在调用者进程且ProcessRecord不为空,则直接返回
    • 当provider并没有处于mLaunchingProviders队列,则启动它
      • 当ProcessRecord不为空,则加入到pubProviders,并开始安装provider;
      • 当ProcessRecord为空,则启动进程
    • 增加引用计数
  5. 等待provider发布完成

小知识:CPR.canRunHere [-> ContentProviderRecord.java]

该ContentProvider是否能运行在调用者所在进程需要满足以下条件:

  • 条件1:ContentProvider在AndroidManifest.xml文件配置multiprocess=true;或调用者进程与ContentProvider在同一个进程。
  • 条件2:ContentProvider进程跟调用者所在进程是同一个uid。

2.4.6 AT.installProvider

[-> ActivityThread.java]

2.5 CPP.query

[-> ContentProviderNative.java ::ContentProviderProxy]

2.5.1 CPN.onTransact

[-> ContentProviderNative.java]

2.5.2 Transport.query

[-> ContentProvider.java ::Transport]

整个查询过程较复杂的,客户端创建BulkCursorToCursorAdaptor,服务端创建的=CursorToCursorAdaptor等还进一步展开,先留坑。。。

三、发布ContentProvider

system_server进程调用startProcessLocked()创建子进程后,在子进程会调用AMS.attachApplication(),ATP.bindApplication再经过binder IPC会调用到目标进程的AT.bindApplication()方法,接下来从该方法说起。

3.1 AT.bindApplication

[-> ActivityThread.java]

该方法先发送消息H.SET_CORE_SETTINGS,再发送消息H.BIND_APPLICATION。这里我们主要讲跟provider相关的部分,当主线程收到H.BIND_APPLICATION消息后,会调用handleBindApplication方法。

3.2 AT.handleBindApplication

[-> ActivityThread.java]

3.3 AT.installContentProviders

[-> ActivityThread.java]

3.4 AT.installProvider

[-> ActivityThread.java]

一般地,installProvider()的第二个参数holder为空,则用于ContentProvider进程的安装过程;第二个参数holder不为空,则用于Client端的使用。

3.5 AMS.publishContentProviders

由AMP.publishContentProviders经过binder IPC,交由AMS.publishContentProviders来处理

四、小节

4.1 CR.query

4.2 releaseProvider

CASE 1: releaseProvider

CASE 2: releaseUnstableProvider

4.3 unstableProviderDied

 整个文章其实还刚刚介绍的整个流程调用链4.1,后续还需要再进一步解释 4.2,4.3……,以及增加流程与架构图。。。

作者微博:@Gityuan

原文出处:Gityuan.com

1 收藏 评论

关于作者:gityuan

Android系统工程师,www.gityuan.com博主,个人新浪微博:http://weibo.com/gityuan 个人主页 · 我的文章 · 3 ·     

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部