标签:
一、什么ContentProvider
。。。
二、如何使用ContentProvider
。。。
三、沙场练兵-实例操练
。。。
四、深入理解ContentProvider原理
为什么使用ContentProvider可以实现跨进程的通讯,第一反应肯定是这货和binder有关,因为android中只有稍微跟跨进程搭上边的,必定想到binder。
下面就来分析ContentProvider是怎么一步一步利用binder实现跨进程通信的:
1、首先你得创建一个ContentProvider运行在进程A,如上篇博客
AndroidManifest.xml中定义provider:
<provider
android:name=".MyContentProvider"
android:authorities="telefk"//这个很重要,Uri中的主机名,查询数据就靠它
android:enabled="true"
android:exported="true">
</provider>
2、进程B中通过如下接口来访问进程A的数据:
getContentResolver().query(uri,columns,null,null,null);
getContentResolver()实际调用的ContextImpl.java的方法返回mContentResolver对象
此对象在ContextImpl的构造方法中实例化
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
现在来看query方法,其在ContentResolver.java中定义:
public final @Nullable Cursor query(final @NonNull Uri uri, @Nullable String[] projection,
@Nullable String selection, @Nullable String[] selectionArgs,
@Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {
Preconditions.checkNotNull(uri, "uri");
IContentProvider unstableProvider = acquireUnstableProvider(uri);//这个函数很关键,通过指定的Uri找到我们刚刚定义的provider
if (unstableProvider == null) {
return null;
}
IContentProvider stableProvider = null;
......
接下来看acquireUnstableProvider()函数:
百转千回,最终走到了刚刚返回的ApplicationContentResolver.java中
这里又调用了ActivityThread.java 的acquireProvider()方法,我们继续往下跟:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
<span
style= "font-size:
15px;" > public final IContentProvider
acquireProvider( Context
c, String auth, int userId, boolean stable)
{<br> final IContentProvider
provider = acquireExistingProvider(c, auth, userId, stable); //刚开始这里很定为空 if (provider
!= null )
{ return provider; } //
There is a possible race here. Another thread may try to acquire //
the same provider at the same time. When this happens, we want to ensure //
that the first one wins. //
Note that we cannot hold the lock while acquiring and installing the //
provider since it might take a long time to run and it could also potentially //
be re-entrant in the case where the provider is in the same process. IActivityManager.ContentProviderHolder
holder = null ; try { holder
= ActivityManagerNative.getDefault().getContentProvider( getApplicationThread(),
auth, userId, stable); //很关键,请注意这里跨进程调到ActivityManagerService.java的方法 } catch (RemoteException
ex) { } if (holder
== null )
{ Slog.e(TAG, "Failed
to find provider info for " +
auth); return null ; } //
Install provider will increment the reference count for us, and break //
any ties in the race. holder
= installProvider(c, holder, holder.info, true /*noisy*/ ,
holder.noReleaseNeeded, stable); return holder.provider; } </span> |
我们继续来看ActivityManagerService.java 的getContentProvider()方法:
@Override
public final ContentProviderHolder getContentProvider(
IApplicationThread caller, String name, int userId, boolean stable) {
enforceNotIsolatedCaller("getContentProvider");
if (caller == null) {
String msg = "null IApplicationThread when getting content provider "
+ name;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
// The incoming user check is now handled in checkContentProviderPermissionLocked() to deal
// with cross-user grant.
return getContentProviderImpl(caller, name, null, stable, userId);
}
肾呐~还没到头!又继续往下走,大家坚持:
//这个方法很长,我们挑选重点的分析,方法第二个参数name就是我们刚刚query传进来的uir的authority
private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
String name, IBinder token, boolean stable, int userId) {
ContentProviderRecord cpr;//AMS中用来记录provider的,还有其他三大组件AcitityRecord、ServiceRecord、BroadcastRecord
ContentProviderConnection conn = null;
ProviderInfo cpi = null;//这个应该是PMS解析应用的AndroidManifest得来的信息
......
// First check if this content provider has been published...
cpr = mProviderMap.getProviderByName(name, userId);
......
如果发现对应的provider 已经运行,这个我们之后再分析,先分析没有运行的情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
<span
style= "font-size:
15px;" > boolean providerRunning
= cpr != null ; if (providerRunning)
{ cpi
= cpr.info; String
msg; checkTime(startTime, "getContentProviderImpl:
before checkContentProviderPermission" ); if ((msg
= checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser)) != null )
{ throw new SecurityException(msg); } checkTime(startTime, "getContentProviderImpl:
after checkContentProviderPermission" ); if (r
!= null &&
cpr.canRunHere(r)) { //
This provider has been published or is in the process //
of being published... but it is also allowed to run //
in the caller‘s process, so don‘t make a connection //
and just let the caller instantiate its own instance. ContentProviderHolder
holder = cpr.newHolder( null ); //
don‘t give caller the provider object, it needs //
to make its own. holder.provider
= null ; return holder; } final long origId
= Binder.clearCallingIdentity(); checkTime(startTime, "getContentProviderImpl:
incProviderCountLocked" ); //
In this case the provider instance already exists, so we can //
return it right away. conn
= incProviderCountLocked(r, cpr, token, stable); if (conn
!= null &&
(conn.stableCount+conn.unstableCount) == 1 )
{ if (cpr.proc
!= null &&
r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { //
If this is a perceptible app accessing the provider, //
make sure to count it as being accessed and thus //
back up on the LRU list. This is good because //
content providers are often expensive to start. checkTime(startTime, "getContentProviderImpl:
before updateLruProcess" ); updateLruProcessLocked(cpr.proc, false , null ); checkTime(startTime, "getContentProviderImpl:
after updateLruProcess" ); } } if (cpr.proc
!= null )
{ if ( false )
{ if (cpr.name.flattenToShortString().equals( "com.android.providers.calendar/.CalendarProvider2" ))
{ Slog.v(TAG, "******************
KILLING " +
cpr.name.flattenToShortString()); Process.killProcess(cpr.proc.pid); } } checkTime(startTime, "getContentProviderImpl:
before updateOomAdj" ); boolean success
= updateOomAdjLocked(cpr.proc); maybeUpdateProviderUsageStatsLocked(r,
cpr.info.packageName, name); checkTime(startTime, "getContentProviderImpl:
after updateOomAdj" ); if (DEBUG_PROVIDER)
Slog.i(TAG_PROVIDER, "Adjust
success: " +
success); //
NOTE: there is still a race here where a signal could be //
pending on the process even though we managed to update its //
adj level. Not sure what to do about this, but at least //
the race is now smaller. if (!success)
{ //
Uh oh... it looks like the provider‘s process //
has been killed on us. We need to wait for a new //
process to be started, and make sure its death //
doesn‘t kill our process. Slog.i(TAG, "Existing
provider " +
cpr.name.flattenToShortString() + "
is crashing; detaching " +
r); boolean lastRef
= decProviderCountLocked(conn, cpr, token, stable); checkTime(startTime, "getContentProviderImpl:
before appDied" ); appDiedLocked(cpr.proc); checkTime(startTime, "getContentProviderImpl:
after appDied" ); if (!lastRef)
{ //
This wasn‘t the last ref our process had on //
the provider... we have now been killed, bail. return null ; } providerRunning
= false ; conn
= null ; } } Binder.restoreCallingIdentity(origId); } </span> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
<span
style= "font-size:
15px;" > if (!providerRunning)
{ try { checkTime(startTime, "getContentProviderImpl:
before resolveContentProvider" ); cpi
= AppGlobals.getPackageManager(). resolveContentProvider(name, STOCK_PM_FLAGS
| PackageManager.GET_URI_PERMISSION_PATTERNS, userId); checkTime(startTime, "getContentProviderImpl:
after resolveContentProvider" ); } catch (RemoteException
ex) { } if (cpi
== null )
{ return null ; } //
If the provider is a singleton AND //
(it‘s a call within the same user || the provider is a //
privileged app) //
Then allow connecting to the singleton provider singleton
= isSingleton(cpi.processName, cpi.applicationInfo, cpi.name,
cpi.flags) &&
isValidSingletonCall(r.uid, cpi.applicationInfo.uid); if (singleton)
{ userId
= UserHandle.USER_OWNER; } cpi.applicationInfo
= getAppInfoForUser(cpi.applicationInfo, userId); checkTime(startTime, "getContentProviderImpl:
got app info for user" ); String
msg; checkTime(startTime, "getContentProviderImpl:
before checkContentProviderPermission" ); if ((msg
= checkContentProviderPermissionLocked(cpi, r, userId, !singleton)) != null )
{ throw new SecurityException(msg); } checkTime(startTime, "getContentProviderImpl:
after checkContentProviderPermission" ); if (!mProcessesReady
&& !mDidUpdate && !mWaitingUpdate &&
!cpi.processName.equals( "system" ))
{ //
If this content provider does not run in the system //
process, and the system is not yet ready to run other //
processes, then fail fast instead of hanging. throw new IllegalArgumentException( "Attempt
to launch content provider before system ready" ); } //
Make sure that the user who owns this provider is running. If not, //
we don‘t want to allow it to run. if (!isUserRunningLocked(userId, false ))
{ Slog.w(TAG, "Unable
to launch app " +
cpi.applicationInfo.packageName + "/" +
cpi.applicationInfo.uid + "
for provider " +
name + ":
user " +
userId + "
is stopped" ); return null ; } ComponentName
comp = new ComponentName(cpi.packageName,
cpi.name); checkTime(startTime, "getContentProviderImpl:
before getProviderByClass" ); cpr
= mProviderMap.getProviderByClass(comp, userId); checkTime(startTime, "getContentProviderImpl:
after getProviderByClass" ); final boolean firstClass
= cpr == null ; if (firstClass)
{ final long ident
= Binder.clearCallingIdentity(); try { checkTime(startTime, "getContentProviderImpl:
before getApplicationInfo" ); ApplicationInfo
ai = AppGlobals.getPackageManager(). getApplicationInfo( cpi.applicationInfo.packageName, STOCK_PM_FLAGS,
userId); checkTime(startTime, "getContentProviderImpl:
after getApplicationInfo" ); if (ai
== null )
{ Slog.w(TAG, "No
package info for content provider " +
cpi.name); return null ; } ai
= getAppInfoForUser(ai, userId); <span
style= "color:
#ff0000;" >
<strong>cpr = new ContentProviderRecord( this ,
cpi, ai, comp, singleton); //很关键的,首先创建cpr,然后再启动目标进程<br><br><br></strong></span></span> |
客户端(应用B)获得cotentprovider 客户端的代理ContentProviderProxy对象,然后调用它的query方法,切记此时还是运行在自己进程中!
1
2
3
4
5
6
7
8
9
10
11
|
<span
style= "font-size:
15px;" > final class ContentProviderProxy implements IContentProvider { public ContentProviderProxy(IBinder
remote) { mRemote
= remote; } public IBinder
asBinder() { return mRemote; }</span> |
1
2
3
4
5
6
7
|
<span
style= "font-size:
15px;" > public Cursor
query(String callingPkg, Uri url, String[] projection, String selection, String[]
selectionArgs, String sortOrder, ICancellationSignal cancellationSignal) ......<br>
data.writeString(sortOrder);
data.writeStrongBinder(adaptor.getObserver().asBinder());
data.writeStrongBinder(cancellationSignal != null ?
cancellationSignal.asBinder() : null );
mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0 ); ......</span><br> |
经过query方法,终于走到了应用A进程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<span
style= "font-size:
15px;" > @Override public boolean onTransact( int code,
Parcel data, Parcel reply, int flags) throws RemoteException
{ try { switch (code)
{ case QUERY_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); String
callingPkg = data.readString(); Uri
url = Uri.CREATOR.createFromParcel(data); ...... String
sortOrder = data.readString(); IContentObserver
observer = IContentObserver.Stub.asInterface( data.readStrongBinder()); ICancellationSignal
cancellationSignal = ICancellationSignal.Stub.asInterface( data.readStrongBinder()); Cursor
cursor = query(callingPkg, url, projection, selection, selectionArgs, sortOrder,
cancellationSignal); //经过这个query方法的转换,最终会走到我们自己定义Contentrovider的query方法<br></span> |
1
2
3
4
5
6
7
8
9
10
|
<span
style= "font-size:
15px;" > try { adaptor
= new CursorToBulkCursorAdaptor(cursor,
observer, getProviderName()); cursor
= null ; BulkCursorDescriptor
d = adaptor.getBulkCursorDescriptor(); adaptor
= null ; reply.writeNoException(); reply.writeInt( 1 );</span> |
cursor从进程A传到进程B,BulkCursorDescriptor肯定是Parcelable类型
大体流程是这样,难点是中间的细节,特别是获得provider这个过程非常繁琐,设计到很多数据结构和其他模块的内容,这块需要慢慢啃。
一.Android四大组件
Android四大组件是Activity, Service, Content Provider, Broadcast Receiver。
Activity作为程序界面,直接与用户交互
Service运行在后台,没有界面,完成特定的功能
ContentProvider维护应用数据,方便应用本身或其它应用访问
Broadcast Receiver提供异步广播消息接收机制,便于各应用/组件进行交互
通过AndroidManifest.xml, 可以看到一个应用使用了哪些组件:
attribute的定义可以参考http://developer.android.com/guide/topics/manifest/manifest-intro.html
下面重点探讨Content Provider的实现和使用。
二. 什么是ContentProvider
Content Provider维护特定的应用数据,并可以让其它应用轻松访问该数据。对数据使
用者来说它是数据提供者。它提供统一的接口对数据进行操作,使用者不用关心数据到底是如何存储的以及数据类型到底是什么。也就是说,Content Provider作为数据提供者,提供了对外共享本地数据一种机制,使Android应用能方便地基于该机制进行数据访问。
为了便于管理和访问,每个Content Provider必须有唯一标示,用Uri表示。Uri类似http url, 构成如下:
content://authority/path
所有Content Provider的Uri必须以content://开头,这是Android规定的。
authority是个字符串,它由开发者自己定义,用于来唯一标示一个ContentProvider。系统会根据这个标示查找ContentProvider。
path也是字符串,表示要操作的数据。可根据自己的实现逻辑来指定:
content://contacts/people表示要操作ContentProvider为contacts下的people表
content://com.android.contacts/people/#表示要操作表people中特定id的行(记录)。
content://downloads/download/10/name表示要操作id为10的行的name字段。
content://downloads/download/*表示操作download表中的所有字段。
总之,#匹配一个数字字符串,*匹配一个文本字符串。
三.ContentProvider 的实现和使用
可以看出 , 实现一个自定义的Content Provider,要基于系统提供的基类ContentProvider,需要实现6个接口。大部分接口就是类似数据库的数据操作接口,实际上Content Provider是需要创建数据库并对数据库进行操作的。完成实现之后,在Androidmanifest.xml中声明自己的Content Provider以及与Provider相关的permission声明(可以没有permission定义)。例如:
最后整个应用被编译成apk。安装之后,该应用里的contentProvider就可以被其它应用访问了。对于Provider使用者来说, 如果特定Provider有permission要求,则要在自己的Androidmanifest.xml中添加指定Permission引用, 如:
使用非常简单,Android提供了Context级别的ContentResolver对象来对Content Provider进行操作。正是因为有了ContentResolver, 使用者才不用关心Provider到底是哪个应用或哪个类实现的。只要知道它的uri就能访问。ContentResolver对象存在于每个Context中。几乎所有对象都有自己的Context。
有些情况下,Content Provider使用者想监听数据的变化,可以注册一个Observer:
四. ContentProvider内部机制
1.ContentProvider接口调用过程
ContentProvider依赖ContentResolver/ActivityThread/ActivityManagerService对外提供
服务。虽然ContentProvider的用法以及表现形式不是一个Service,实际上它可以看作是ActivityManagerService提供的一种服务, 它实现了IBinder接口。
首先调用者通过特定uri调用特定ContentProvider的接口函数,比如insert(), 此时ContentResolver会通过uri获取特定ContentProvider的实例,ActivityThread检查本地Cache,如果发现此ContentProvider已经被引用过,则直接直接取出ContentProvider返回给调用者。如果没有发现,由于 ContentProvider可能已经被load了,可能还没有load;可能要创建Process,可能要检查permission,所以ActivityThread调用到ActivityManagerService来进行相关处理/检查。如果该Provider是Single Process,ActivityManagerService会为ContentProvider创建一个独立Process;如果是MultiProcess,说明每个调用者可以拥有独立的ContentProvider实例,于是ActivityManagerService只是返回ContentProvider的相关信息给ActivityThread,由ActivityThread负责ContentProvider的实例化,此时ContentProvider运行在调用者Process中。实例化后,IConentProvider会返回给调用者,通过该接口可以调用所需功能。
ActivityThread本地维护一个mProviderMap <ProviderName, ProviderRecord >,记录已被引用的ContentProvider, 同时使用引用计数mProviderRefCountMap <IBinder, ProviderRefCount>记录特定ContentProvider的引用情况。
2.ContentProvider实例创建过程
ContentProvider实例的创建与multiprocess属性有关系(Androidmanifest.xml里指定),个人认为理解成多进程并不准确。应该理解为ContentProvider的多实例,不会存在多个ContentProvider进程的情况,ContentProvider 可能存在多个实例。
1) 对于android:multiprocess=true的ContentProvider,意味着可以多实例,那么由调用者在自己的进程空间实例化一个ContentProvider对象,此时定义ContentProvider的App可能并没有启动
注意:ContentProvider是否多实例,还得看contentProvider的uid与调用者的uid是否相同或contentProvider的uid是System user。具体逻辑是:
public boolean canRunHere(ProcessRecord app) {
return (info.multiprocess || info.processName.equals(app.processName))
&& (uid == Process.SYSTEM_UID || uid == app.info.uid);
}
2)对于android:multiprocess=false(默认值)的ContentProvider,由系统把定义该ContentProvider的App启动起来(一个独立的Process)并实例化ContentProvider,这种ContentProvider只有一个实例,运行在自己App的Process中。所有调用者共享该ContentProvider实例,调用者与ContentProvider实例位于两个不同的Process
其中Process.start()->zygoteSendArgsAndGetPid()->ZygoteInit.runSelectLoopMode()
-> ZygoteConnection.runOnce() -> Zygote.forkAndSpecialize()->RuntimeInit.zygoteInit()
-> invokeStaticMain()->MethodAndArgsCaller.run()->Method.invokeNative
->ActivityThread.main(),这是通用的APK启动流程。
3)ContentProvider的加载/发布过程
ContentProvider不能单独发布,总是被打包到某个Android应用(apk)里。APK被安装之后,实例化之后每个Android应用都有一个Application实例,每个Activity或Service有一个ApplicationContext实例。
Android应用程序的入口函数是ActivityThread.main(), 该函数不仅创建了ActivityThread实例以及消息循环机构,而且创建了ApplicationThread实例,通过此实例向Activity Manager Service(AMS)提供IApplicationThread接口,AMS正是通过该接口调度和管理Activity。
ActivityThread通过attachApplication()把自己的ApplicationThread实例告知AMS。
AMS根据thread信息更新进程记录(ProcessRecord)并调用thread的bindApplication()进行初始化工作并创建ApplicationContext和Application实例,然后安装package里声明的所有contentProvider。 主要过程如下:
AMS维护了很多信息,其中比较重要的有:
mProcessNames: 包名(processName)和进程信息(ProcessRecord)映射表
mProvidersByName: Provider发布名和Provider信息(ContentProviderRecord)映射表
mProvidersByClass: Provider类名和Provider信息(ContentProviderRecord)映射表
conProviders:属于ProcessRecord信息,特定Process正在使用的ContentProvider及其个数映射表
pubProviders:属于ProcessRecord信息,特定Process已经Published的Provider类名和Provider信息
(ContentProviderRecord)映射表
ActivityThread维护了3个与ContentProvider相关的Map:
mProviderMap: 记录本应用已使用的Provider信息:<Provider发布名, ProviderRecord>
mProviderRefCountMap: 记录本应用已使用的Provider引用计数信息:<IBinder, ProviderRefCount>
mLocalProviders: 记录本地加载的Provider信息:<IBinder, ProviderRecord>
4)ContentProvider通知机制
注意:这个通知机制需要ContentProvider的实现者在实现insert/delete/query/update接口时调用ContentResolver的notifyChange(), 否则没法实现数据变化的通知。
标签:
原文地址:http://blog.csdn.net/macdroid/article/details/51789922