provider 实现多进程实例
前面我们总是隐隐约约地提到,provider 可以安装在客户端进程,那么什么样的条件下,provider 可以安装在客户端进程中? 前面一篇文章的分析中有提到过,现在展示出部分代码
// ContentProviderHelper.java
private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
String name, IBinder token, int callingUid, String callingPackage, String callingTag,
boolean stable, int userId) {
// ...
synchronized (mService) {
// 获取客户端的进程实例
ProcessRecord r = null;
if (caller != null) {
r = mService.getRecordForAppLOSP(caller);
if (r == null) {
throw new SecurityException("Unable to find app for caller " + caller
+ " (pid=" + Binder.getCallingPid() + ") when getting content provider "
+ name);
}
}
// ...
// provider 正在运行
if (providerRunning) {
cpi = cpr.info;
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, true);
// don't give caller the provider object, it needs to make its own.
holder.provider = null;
return holder;
}
// ...
}
// provider 没有运行
if (!providerRunning) {
// ...
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, true);
}
// ...
}
// ...
}
// ...
}
可以看到,无论 provider 是否已经运行,都有机会在客户端进程中创建 provider 实例,而这个机会就在 ContentProviderRecord#canRunHere()
provider 已经运行,居然还可以运行在客户端进程中,也就是在客户端进程中创建 ContentProvider 实例,这样的设计又是为了什么呢?
public boolean canRunHere(ProcessRecord app) {
// info 为 provider 信息,也就是在 AndroidManifest 中声明的 provider 信息
// provider 可以 运行在客户端进程中的条件
// 1. provider 所在的 app 的 uid 与客户端 app 的 uid 相同
// 2. provider 支持多进程 或者 provider 的进程名与客户端 app 的进程名相同
return (info.multiprocess || info.processName.equals(app.processName))
&& uid == app.info.uid;
}
这里的条件可要看清楚了,首先 provider 所在 app 和 客户端 app 的 uid 相同,其实就是下面这个玩意要一样
<manifest
android:sharedUserId="">
然后,还需要 provider 支持多进程,其实就是下面这个玩意
<provider
android:multiprocess="true"/>
如果 provider 不支持多进程,只要 provider 的进程名与客户端 app 的进程名一样,provider 也是可以运行在客户端进程中。那么 provider 进程名是什么呢? provider 可以声明自己的进程名,如下
<provider
android:process=""
/>
而如果 provider 没有声明自己的进程名,那么 provider 进程名取自 app 的进程名。
现在 provider 怎样运行在客户端进程中,大家会玩了吗?如果会玩了,那么继续看下客户端如何安装 provider,这一次可就是真的安装 provider 了
private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
// 此时 holder.provider == null 是成立的
if (holder == null || holder.provider == null) {
// ...
try {
final java.lang.ClassLoader cl = c.getClassLoader();
LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
if (packageInfo == null) {
// System startup case.
packageInfo = getSystemContext().mPackageInfo;
}
// 1. 通过反射创建 ContentProvider 对象
localProvider = packageInfo.getAppFactory()
.instantiateProvider(cl, info.name);
// 获取 provider 接口,其实就是获取 provider binder
provider = localProvider.getIContentProvider();
if (provider == null) {
return null;
}
// 2. 为 ContentProvider 对象保存 provider 信息,并且调用 ContentProvider#onCreate()
localProvider.attachInfo(c, info);
} catch (java.lang.Exception e) {
// ...
}
} else {
// ...
}
ContentProviderHolder retHolder;
synchronized (mProviderMap) {
if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider
+ " / " + info.name);
IBinder jBinder = provider.asBinder();
if (localProvider != null) {
ComponentName cname = new ComponentName(info.packageName, info.name);
ProviderClientRecord pr = mLocalProvidersByName.get(cname);
if (pr != null) {
// ...
} else {
// 本地创建 ContentProviderHolder
holder = new ContentProviderHolder(info);
// 保存 provider binder
holder.provider = provider;
// 本地安装的 provider,不需要释放
holder.noReleaseNeeded = true;
// 3. 创建 provider 记录,并保存
pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
mLocalProviders.put(jBinder, pr);
mLocalProvidersByName.put(cname, pr);
}
retHolder = pr.mHolder;
} else {
// ...
}
}
return retHolder;
}
其实这一部分代码在前面文章中已经分析过,这里简单介绍下过程
- 客户端自己创建 ContentProvider 对象,然后保存 provider 信息,并调用 ContentProvider#onCreate() 方法。
- 创建 provider 记录,也就是 ContentProviderRecord 对象,然后用数据结构保存。
unstable provier 和 stable provider 区别
前面我们提到过 unstable provider 和 stable provider 的区别,现在我们用代码来解释下这两者的区别
假设我们通过 ActivityManager#forceStopPackage() 来杀掉 provider 进程,在 AMS 的调用如下
public void forceStopPackage(final String packageName, int userId) {
// ...
try {
IPackageManager pm = AppGlobals.getPackageManager();
synchronized(this) {
int[] users = userId == UserHandle.USER_ALL
? mUserController.getUsers() : new int[] { userId };
for (int user : users) {
// ...
if (mUserController.isUserRunning(user, 0)) {
// 杀掉进程
forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
// 发送广播
finishForceStopPackageLocked(packageName, pkgUid);
}
}
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
最终调用如下代码
final boolean forceStopPackageLocked(String packageName, int appId,
boolean callerWillRestart, boolean purgeCache, boolean doit,
boolean evenPersistent, boolean uninstalling, int userId, String reason) {
// ...
// 获取 app 的所有 provider
ArrayList<ContentProviderRecord> providers = new ArrayList<>();
if (mCpHelper.getProviderMap().collectPackageProvidersLocked(packageName, null, doit,
evenPersistent, userId, providers)) {
if (!doit) {
return true;
}
didSomething = true;
}
// 移除 provider
for (i = providers.size() - 1; i >= 0; i--) {
mCpHelper.removeDyingProviderLocked(null, providers.get(i), true);
}
// ...
}
不出意外,最终由 ContentProviderHelper 来移除 provider
boolean removeDyingProviderLocked(ProcessRecord proc, ContentProviderRecord cpr,
boolean always) {
// ...
for (int i = cpr.connections.size() - 1; i >= 0; i--) {
ContentProviderConnection conn = cpr.connections.get(i);
// ...
ProcessRecord capp = conn.client;
final IApplicationThread thread = capp.getThread();
conn.dead = true;
// 1. 如有 stable provider 的客户端
if (conn.stableCount() > 0) {
final int pid = capp.getPid();
// 注意,要排除 persistent app 进程,以及 system_server 进程
if (!capp.isPersistent() && thread != null
&& pid != 0 && pid != ActivityManagerService.MY_PID) {
// 杀掉客户端进程
capp.killLocked(
"depends on provider " + cpr.name.flattenToShortString()
+ " in dying proc " + (proc != null ? proc.processName : "??")
+ " (adj " + (proc != null ? proc.mState.getSetAdj() : "??") + ")",
ApplicationExitInfo.REASON_DEPENDENCY_DIED,
ApplicationExitInfo.SUBREASON_UNKNOWN,
true);
}
}
// 2. 如果只有 unstable provider 客户端
else if (thread != null && conn.provider.provider != null) {
try {
// 通知客户端移除数据
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.mProviders.removeProviderConnection(conn)) {
mService.stopAssociationLocked(capp.uid, capp.processName,
cpr.uid, cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
}
}
}
// ...
}
看到了吧,对于 stable provider,如果 provider 进程挂掉了,那么客户端也会受牵连被杀掉。
而对于 unstable provider,如果 provier 进程挂掉了,客户端只是移除了保存了的数据而已,并不会被杀掉。
最后,我们再来看看文章开头获取 provider 时关于两种 provider 代码
// ContentResolver.java
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable Bundle queryArgs,
@Nullable CancellationSignal cancellationSignal) {
// ...
// 获取 unstable provider
IContentProvider unstableProvider = acquireUnstableProvider(uri);
if (unstableProvider == null) {
return null;
}
IContentProvider stableProvider = null;
Cursor qCursor = null;
try {
// ...
// 注意,这里获取的 stable provider 并返回
final IContentProvider provider = (stableProvider != null) ? stableProvider
: acquireProvider(uri);
final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
stableProvider = null;
qCursor = null;
// 返回数据
return wrapper;
} catch (RemoteException e) {
return null;
} finally {
// ...
}
}
我们可以看到,查询的时候使用的是 unstable provier,但是返回的结果 Curosr 使用的是 stable provider。这说明了什么? 它说明了,在 Cursor 没有被 close 之前,只要 provider 进程挂掉了,那么客户端也会受牵连,会被杀掉。
TODO
provider 其实还有很多知识点没有解释,但是碍于时间的关系,我不可能把所有事情都解析出来,后续我会慢慢补充新的东西。