Android 浅析 ContentProvider (三) 获取原理
前言
Linus Benedict Torvalds : RTFSC – Read The Fucking Source Code
ContentProvider下文将会简称CP。
ContentResolver下文将会简称CR。
概括
本文首先从ContentResolver一路深入浅析CP客户端如何找到对应的provider。
CP 获取原理
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
这是CP客户端用来启动服务端的代码。在获取到cursor后就可以从中取出数据集。
我们首先通过getContentResolver()来获取一个ContentResolve对象。
private final ApplicationContentResolver mContentResolver;
public ContentResolver getContentResolver() {return mContentResolver;}
通过代码我们可以知道返回的mContentResolver对象是ApplicationContentResolver类,而ApplicationContentResolver类又是继承于ContentResolver的。所以我们接下来首先分析下ContentResolver。
Step1、ContentResolver
SDK:provides applications access to the content model.
翻译:提供app访问内容模型。
CR 通过一套标准及统一的接口获取其他应用程序暴露的数据,那个标准就是URI,除了URI以外,还必须知道需要获取的数据段的名称,以及此数据段的数据类型。
主要方法
因为CP是以类似数据库中表的方式将数据暴露出去,那么CR也将采用类似数据库的操作来从CP中获取数据。
insert
public final @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues values){}
public final int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] values){}
insert:
Inserts a row into a table at the given URL.
If the content provider supports transactions the insertion will be atomic.
翻译:在给定的URL插入一行到表里面,如果CP支持,处理过程将是原子操作。
bulkInsert:
Inserts multiple rows into a table at the given URL.
This function make no guarantees about the atomicity of the insertions.
翻译:在给定的URL插入多行到表里面,处理过程不对原子操作作保证。
delete
public final int delete(Uri url, String where, String[] selectionArgs){}
Deletes row(s) specified by a content URI.
If the content provider supports transactions, the deletion will be atomic.
翻译:删除一行或多行由uri指定,如果CP支持,处理过程将是原子操作。
update
public final int update(Uri uri, ContentValues values, String where,
String[] selectionArgs) {}
Update row(s) in a content URI.
If the content provider supports transactions the update will be atomic.
翻译:在给定的URL更新多行,如果CP支持,处理过程将是原子操作。
Query
public final Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder) {}
public final Cursor query(final Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder,
CancellationSignal cancellationSignal) {}
Query the given URI, returning a Cursor over the result set.
翻译:从给定的URI中查询,从结果集中返回一个Cursor对象。
注意:
1:提供一个明确的空间,防止从不希望被使用的内存中读取数据。
2:使用问号参数标记,如“电话=?”而不是在选择参数中的显式值,因此,不同的值的查询将被确认为缓存的目的相同的。
call
public final Bundle call(Uri uri, String method, String arg, Bundle extras) {}
Call a provider-defined method. This can be used to implement read or write interfaces which are cheaper than using a Cursor and/or do not fit into the traditional table model.
翻译:
调用提供者定义方法。这可以用来实现读写接口,比使用游标和/或不符合传统的表模型更好。
核心方法
在insert、query、updata这些方法中都调用一个最主要的方法acquireProvider()。
acquireProvider
//Returns the content provider for the given content URI.
public final IContentProvider acquireUnstableProvider(Uri uri) {
...
return acquireUnstableProvider(mContext, uri.getAuthority());
}
acquireProvider()是一个抽象函数,由子类去实现细节。那么在android中,实现细节的子类就是ApplicationContentResolver。
Step2、ApplicationContentResolver
ApplicationContentResolver是contextimpl的内部类,继承ContentResolver。其内部封装了一个ActivityThread对象,最后调用的方法都是调用ActivityThread的方法,所以ApplicationContentResolver就是一个中间过度类。
接着我们来看下ActivityThread关于acquireProvider()的核心方法:
Step3、ActivityThread
acquireProvider:
public final IContentProvider acquireProvider(
Context c, String auth, int userId, boolean stable) {
final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
if (provider != null) {return provider;}
IActivityManager.ContentProviderHolder holder = null;
holder = ActivityManagerNative.getDefault().getContentProvider(
getApplicationThread(), auth, userId, stable);
if (holder == null) {return null;}
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
}
这段函数主要流程:
1、从已经保存的本地provider中查找是否有对应的provider,有则将其返回退出。
2、从AMS中找到对应的provider。
3、安装从AMS中找到的provider。并且将provider保存在本地。
4、返回此provider。
最后我们来看下ActivityManagerService关于getContentProvider()的核心方法:
Step4、ActivityManagerService
getContentProvider:
public final ContentProviderHolder getContentProvider(
IApplicationThread caller, String name, int userId, boolean stable) {
enforceNotIsolatedCaller("getContentProvider");
return getContentProviderImpl(caller, name, null, stable, userId);
}
private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
String name, IBinder token, boolean stable, int userId) {
...
ContentProviderRecord cpr;
// First check if this content provider has been published...
cpr = mProviderMap.getProviderByName(name, userId);
...
if (!providerRunning) {
cpi = AppGlobals.getPackageManager().resolveContentProvider(name, STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
}
...
cpr = mProviderMap.getProviderByClass(comp, userId);
if (firstClass) {
try {
ApplicationInfo ai = AppGlobals.getPackageManager().
getApplicationInfo(cpi.applicationInfo.packageName, STOCK_PM_FLAGS, userId);
cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
...
if (firstClass) {
mProviderMap.putProviderByClass(comp, cpr);
}
mProviderMap.putProviderByName(name, cpr);
...
// Wait for the provider to be published...
synchronized (cpr) {
while (cpr.provider == null) {
......
try {
cpr.wait();
}
}
}
return cpr != null ? cpr.newHolder(conn) : null;
}
这段函数主要从AMS中查找已经注册了的provider,然后将provider对象返回给客户端。
在ActivityManagerService中,有两个成员变量是用来保存系统中的Content Provider信息的,一个是mProvidersByName,一个是mProvidersByClass,前者是以Content Provider的authoriry值为键值来保存的,后者是以Content Provider的类名为键值来保存的。这里要用两个Map来保存,这里为了方便根据不同条件来快速查找而设计的。
如果在mProviderMap里找不到对应的provider,会通过AppGlobals.getPackageManager()从PackageManagerService里获取。然后保存到mProviderMap里面。
Step5、PackageManagerService
resolveContentProvider:
public ProviderInfo resolveContentProvider(String name, int flags, int userId) {
synchronized (mPackages) {
final PackageParser.Provider provider = mProvidersByAuthority.get(name);
PackageSetting ps = mSettings.mPackages.get(provider.owner.packageName);
return PackageParser.generateProviderInfo(provider, flags,
ps.readUserState(userId), userId);
}
}
查找的最底层就是在PMS里面,PMS里面的mProvidersByAuthority保存了本机所有apk包含的provider定义。通过它可以找到所有对应的provider。(终)
小结
ContentProvider的整个获取原理比较简单,并没有太难的地方,主要还是一层层调用比较费劲,封装了几层。
再来总结下获取的整个流程(以query函数为例):
1、首先每个context类都会内部包含了一个ContentResolver的子对象ApplicationContentResolver。
2、通过调用ApplicationContentResolver的主要方法query来获取CP的数据库数据。
3、调用的过程首先会调用ContentResolver的核心方法acquireProvider()。而acquireProvider()方法是一个抽象方法,其实现是交由子类实现。
4、通过子类的acquireProvider()方法实现了解到主要的实现是交由ActivityThread类来完成。
5、ActivityThread类会做出一个判断,如果本地保存一个需要获取的CP对象实例,就会直接返回这个对象实例,如果没有保存,则会访问AMS对象去查找获取一个对象的CP对象实例,当找到这个对象实例后会保存到本地以便日后快速获取。
6、如果在AMS里面没有找到,就会继续深入到PMS里去从全部的provider中查找。
7、获取到CP对象实例后会通过层层返回,最后再调用该CP对象的query方法获取相应的数据。