0%

ContentProvider 源码分析

ContentProvider 简介

内容提供程序(Content Provider)是 Android 应用程序四大组件之一,它提供了对应用使用的底层数据的抽象,使其他应用可以通过 ContentResolver 接口访问其数据,而无需了解数据的具体存储方式。

使用 ContentProvider

实现 ContentProvider 类

ContentProvider 是一个抽象类,开发者需要实现其中一些方法才能使用:

  • onCreate(): 用来初始化内容提供程序提供程序。
  • query(Uri, String[], Bundle, CancellationSignal): 返回数据给调用者。
  • insert(Uri, ContentValues): 插入新数据。
  • update(Uri, ContentValues, String, String[]): 更新现有的数据。
  • delete(Uri, String, String[]): 删除数据。
  • getType(Uri): 返回数据的 MIME 类型。

注意数据访问的方法可能被多个线程同时访问,因此需要做到线程安全。

配置清单文件

仅仅一个自定义类还不够,ContentProvider与activity、service一样,需要在AndroidManifest.xml文件中进行配置。

1
2
3
4
5
6
<provider
android:name=".MyContentProvider"
android:authorities="com.example.content.provider"
android:multiprocess="false"
android:process=":remote"
android:exported="true"/>
  • name 为自定义的 ContentProvider 子类名称
  • authorities 类似 URL 的域名,其他应用使用该值获取访问内容提供程序的 URI,需要保证全局唯一。
  • exported 为 true 则表示允许其他应用访问应用中的 ContentProvider(跨应用访问),默认为 false。
  • process 表示 ContentProvider 所在的进程。
  • multiprocess 为 true 表示每个调用者进程都会创建一个 ContentProvider 实例,默认为 false。

当 multiprocess、process 这两个参数结合起来会产生以下几种情况。

  • android:process=":remote"、android:multiprocess="true",ContentProvider 不会随应用的启动而加载,当调用 ContentProvider 的时候才会加载,并且 ContentProvider 是在调用者的进程中初始化。这时候可能定义 ContentProvider 的":remote"进程还没有启动。
  • android:process=":remote"、android:multiprocess="false"(默认情况) ,ContentProvider 不会随应用的启动而加载,当调用到 ContentProvider 的时候才会加载,并且 ContentProvider 是在“:remote”进程中初始化。
  • android:multiprocess="true",ContentProvider 会随着应用的启动而加载,并且 ContentProvider 是在应用进程的主线程中初始化的。当被调用时会在调用者进程中实例化一个 ContentProvider 对象。
  • android:multiprocess="false"(默认情况),ContentProvider 会随着应用的启动而加载,并且 ContentProvider 是在应用主进程的主线程中初始化的。这种 ContentProvider 只有一个实例,运行在自己 App 的进程中。所有调用者共享该 ContentProvider 实例,调用者与 ContentProvider 实例位于两个不同的进程。

获取 URI

ContentProvider 的使用者需要获取 URI,再使用 ContentResolver 对象进行 CRUD 操作。示例代码如下:

1
2
3
4
5
6
Uri uri_user = Uri.parse("content://com.example.content.provider");
ContentResolver resolver = getContentResolver();
resolver.insert(uri_user, ...);
resolver.query(uri_user,...);
resolver.update(uri_user,...);
resolver.delete(uri_user,...);

URI 类似 URL,由四个部分组成:

  1. URI Scheme,固定为 "content://"
  2. Content Provider 组件的 authorities 属性值;
  3. 资源相对路径;
  4. 资源 ID。

如果这个 ContentProvider 只有一个资源,那么资源相对路径和资源 ID 不是必须的。

源码分析

ContentProvider 类图

读者可以参考这张类图,对 ContentProvider 使用到的类和方法建立一个初步印象。

ContentResolver

getContentResolver

应用开发者使用 Context.getContentResolver() 方法获取 ContentResolver,其实现在 ContextImpl 类中:

1
2
3
4
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}

其中 mContentResolver 是 ApplicationContentResolver 类型的私有变量,该类型实现了 ContentResolver,提供了一系列 ActivityThread mainThread 的代理方法。

ContentResolver 交互过程

ContentResolver 交互过程

下面从 ContentResolver.query 入手,分析 ContentResolver 如何解析并获取对应的 ContentProvider 的。

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
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable Bundle queryArgs,
@Nullable CancellationSignal cancellationSignal) {
Preconditions.checkNotNull(uri, "uri");

try {
if (mWrapped != null) {
return mWrapped.query(uri, projection, queryArgs, cancellationSignal);
}
} catch (RemoteException e) {
return null;
}

IContentProvider unstableProvider = acquireUnstableProvider(uri);
if (unstableProvider == null) {
return null;
}
IContentProvider stableProvider = null;
Cursor qCursor = null;
try {
long startTime = SystemClock.uptimeMillis();

ICancellationSignal remoteCancellationSignal = null;
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
remoteCancellationSignal = unstableProvider.createCancellationSignal();
cancellationSignal.setRemote(remoteCancellationSignal);
}
try {
qCursor = unstableProvider.query(mPackageName, uri, projection,
queryArgs, remoteCancellationSignal);
} catch (DeadObjectException e) {
// The remote process has died... but we only hold an unstable
// reference though, so we might recover!!! Let's try!!!!
// This is exciting!!1!!1!!!!1
unstableProviderDied(unstableProvider);
stableProvider = acquireProvider(uri);
if (stableProvider == null) {
return null;
}
qCursor = stableProvider.query(
mPackageName, uri, projection, queryArgs, remoteCancellationSignal);
}
if (qCursor == null) {
return null;
}

// Force query execution. Might fail and throw a runtime exception here.
qCursor.getCount();
...

// Wrap the cursor object into CursorWrapperInner object.
final IContentProvider provider = (stableProvider != null) ? stableProvider
: acquireProvider(uri);
final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
stableProvider = null;
qCursor = null;
return wrapper;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
return null;
} finally {
... // 释放资源
}
}
  1. 使用 Preconditions 检查 uri 是否为空,若为空抛出 NullPointerException;
  2. 检查是否设定对象成员ContentInterface mWrapped,若设定则直接使用其 query 方法返回值;
  3. 尝试调用 acquireUnstableProvider(Uri) 方法获取 IContentProvider unstableProvider,如果获取失败则直接返回 null;
  4. 如果传入了 cancellationSignal,处理之;
  5. 调用 unstableProvider.query 方法,获取 qCursor
    1. 如果这时远程进程已结束(DeadObjectException),尝试获取 stableProvider:调用 unstableProviderDied 释放相关引用,再尝试用 acquireProvider 方法获取 stableProvider,最后调用其 query 方法获取 qCursor
  6. 如果之前没有获取 stableProvider,获取之;
  7. stableProviderqCursor 包装到 CursorWrapperInner 对象,返回这个对象;
  8. 最后释放资源。

unstableProvider 与 stableProvider 在 ActivityThread 和 AMS 都有各自独立的引用计数,区别在于使用 unstableProvider 的客户端进程不会受到 ContentProvider 服务端进程死亡的牵连,而使用 stableProvider,服务端死亡将导致客户端进程也被杀死。

insert, update, delete 方法直接使用 stableProvider。这一设计的目的是保证客户端与服务端的数据一致性。

获取 ContentProvider 过程

acquireUnstableProvider & acquireProvider

1
2
3
4
5
6
7
8
public final IContentProvider acquireUnstableProvider(Uri uri) {
... // 检查 URI scheme
String auth = uri.getAuthority();
if (auth != null) {
return acquireUnstableProvider(mContext, uri.getAuthority());
}
return null;
}

acquireUnstableProvider(Uri) 方法检查 URI sheme,再调用 acquireUnstableProvider(Context, String) 方法。这个方法的实现在之前介绍的 ApplicationContentResolver 中,实际上是代理了 ActivityThread 中的 acquireProvider 方法。

android.app.ContextImpl

1
2
3
4
5
6
@Override
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
return mMainThread.acquireProvider(c,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), false);
}

acquireProvider(Uri) 方法的情况与 acquireUnstableProvider 的情况相似,唯一要注意的是调用 ActivityThread.acquireProvider 方法时最后一个参数为 true 而非 false

ActivityThread.acquireProvider

android.app.ActivityThread

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
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;
}

// 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.
ContentProviderHolder holder = null;
try {
synchronized (getGetProviderLock(auth, userId)) {
holder = ActivityManager.getService().getContentProvider(
getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
}
} catch (RemoteException ex) {...}
if (holder == null) {...} // 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;
}
  1. 尝试调用 acquireExistingProvider 获取已有的 ContentProvider,如果成功则直接返回;
  2. 获取锁,再调用 AMS 的 getContentProvider 方法获取 ContentProviderHolder 对象;
  3. 使用 installProvider 方法安装内容提供程序;
  4. 返回 ContentProviderHolder 的 provider 成员;

ActivityThread.acquireExistingProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public final IContentProvider acquireExistingProvider(
Context c, String auth, int userId, boolean stable) {
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();
if (!jBinder.isBinderAlive()) {...} // return null

// Only increment the ref count if we have one. If we don't then the
// provider is not reference counted and never needs to be released.
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc != null) {
incProviderRefLocked(prc, stable);
}
return provider;
}
}
  1. authuserId 为 key,从 ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap 中查询已有的记录。
  2. 如果找到记录,获取对应的 Binder 对象,尝试获取并增加引用计数;
  3. 返回 ProviderClientRecord 的 mProvider 成员。

调用 incProviderPrefLocked 方法增加引用计数时会传入 stable 标志,因为需要 AMS 对 stableProvider 和 unstableProvider 做区别处理,所以需要分别计数。ActivityThread 在增加本地计数时,也会通知 AMS 增加那里的引用计数,以使两边计数保持同步。

注意不是所有 provider 都需要引用计数,因为有的系统内容提供程序从不需要被释放,也就完全没有引用计数来管理其生命周期。

ActivityManagerService.getContentProvider

这个方法调用 getContentProviderImpl 方法。

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
private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
String name, IBinder token, int callingUid, String callingPackage, String callingTag,
boolean stable, int userId) {
ContentProviderRecord cpr;
ContentProviderConnection conn = null;
ProviderInfo cpi = null;
boolean providerRunning = false;

synchronized(this) {
...
ProcessRecord r = null;
if (caller != null) {
r = getRecordForAppLocked(caller);
...
}

boolean checkCrossUser = true;
...

// First check if this content provider has been published...
cpr = mProviderMap.getProviderByName(name, userId);
// If that didn't work, check if it exists for user 0 and then
// verify that it's a singleton provider before using it.
if (cpr == null && userId != UserHandle.USER_SYSTEM) {
cpr = mProviderMap.getProviderByName(name, UserHandle.USER_SYSTEM);
if (cpr != null) {
cpi = cpr.info;
if (isSingleton(cpi.processName, cpi.applicationInfo,
cpi.name, cpi.flags)
&& isValidSingletonCall(r.uid, cpi.applicationInfo.uid)) {
userId = UserHandle.USER_SYSTEM;
checkCrossUser = false;
} else {
cpr = null;
cpi = null;
}
}
}

if (cpr != null && cpr.proc != null) {
providerRunning = !cpr.proc.killed;

if (cpr.proc.killed && cpr.proc.killedByAm) {...} // 调用 appDiedLocked() 清理进程
}

首先检查请求的内容提供程序是否已经发布并记录在 ProviderMap mProviderMap 中,注意这里的mProviderMap 是存在于 AMS 中的 ProviderMap 对象,与 ActivityThread 中的 mProviderMap 不是一个东西。如果当前用户中找不到,再从系统用户(UserId = 0)的内容提供程序中寻找合法的单例调用。找到的记录在 ContentProviderRecord cpr 中。

接下来检查这个记录对应的进程是否还在运行。如果是被 ActivityManager 所杀,就主动清理进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// synchronized(this)
if (providerRunning) {
cpi = cpr.info;
String msg;

if (r != null && cpr.canRunHere(r)) {
...... // 权限检查
ContentProviderHolder holder = cpr.newHolder(null);
holder.provider = null;
return holder;
}
...... // 权限检查
// In this case the provider instance already exists, so we can
// return it right away.
conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag,
stable);
if (conn != null && (conn.stableCount+conn.unstableCount) == 1) {
if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
mProcessList.updateLruProcessLocked(cpr.proc, false, null);
}
}
...... // 调整进程 OOM Adj
}

如果内容提供程序对应进程正在运行,这里分两种情况处理:

  1. 这个 ContentProvider 能在调用者的进程中直接运行,就不建立调用者和当前运行进程的连接,而是使调用者启动自己的 ContentProvider 实例(通过设置 holder.provider = null),向调用者返回 ContentProviderHolder。
  2. 不能在调用者进程运行,需要连接到服务端进程,就获取一个 ContentProviderConnection,同时增加提供程序的引用计数。如果这个连接的总引用计数为 1,需要更新 LruProcess 列表。最后还要调整 OOM Adj。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// synchronized(this)
if (!providerRunning) {
try {
cpi = AppGlobals.getPackageManager().
resolveContentProvider(name,
STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
} 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
boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo,
cpi.name, cpi.flags)
&& isValidSingletonCall(r.uid, cpi.applicationInfo.uid);
if (singleton) {
userId = UserHandle.USER_SYSTEM;
}
cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);

...... // 检查权限,处理异常情况

如果对应进程没有在运行,首先利用 authorities 从 PackageManager 获取 ProviderInfo。处理提供者是系统单例提供者的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
cpr = mProviderMap.getProviderByClass(comp, userId);
final boolean firstClass = cpr == null;
if (firstClass) {
... // 要求权限确认,return null

try {
ApplicationInfo ai =
AppGlobals.getPackageManager().
getApplicationInfo(
cpi.applicationInfo.packageName,
STOCK_PM_FLAGS, userId);
if (ai == null) {
return null;
}
ai = getAppInfoForUser(ai, userId);
cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);
} catch (RemoteException ex) {
// pm is in same process, this will never happen.
} finally {...}
}

尝试调用 mProviderMap.getProviderByClass 方法获取 ContentProviderRecord。如果获取失败,说明这个 ContentProvider 是第一次运行。调用 PackageManager.getApplicationInfo 获取应用信息,创建 ContentProviderRecord。

1
2
3
4
5
6
7
if (r != null && cpr.canRunHere(r)) {
// If this is a multiprocess provider, then just return its
// info and allow the caller to instantiate it. Only do
// this if the provider is the same user as the caller's
// process, or can run as root (so can be in any process).
return cpr.newHolder(null);
}

如果内容提供程序是多进程的,只需要返回一个新的 ContentProviderHolder,让调用者去初始化其余内容。

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
// This is single process, and our app is now connecting to it.
// See if we are already in the process of launching this
// provider.
final int N = mLaunchingProviders.size();
int i;
for (i = 0; i < N; i++) {
if (mLaunchingProviders.get(i) == cpr) {
break;
}
}

// If the provider is not already being launched, then get it
// started.
if (i >= N) {
try {
// Content provider is now in use, its package can't be stopped.
try {
AppGlobals.getPackageManager().setPackageStoppedState(
cpr.appInfo.packageName, false, userId);
} catch (RemoteException e) {
} catch (IllegalArgumentException e) {...}

// Use existing process if already started
ProcessRecord proc = getProcessRecordLocked(
cpi.processName, cpr.appInfo.uid, false);
if (proc != null && proc.thread != null && !proc.killed) {
if (!proc.pubProviders.containsKey(cpi.name)) {
proc.pubProviders.put(cpi.name, cpr);
try {
proc.thread.scheduleInstallProvider(cpi);
} catch (RemoteException e) {
}
}
} else {
proc = startProcessLocked(cpi.processName,
cpr.appInfo, false, 0,
new HostingRecord("content provider",
new ComponentName(cpi.applicationInfo.packageName,
cpi.name)), false, false, false);
if (proc == null) {...} // return null
}
cpr.launchingApp = proc;
mLaunchingProviders.add(cpr);
} finally {...}
}

尝试启动内容提供程序。

首先遍历 ArrayList<ContentProviderRecord> mLaunchingProviders,检查之前是否已经安排启动了该内容提供程序。

如果没有找到,尝试启动之:

  1. 调用 PackageManager.setStoppedState 方法,使应用不能被停止;
  2. 尝试获取应用的 ProcessRecord,如果获取成功,调用其 ActivityThread.scheduleInstallProvider 方法;
  3. 如果获取失败,说明应用进程没有启动,调用 startProcessLocked 方法启动该进程
  4. 设置 cprlaunchingApp 变量,将其加入到 mLaunchingProviders 中。
1
2
3
4
5
6
7
8
9
10
11
12
13
    // Make sure the provider is published (the same provider class
// may be published under multiple names).
if (firstClass) {
mProviderMap.putProviderByClass(comp, cpr);
}

mProviderMap.putProviderByName(name, cpr);
conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag,
stable);
if (conn != null) {
conn.waiting = true;
}
}
  1. 如果是首次启动,向 mProviderMap 添加 ComponentName 记录;
  2. mProviderMap 添加 authorities 的记录;
  3. 尝试获取 ContentProviderConnection,设定其 waiting 标志为 true。
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
    ...
// Wait for the provider to be published...
final long timeout = SystemClock.uptimeMillis() + CONTENT_PROVIDER_WAIT_TIMEOUT;
boolean timedOut = false;
synchronized (cpr) {
while (cpr.provider == null) {
if (cpr.launchingApp == null) {... return null;}
try {
final long wait = Math.max(0L, timeout - SystemClock.uptimeMillis());
...
if (conn != null) {
conn.waiting = true;
}
cpr.wait(wait);
if (cpr.provider == null) {
timedOut = true;
break;
}
} catch (InterruptedException ex) {
} finally {
if (conn != null) {
conn.waiting = false;
}
}
}
}
if (timedOut) {... return null;}

return cpr.newHolder(conn);
}

等待内容提供程序启动完成并发布。当前设置的超时时间 CONTENT_PROVIDER_WAIT_TIMEOUT = 20000,单位毫秒,也就是 20 秒。cpr 将等待 cpr.provider 就绪,直到超时时间已过或 publishContentProviders 方法调用 notify 唤醒线程。如果超时时间已过,就返回 null

最后,cpr 以获取的 conn(可能为 null)创建 ContentProviderHolder 返回给调用者。

与 ContentProvider 交互过程

ContentResolver 交互过程

ContentResolver 与 ContentProvider 实现了相同的接口 ContentInterface,但 IPC 通信时调用的是 IContentProvider 这套接口。

ContentResolver 通过 acquireProvider 方法获取的 IContentProvider 接口,得到的实际上是 ContentProviderPorxy 对象。我们来看这个代理如何处理 insert 请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// method of class ContentProviderProxy
public Uri insert(String callingPkg, Uri url, ContentValues values) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);

data.writeString(callingPkg);
url.writeToParcel(data, 0);
values.writeToParcel(data, 0);

mRemote.transact(IContentProvider.INSERT_TRANSACTION, data, reply, 0);

DatabaseUtils.readExceptionFromParcel(reply);
Uri out = Uri.CREATOR.createFromParcel(reply);
return out;
} finally {
data.recycle();
reply.recycle();
}
}

代理对象把数据打包发送到服务端对象处理,在这里是 ContentProviderNative。

1
2
3
4
5
6
7
8
9
10
11
12
13
// onTransact method of abstract class ContentProviderNative
case INSERT_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
ContentValues values = ContentValues.CREATOR.createFromParcel(data);

Uri out = insert(callingPkg, url, values);
reply.writeNoException();
Uri.writeToParcel(reply, out);
return true;
}

收到数据后调用 IContentProvider 接口的 insert 方法,其实现位于 Transport 类。

1
2
3
4
5
6
7
8
// method of class Transport
public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) {
...... // 验证 URI 和权限
try {
return maybeAddUserId(mInterface.insert(uri, initialValues), userId);
} catch (RemoteException e) { ...
} finally {...}
}

在这里调用了 ContentInterface mInterface 接口的 insert 方法。

1
2
3
4
5
6
7
8
// method of abstract class ContentProvider
public final void setTransportLoggingEnabled(boolean enabled) {
if (enabled) {
mTransport.mInterface = new LoggingContentInterface(getClass().getSimpleName(), this);
} else {
mTransport.mInterface = this;
}
}

Transport 类作为 ContentProvider 的成员 mTransport 实例化。ContentProvider 在初始化时,会设置其 mInterface 成员为自身(this)。终于,服务端 ContentProvider 中的各种方法可以被客户端所使用。

ContentProvider 初始化过程

应用的内容提供程序初始化有两个入口。

  1. 应用进程已存在时,由 AMS 远程调用 ActivityThread 进行实例化;
  2. 应用进程启动时,进行批量安装;

AMS 发起的 ContentProvider 安装

AMS 发起 ContentProvider 安装时序图

1
2
3
public void scheduleInstallProvider(ProviderInfo provider) {
sendMessage(H.INSTALL_PROVIDER, provider);
}
1
2
3
4
// handleMessage
case INSTALL_PROVIDER:
handleInstallProvider((ProviderInfo) msg.obj);
break;

AMS 首先调用 ActivityThread.ApplicationThread.scheduleInstallProvider 这个方法向 ActivityThread 的 Handler 发送消息以安排 provider 的安装。Handler 收到消息后启动 handleInstallProvider 方法。这个方法进一步调用 installContentProviders 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void installContentProviders(
Context context, List<ProviderInfo> providers) {
final ArrayList<ContentProviderHolder> results = new ArrayList<>();

for (ProviderInfo cpi : providers) {
...
ContentProviderHolder cph = installProvider(context, null, cpi,
false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
if (cph != null) {
cph.noReleaseNeeded = true;
results.add(cph);
}
}

try {
ActivityManager.getService().publishContentProviders(
getApplicationThread(), results);
} catch (RemoteException ex) {...}
}

这个方法遍历接收到的 ProviderInfo 列表,对每个 ProviderInfo 调用 installProvider 方法。随后,调用 AMS 的 publishContentProviders 方法,发布成功安装的 ContentProvider。

应用进程启动时的 ContentProvider 安装

进程启动时序图

进程启动完成时,会调用 AMS 的 attachApplication 方法,进一步调用 installContentProviders 方法,将清单声明在进程启动时安装的 ContentProvider 批量安装。

ActivityThread.installProvider

这一方法负责安装内容提供程序。根据官方文档,同一进程或来自 system server 的内容提供程序可以永久安装,因此设置 noReleaseNeeded 为 true。其他内容提供程序使用引用计数来管理其生命周期。这个方法还会检测当前内容提供程序是否已经安装。如果已经安装,就增加引用计数,返回已有的内容提供程序。

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
private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
if (holder == null || holder.provider == null) {
...
Context c = null;
ApplicationInfo ai = info.applicationInfo;
if (context.getPackageName().equals(ai.packageName)) {
c = context; // ContentProvider 在主进程
} else if (mInitialApplication != null &&
mInitialApplication.getPackageName().equals(ai.packageName)) {
c = mInitialApplication; // ContentProvider 在其他进程
} else {
try { // 尝试就地创建请求的 Context 对象
c = context.createPackageContext(ai.packageName,
Context.CONTEXT_INCLUDE_CODE);
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
}
if (c == null) {...; return null;}

if (info.splitName != null) {
try {
c = c.createContextForSplit(info.splitName);
} catch (NameNotFoundException e) {...} // throw RuntimeException
}

try {
final java.lang.ClassLoader cl = c.getClassLoader();
LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
if (packageInfo == null) {
// System startup case.
packageInfo = getSystemContext().mPackageInfo;
}
localProvider = packageInfo.getAppFactory()
.instantiateProvider(cl, info.name);
provider = localProvider.getIContentProvider();
if (provider == null) {...; return null;}
// XXX Need to create the correct context for this provider.
localProvider.attachInfo(c, info);
} catch (java.lang.Exception e) {...; return null;}
} else {
provider = holder.provider;
...
}

这一节获取 IContentProvider。如果 holder.provider 不为空,则直接使用。否则:

  1. 获取 ContentProvider 所在的 Context 对象;
  2. 创建 ContentProvider 实例,获取对应的 IContentProvider 接口;
  3. 调用 ContentProvider.attachInfo 方法,该方法调用 onCreate 方法;
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
    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 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 {
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc != null) {
...
// We need to transfer our new reference to the existing
// ref count, releasing the old one... but only if
// release is needed (that is, it is not running in the
// system process).
if (!noReleaseNeeded) {
incProviderRefLocked(prc, stable);
try {
ActivityManager.getService().removeContentProvider(
holder.connection, stable);
} catch (RemoteException e) {
//do nothing content provider object is dead any way
}
}
} 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;
}
}
return retHolder;
}

这一节获取 ContentProviderHolder。

如果 localProvider != null,说明之前走了 holder.provider == null 的分支,获取了本地的 ContentProvider 实例,那么:

  1. 尝试从 mLocalProvidersByName 查询对应的 ProviderClientRecord,如果找到,则直接返回其 mProvider 成员;
  2. 如果没有找到,创建新的 ContentProviderHolder 对象,存入所需信息,调用installProviderAuthoritiesLocked 注册;

否则,说明引用了已有的 ContentProvider:

  1. 尝试从 mProviderRefCountMap 查询对应的 ProviderRefCount,如果找到,更新其引用计数;
  2. 如果没有找到,调用 installProviderAuthoritiesLocked 注册,创建新的引用计数,存入 mProviderRefCountMap
  3. 使用ProviderRefCount 的 holder 成员作为返回的对象。

最后返回获取的 ContentProviderHolder。

installProviderAuthoritiesLocked

创建新的 ProviderClientRecord 对象,将其保存到 ProviderMap mProviderMap 中。

AMS.publishContentProviders

这个方法将收到的 ContentProviderHolder 对应的 ContentProviderRecord 保存到 mProviderMap 中,完成其初始化,并调用 notifyAll 唤醒之前在 getContentProviderImpl 方法等待其就绪的线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public final void publishContentProviders(IApplicationThread caller,
List<ContentProviderHolder> providers) {
if (providers == null) {
return;
}
...
synchronized (this) {
final ProcessRecord r = getRecordForAppLocked(caller);
if (r == null) {throw new SecurityException(...);}
...
final int N = providers.size();
for (int i = 0; i < N; i++) {
ContentProviderHolder src = providers.get(i);
if (src == null || src.info == null || src.provider == null) {
continue;
}
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);
}

首先获取调用者的 ProcessRecord。

接下来遍历 ContentProviderHolder 的列表,对于每一个 ContentProviderHolder:

  1. 获取 ProcessRecord 的 pubProviders 成员中对应的 ContentProviderRecord 到 dst
  2. 保存 dstmProviderMap
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
                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);
}
... // 奇怪的 patch
synchronized (dst) {
dst.provider = src.provider;
dst.setProcess(r);
dst.notifyAll();
}
updateOomAdjLocked(r, true, OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
maybeUpdateProviderUsageStatsLocked(r, src.info.packageName,
src.info.authority);
}
}

Binder.restoreCallingIdentity(origId);
}
}
  1. 检查 dst 是否在 mLaunchingProviders 中,如果是,就从该列表中去除,并按需从 AMS 的 Handler 中拆除发布内容提供程序超时的“定时炸弹”消息,这个“炸弹”是在 attachApplicationLocked 方法中安装的;
  2. 设置 dstproviderproc 属性;
  3. 调用 notifyAll 唤醒之前等待该 ContentProviderRecord 就绪的线程
  4. 调整应用的 OOM Adj。

ContentProvider 服务端被杀牵连客户端的过程

AMS.attachApplicationLocked 方法中,添加了 ActivityThread 对象的死亡回调。

1
2
3
4
5
6
try {
AppDeathRecipient adr = new AppDeathRecipient(
app, pid, thread);
thread.asBinder().linkToDeath(adr, 0);
app.deathRecipient = adr;
} catch (RemoteException e) {...}

这个 AppDeathRecipient 的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
private final class AppDeathRecipient implements IBinder.DeathRecipient {
final ProcessRecord mApp;
final int mPid;
final IApplicationThread mAppThread;
......
@Override
public void binderDied() {
...
synchronized(ActivityManagerService.this) {
appDiedLocked(mApp, mPid, mAppThread, true);
}
}
}

可以看到调用了 appDiedLocked 方法。继续追踪:
appDiedLocked -> handleAppDiedLocked -> cleanUpApplicationRecordLocked

cleanUpApplicationRecordLocked

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
......
// Remove published content providers.
for (int i = app.pubProviders.size() - 1; i >= 0; i--) {
ContentProviderRecord cpr = app.pubProviders.valueAt(i);
final boolean always = app.bad || !allowRestart;
boolean inLaunching = removeDyingProviderLocked(app, cpr, always);
if ((inLaunching || always) && cpr.hasConnectionOrHandle()) {
// We left the provider in the launching list, need to
// restart it.
restart = true;
}

cpr.provider = null;
cpr.setProcess(null);
}
app.pubProviders.clear();

// Take care of any launching providers waiting for this process.
if (cleanupAppInLaunchingProvidersLocked(app, false)) {
restart = true;
}

// Unregister from connected content providers.
if (!app.conProviders.isEmpty()) {
for (int i = app.conProviders.size() - 1; i >= 0; i--) {
ContentProviderConnection conn = app.conProviders.get(i);
conn.provider.connections.remove(conn);
stopAssociationLocked(app.uid, app.processName, conn.provider.uid,
conn.provider.appInfo.longVersionCode, conn.provider.name,
conn.provider.info.processName);
}
app.conProviders.clear();
}
......
  1. 首先移除已经发布的 ContentProvider,如有必要杀掉客户端进程;
  2. 清理正在启动且有客户端正在等待的 ContentProvider;
  3. 从关联的 ContentProviderConnection 中注销;

removeDyingProviderLocked

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
private final boolean removeDyingProviderLocked(ProcessRecord proc,
ContentProviderRecord cpr, boolean always) {
final boolean inLaunching = mLaunchingProviders.contains(cpr);

if (!inLaunching || always) {
synchronized (cpr) {
cpr.launchingApp = null;
cpr.notifyAll();
}
mProviderMap.removeProviderByClass(cpr.name, UserHandle.getUserId(cpr.uid));
String names[] = cpr.info.authority.split(";");
for (int j = 0; j < names.length; j++) {
mProviderMap.removeProviderByName(names[j], UserHandle.getUserId(cpr.uid));
}
}

for (int i = cpr.connections.size() - 1; i >= 0; i--) {
ContentProviderConnection conn = cpr.connections.get(i);
if (conn.waiting) {
// If this connection is waiting for the provider, then we don't
// need to mess with its process unless we are always removing
// or for some reason the provider is not currently launching.
if (inLaunching && !always) {
continue;
}
}
ProcessRecord capp = conn.client;
conn.dead = true;
if (conn.stableCount > 0) {
if (!capp.isPersistent() && capp.thread != null
&& capp.pid != 0
&& capp.pid != MY_PID) {
capp.kill(..., true);
}
} else if (capp.thread != null && conn.provider.provider != null) {
try {
capp.thread.unstableProviderDied(conn.provider.provider.asBinder());
} catch (RemoteException e) {
}
// In the protocol here, we don't expect the client to correctly
// clean up this connection, we'll just remove it.
cpr.connections.remove(i);
if (conn.client.conProviders.remove(conn)) {
stopAssociationLocked(capp.uid, capp.processName, cpr.uid,
cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
}
}
}

if (inLaunching && always) {
mLaunchingProviders.remove(cpr);
}
return inLaunching;
}
  1. 如果这个内容提供程序当前正在运行,调用 notifyAll (TODO),从 mProviderMap 移除记录;
  2. 遍历所有 ContentProviderConnection:
    1. 如果当前连接正在等待提供程序就绪,并且不是必须要移除连接的情况,就跳过处理该连接;
    2. 否则,获取当前连接的客户端程序 ProcessRecord,如果其 stableCount > 0,不是常驻进程,且正在运行,就调用 ProcessRecord.kill 方法杀掉该进程;
    3. 否则(没有 stableConnection),调用 ApplicationThread.unstableProviderDied 方法,再从 ContentProviderRecord 中移除该连接;
  3. 按需移除 mLaunchingProviders 中相应记录,返回 inLaunching 状态给调用者。

Q&A

Q: ContentProviderConnection中stableCount与unstableCount的作用是什么?

A: stableCountunstableCount 分别记录这个 ContentProviderConnection 中使用 stableProviderunstableProvider 的个数。如果 stableCount > 0,服务端进程死亡时,AMS 就会杀掉对应的客户端进程,以确保数据一致性。

参考文献

  1. ContentProvider | Android 开发者, https://developer.android.com/guide/topics/providers/content-providers
  2. Android之ContentProvider源码解析, https://juejin.im/post/5c789ebee51d453f0d4413a2
  3. 理解ContentProvider原理, http://gityuan.com/2016/07/30/content-provider/
  4. ContentProvider流程解析, https://www.jianshu.com/p/f01357e6bb32
  5. 从源码角度看ContentProvider, https://juejin.im/post/5a0c611f6fb9a0450b65ee7c

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
frameworks/base/core/java/android/content/ContentProvider.java
frameworks/base/core/java/android/content/ContentResolver.java
frameworks/base/core/java/android/content/IContentProvider.java

frameworks/base/core/java/android/content/pm/ProviderInfo.java
frameworks/base/core/java/android/content/pm/PackageManager.java

frameworks/base/core/java/android/app/ActivityThread.java
frameworks/base/core/java/android/app/ContentProviderHolder.java

frameworks/base/core/services/com/android/server/am/ActivityManagerService.java
frameworks/base/core/services/com/android/server/am/ProviderMap.java
frameworks/base/core/services/com/android/server/am/ContentProviderRecord.java
frameworks/base/core/services/com/android/server/am/ContentProviderConnection.java
frameworks/base/core/services/com/android/server/am/ProviderMap.java