基于Android10.0,分析ContentProvider原理
一、概述
ContentProvider用于提供数据的统一访问格式,封装具体的实现。对于数据的使用无需知道是数据库、文件、网络,只需要使用ContentProvider的数据操作接口,即增(insert)删(delete)改(update)查(query)。
1.1 ContentProvider
ContentProvider作为四大组件之一,没有Activity复杂的生命周期,只有简单的onCreate过程。ContentProvider是一个抽象类,当实现自己的ContentProvider类,需要继承ContentProvider,并且实现下面6个抽象即可:
1 | insert(Uri, ContentValues):插入新数据; |
Uri数据格式如下:content://com.skytoby.articles/android/1
字段 | 含义 | 对应项 |
---|---|---|
前缀 | 默认的固定开头格式 | content:// |
授权 | 唯一表示provider | com.skytoby.articles |
路径 | 数据类别以及数据项 | /android/1 |
1.2 ContentResolver
其他进程想要操作ContentProvider,需要获取其对应的ContentResolver,利用ContentResolver类完成对数据的增删改查操作,下面列举一个查询的操作。
1 | //获取ContentResolver |
1.3 类图
CPP和CPN是Binder通信的C/S两端
ACR(ApplicationContentResolver)继承于ContentResolver,是ContextImpl的内部类,ACR的实现通过调用其成员变量mMainThread来实现。
1.4 重要成员变量
类名 | 成员变量 | 含义 |
---|---|---|
AMS | CONTENT_PROVIDER_PUBLISH_TIMEOUT | 默认值为10s |
AMS | mProviderMap | 记录所有contentProvider |
AMS | mLaunchingProviders | 记录存在客户端等待publish的ContentProviderRecord |
PR | pubProviders | 该进程创建的ContentProviderRecord |
PR | conProviders | 该进程使用的ContentProviderConnection |
AT | mLocalProviders | 记录所有本地的ContentProvider,以IBinder以key |
AT | mLocalProvidersByName | 记录所有本地的ContentProvider,以组件名为key |
AT | mProviderMap | 记录该进程的contentProvider |
AT | mProviderRefCountMap | 记录所有对其他进程中的ContentProvider的引用计数 |
CONTENT_PROVIDER_PUBLISH_TIMEOUT
(10s): provider所在进程发布其ContentProvider的超时时长为10s,超过10s则会系统所杀;mProviderMap
: AMS和AT都有一个同名的成员变量, AMS的数据类型为ProviderMap,而AT则是以ProviderKey为key的ArrayMap类型;mLaunchingProviders
:记录的每一项是一个ContentProviderRecord对象, 所有的存在client等待其发布完成的contentProvider列表,一旦发布完成则相应的contentProvider便会从该列表移除;- PR:ProcessRecord, AT: ActivityThread;
mLocalProviders
和mLocalProvidersByName
:都是用于记录所有本地的ContentProvider,不同的只是key。
1.5 query流程图
二、发布ContentProvider
通过ContentProvider共享数据时,需要编写一个类继承ContentProvier,创建数据库,并在AndroidManifest声明该Provider,这样其他的进行就可以通过ContentResolver去查询共享的信息。先来看一下应用时如何发布ContentProvider,提供给其他应用使用。ContentProvider一般是在应用进程启动的时候启动,是四大组件中最早启动的。进程的启动在四大组件与进程启动那里有详细的分析。
发布ContentProvider分两种情况:Provider进程未启动,Provider进程已经启动但未发布。
Provider进程未启动
systemserver进程调用startProcessLocked创建provider进程,并attach到systemserver后,通过binder机制到Provider进程执行bindApplication方法,见2.1节。
Provider进程已经启动但未发布
如果发现provider进程已经存在且attach到systemserver,但对应的provider还没有发布,
则通过binder机制到provider进程执行scheduleInstallProvider方法,见2.7节。
这两种情况最后都会走到installProvider这个方法。
2.1 Provider进程未启动
2.1.1 AT.bindApplication
[->ActivityThread.java]
1 | public final void bindApplication(String processName, ApplicationInfo appInfo, |
发送BIND_APPLICATION消息到主线程。
2.1.2 AT.handleMessage
[->ActivityThread.java]
1 | public void handleMessage(Message msg) { |
主线程收到BIND_APPLICATION后,执行handleBindApplication方法。
2.1.3 AT.handleMessage
[->ActivityThread.java]
1 | private void handleBindApplication(AppBindData data) { |
2.1.4 AT.installContentProviders
[->ActivityThread.java]
1 | @UnsupportedAppUsage |
2.1.5 AT.installProvider
[->ActivityThread.java]
1 | private ContentProviderHolder installProvider(Context context, |
这里主要是通过反射获取获取contentprovider,并回调其onCreate方法,最后对ContentProviderHolder进行赋值并返回。
2.1.6 AMS.publishContentProviders
[->ActivityManagerService.java]
1 | public final void publishContentProviders(IApplicationThread caller, |
ContentProviders一旦publish成功,则会移除超时发布的消息,并调用notifyAll来唤醒所有等待client端进程。
2.2 Provider进程启动但未发布
下面在看下Provider进程未发布的情况,。。。。。。。。。。。。。。。。。。。。。。
2.2.1 AT.scheduleInstallProvider
[->ActivityThread.java]
1 | @Override |
2.2.2 AT.handleInstallProvider
[->ActivityThread.java]
1 | public void handleInstallProvider(ProviderInfo info) { |
这个方法之后就是2.1.4节的流程installContentProviders
三、查询ContentResolver
一般用ContentProvider的时候,会先得到ContentResolver,之后通过uri可以获取相应的信息,下面就从查询方法开始,分析这个过程的源码流程。
1 | ContentResolver contentResolver = getContentResolver(); |
3.1 CI.getContentResolver
[->ContextImpl.java]
1 | @Override |
Context中调用getContentResolver,经过层层调用到ContextImpl,返回是在ContextImpl对象创建过程中完成赋值的。下面看下查询的操作。
3.2 CR.query
[->ContentResolver.java]
1 | public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri, |
一般获取unstable的Provider:调用acquireUnstableProvider,尝试获取unstable的ContentProvider;然后执行query操作
当执行query操作过程中抛出DeadObjectException,即ContentProvider所在的进程死亡,则尝试获取stable的ContentProvider:
1.先调用unstableProviderDied,清理刚创建的unstable的ContentProvider
2.调用acquireProvider,尝试获取stable的ContentProvider,此时当ContentProvider进程死亡,则会杀掉该ContentProvider的客户端进程;
3.执行query操作。
stable和unstable的区别:
采用unstable类型的ContentProvider的应用不会因为远程ContentProvider进程的死亡而被杀。
采用stable类型的ContentProvider的应用会因为远程ContentProvider进程的死亡而被杀。
对于应用无法决定创建的ContentProvider是stable还是unstable的,也无法知道自己的进程是否依赖于远程进程的生死。
3.3 CR.acquireUnstableProvider
[->ContentResolver.java]
1 | /** |
3.4 ACR.acquireUnstableProvider
[->ContextImpl.java::ApplicationContentResolver]
1 | private static final class ApplicationContentResolver extends ContentResolver { |
从上面可以看出,无论是acquireProvider还是acquireUnstableProvider方法,最后调用的都市ActivityThread的同一个方法的acquireProvider。getAuthorityWithoutUserId是字符截断过程,即去掉auth中的userid信息,比如com.skytoby.article@666,经过该方法则变成了com.skytoby.article。
3.5 AT.acquireProvider
[->ActivityThread.java]
1 | public final IContentProvider acquireProvider( |
主要过程:1.获取已经存在的provider,如果存在则返回,否则继续执行;
2.通过AMS获取provider,无法获取则返回,否则继续执行;
3.通过installProvider方法安装provider,并增加该provider的引用计数。
3.5.1 AT.acquireExistingProvider
[->ActivityThread.java]
1 | @UnsupportedAppUsage |
查询mProviderMap是否存在provider,如果不存在则直接返回。如果存在判断provider的进程是否死亡,死亡则返回null。如果provider所在的进程还在,则增加引用计数。
3.6 AMS.getContentProvider
[->ActivityManagerService.java]
1 | @Override |
3.7 AMS.getContentProviderImpl
[->ActivityManagerService.java]
1 | private ContentProviderHolder getContentProviderImpl(IApplicationThread caller, |
这个方法比较长,主要分为三个部分:
目标provider存在的情况;目标provider不存在的情况;循环等待目标provider发布完成。
3.7.1 目标provider存在
1 | if (providerRunning) { |
当ContentProvider所在的进程已经存在:
检查权限;当允许运行在调用者进程且已经发布,则直接返回
增加引用计数;更新进程LRU队列;更新adj
当provider进程被杀时,则奸商引用计数并调用appDiedLocked,设置provider为未发布状态。
3.7.2 目标provider不存在
1 | if (!providerRunning) { |
当ContentProvider所在的进程不存在:
根据authority获取ProviderInfo对象;权限检查;
当系统不是运行在system进程,且系统未准备好,则抛出异常;
当拥有该provider的用户没有运行,则直接返回;
当允许运行在调用者进程且ProcessRecord不为空,则直接返回;
当provider并没有在mLaunchingProviders队列,则启动它:
当ProcessRecord不为空,则直接加入到pubProviders队列,并安装provider
当ProcessRecord为空,则启动进程
增加引用计数
3.7.3 等待目标provider发布
【ContentProviderRecord
1 | static final int CONTENT_PROVIDER_WAIT_TIMEOUT = 20 * 1000; |
循环等待20s,直到发布完成才退出。
3.7.4 CPR.canRunHere
[->ContentProviderRecord.java]
1 | public boolean canRunHere(ProcessRecord app) { |
ContentProvider是否能运行在调用者所在的进程需要满足以下条件
1.ContentProvider在AndroidManifest.xml文件配置中multiprocess=true;或者调用者进程和ContentProvider在同一进程
2.ContentProvider进程跟调用者所在的进程是同一个uid。
3.8 AT.installProvider
[->ActivityThread.java]
1 | private ContentProviderHolder installProvider(Context context, |
3.8.1 AMS.incProviderCountLocked
[->ActivityManagerService.java]
1 | ContentProviderConnection incProviderCountLocked(ProcessRecord r, |
3.8.2 AMS.removeContentProvider
[->ActivityManagerService.java]
1 | /** |
3.8.3 AMS.decProviderCountLocked
[->ActivityManagerService.java]
1 | boolean decProviderCountLocked(ContentProviderConnection conn, |
3.8.4 AT.incProviderRefLocked
[->ActivityThread.java]
1 | private final void incProviderRefLocked(ProviderRefCount prc, boolean stable) { |
3.8.5 AT.installProviderAuthoritiesLocked
[->ActivityThread.java]
1 | private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider, |
3.8.6 ProviderRefCount
[->ActivityThread.java::ProviderRefCount]
1 | private static final class ProviderRefCount { |
获取到Provider后,就可以进行查询操作了。
3.9 CPP.query
[->ContentProviderNative.java::ContentProviderProxy]
1 | @Override |
3.10 CPN.onTransact
[->ContentProviderNative.java]
1 | @Override |
3.11 Transport.query
[->ContentProvider.java::Transport]
1 | public Cursor query(String callingPkg, Uri uri, @Nullable String[] projection, |
四、总结
本文通过分析了发布ContentProvider的过程,并分析query过程,先获取provider在安装provider信息,最后才查询。而查询操作主要分为两种情况:
4.1 进程未启动
provider进程不存在,需要创建进程并发布相关的provider
1.client进程:通过Binder向systemserver进程请求相应的provider
2.systemserver进程:如果目标进程未启动,则调用startProcessLocked启动进程,当启动完成,当cpr.provider ==null,则systemserver进入wait阶段,等待目标provider发布;
3.provider进程:进程启动后执行attach到systemserver,而后bindApplication,在这个过程会installProvider和PublishContentProviders,再binder到systemserver进程;
4.systemserver进程:回到systemserver发布provider信息,并且通过notify机制唤醒当前处于wait状态的binder线程,并将结果返回给client进程;
5.client进程:回到client进程,执行installProvider操作,安装provider
关于CONTENT_PROVIDER_PUBLISH_TIMEOUT
超时机制所统计的时机区间是指在startProcessLocked之后会调用AMS.attachApplicationLocked为起点,一直到AMS.publishContentProviders的过程。
4.2 进程已启动
provider进程已启动但未发布,需要发布相关的provider
Client进程:获取provider发现cpr为空,则调用scheduleInstallProvider来向provider所在的进程发出一个oneway的binder请求,进入wait状态;
provider进程:安装完成provider信息后,通过notify机制唤醒当前处于wait状态的binder线程。
如果provider在publish之后,这是在请求provider则没用最右边的过程,直接AMS.getContentProvierImpl之后便进入AT.installProvider的过程,而不会再进入wait过程。
4.3 引用计数
provider分为stable provider和unstable provider,主要在于引用计数的不同。
provider引用计数的增加与减少关系,removePending是指即将被移除的引用,lastRef表示当前引用为0。
方法 | stableCount | unstableCount | 条件 |
---|---|---|---|
acquireProvider | +1 | 0 | removePending=false |
acquireProvider | +1 | -1 | removePending=true |
acquireUnstableProvider | 0 | +1 | removePending=false |
acquireUnstableProvider | 0 | 0 | removePending=true |
releaseProvider | -1 | 0 | lastRef=false |
releaseProvider | -1 | 1 | lastRef=true |
releaseUnstableProvider | 0 | -1 | lastRef=false |
releaseUnstableProvider | 0 | 0 | lastRef=true |
当Client进程存在对某个provider的引用时,Provider进程死亡则会根据provider类型进行不同的处理:
对于stable provider,会杀掉所有和该provider建立stable连接的非persistent进程;
对于unstable provider,不会导致client进程被级联所杀,会回调unstableProviderDied来清理相关信息。
当stable和unstable引用计数都为0则移除connection信息
- AMS.removeContentProvider过程移除connection相关所有信息。
附录
源码路径
1 | frameworks/base/core/java/android/app/ActivityThread.java |