ContentProvider客户端处理provider逻辑分析
引言
前面一篇文章分析了 AMS 端处理 provider 的逻辑,请读者务必仔细阅读前面一篇文章,否则看本文,你可能有很多疑惑。
以查询 provider 为例来分析客户端是如何处理 provider,它调用的是 ContentResolver#query()
// ContentResolver.java public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri, @Nullable String[] projection, @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) { Objects.requireNonNull(uri, "uri"); // ApplicationContentResolver 的 mWrapped 为 null try { if (mWrapped != null) { return mWrapped.query(uri, projection, queryArgs, cancellationSignal); } } catch (RemoteException e) { return null; } // 1. 获取 unstable provider 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 { // 2. 执行操作 qCursor = unstableProvider.query(mContext.getAttributionSource(), uri, projection, queryArgs, remoteCancellationSignal); } catch (DeadObjectException e) { // 处理 unstable provider 进程挂掉的情况 // 通知 AMS,provider 进程挂掉了 unstableProviderDied(unstableProvider); // 获取 stable provider,再次尝试获取数据 stableProvider = acquireProvider(uri); if (stableProvider == null) { return null; } qCursor = stableProvider.query(mContext.getAttributionSource(), uri, projection, queryArgs, remoteCancellationSignal); } if (qCursor == null) { return null; } // Force query execution. Might fail and throw a runtime exception here. qCursor.getCount(); long durationMillis = SystemClock.uptimeMillis() - startTime; maybeLogQueryToEventLog(durationMillis, uri, projection, queryArgs); // 注意,这里最终还是从 stable provider 获取 provider 接口 final IContentProvider provider = (stableProvider != null) ? stableProvider : acquireProvider(uri); final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider); stableProvider = null; qCursor = null; // 3. 返回数据 return wrapper; } catch (RemoteException e) { return null; } finally { // ... } }
纵观整个 provider 的查询过程,其实就是三步
- 获取 provider。
- 从获取到的 provider 执行查询操作。
- 返回查询的结果。
我们注意到,代码中出现了两种 provider,unstable provider 和 stable provider。这两者的区别是,如果 provider 进程挂掉了,对于 stable provider,会杀死客户端进程,而 unstable 不会。这个我们会在后面分析。
现在我们要抓住重点,来分析如何获取 provider 。unstable provider 和 stable provider 的获取方式其实是一样的,本文只分析获取 unstbale provider。
1. 获取 provider
对于 app 进程来说,ContentResolver 接口的实现类为 ApplicationContentResolver,获取 unstable provider 的操作最终会调用 ApplicationContentResolver#acquireUnstableProvider()
//ContextImpl.java class ContextImpl { private static final class ApplicationContentResolver extends ContentResolver { private final ActivityThread mMainThread; @Override protected IContentProvider acquireUnstableProvider(Context c, String auth) { return mMainThread.acquireProvider(c, ContentProvider.getAuthorityWithoutUserId(auth), resolveUserIdFromAuthority(auth), false); } } }
原来最终是交给 ActivityThread 来获取 provider
// ActivityThread.java 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; } ContentProviderHolder holder = null; // 合成一个 KEY final ProviderKey key = getGetProviderKey(auth, userId); try { synchronized (key) { // 1. 获取 ActivityManagerService 获取 holder = ActivityManager.getService().getContentProvider( getApplicationThread(), c.getOpPackageName(), auth, userId, stable); // 2. 等待 provider 发布完成 // holder != null 表示 provider 存在 // holder.provider == null 表示 provider 正在发布中 // holder.mLocal 为 false,表示 provider 不是安装在客户端 if (holder != null && holder.provider == null && !holder.mLocal) { synchronized (key.mLock) { // 2.1 超时等等 provider 发布 // 超时时间一般为 20s key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS); // 这里可能因为超时被唤醒,获取的数据为空 // 也可以是因为provider发布完成,被AMS唤醒,holder 为AMS返回的数据 holder = key.mHolder; } // 2.2 确认是否是超时唤醒 if (holder != null && holder.provider == null) { // probably timed out holder = null; } } } } // ... // 这里记录了获取provider失败的日志 if (holder == null) { if (UserManager.get(c).isUserUnlocked(userId)) { Slog.e(TAG, "Failed to find provider info for " + auth); } else { Slog.w(TAG, "Failed to find provider info for " + auth + " (user not unlocked)"); } return null; } // 3. 成功从服务端获取 provider,本地安装它 holder = installProvider(c, holder, holder.info, true /*noisy*/, holder.noReleaseNeeded, stable); return holder.provider; }
客户端获取 provider 的过程大致分为如下几步
- 从 AMS 获取 provider。
- 如果 provider 还是发布的过程中,那么就超时等待它发布完成。 但是等待是有时间限制的,大约为 20s。超时等待的过程中被唤醒,有两种可能,一种是因为超时了,另外一种是因为 provider 成功发布,AMS 唤醒了客户端。因此需要判断到底是哪一种情况,检测的条件是被唤醒后,是否获取到 provider binder,也就是 holder.provider。详见【1.1 等待 provider 发布】
- 从 AMS 成功获取到 provider 后,那就在本地“安装”。这个方法的命令起的并不是很好,如果成功从 AMS 获取到 provider,其实这里的逻辑是保存数据。而如果 AMS 通知客户端,provider 可以安装在客户端进程中,客户端会在这个方法中创建 ContentProvider 对象并保存,这才叫安装。详见【1.2 安装 provider】
1.1 等待 provider 发布
从前面的文章可知,当 provider 发布超时 或者 成功发布时,都会调用 ContentProviderRecord#onProviderPublishStatusLocked(boolean status) 来通知客户端 provider 的发布状态。参数 status 如果为 true,表示发布成功,如果为 false,表示发布超时。
// ContentProviderRecord.java void onProviderPublishStatusLocked(boolean status) { final int numOfConns = connections.size(); for (int i = 0; i < numOfConns; i++) { // 遍历所有等待 provider 发布的客户端连接 final ContentProviderConnection conn = connections.get(i); if (conn.waiting && conn.client != null) { final ProcessRecord client = conn.client; // 记录发布超时的日志 if (!status) { // 从这里可以看出status为false时,不一定表示发布超时,还可能因为进程挂掉了 if (launchingApp == null) { Slog.w(TAG_AM, "Unable to launch app " + appInfo.packageName + "/" + appInfo.uid + " for provider " + info.authority + ": launching app became null"); EventLogTags.writeAmProviderLostProcess( UserHandle.getUserId(appInfo.uid), appInfo.packageName, appInfo.uid, info.authority); } else { Slog.wtf(TAG_AM, "Timeout waiting for provider " + appInfo.packageName + "/" + appInfo.uid + " for provider " + info.authority + " caller=" + client); } } // 通知客户端 final IApplicationThread thread = client.getThread(); if (thread != null) { try { thread.notifyContentProviderPublishStatus( newHolder(status ? conn : null, false), info.authority, conn.mExpectedUserId, status); } catch (RemoteException e) { } } } conn.waiting = false; } }
很简单,通过遍历所有等待 provider 发布的客户端连接,然后通过客户端 attach 的 thread 来通知它们。
// ActivityThread.java public void notifyContentProviderPublishStatus(@NonNull ContentProviderHolder holder, @NonNull String authorities, int userId, boolean published) { final String auths[] = authorities.split(";"); for (String auth: auths) { final ProviderKey key = getGetProviderKey(auth, userId); synchronized (key.mLock) { // 保存服务端传过来的数据 key.mHolder = holder; // 唤醒等待provider的线程 key.mLock.notifyAll(); } } }
客户端收到信息后,唤醒了等待的线程,谁在等待呢?这里是不是有点熟悉,其实就是前面分析获取 provider 时,超时等待,部分代码如下
// ActivityThread.java public final IContentProvider acquireProvider( Context c, String auth, int userId, boolean stable) { // ... try { synchronized (key) { holder = ActivityManager.getService().getContentProvider( getApplicationThread(), c.getOpPackageName(), auth, userId, stable); // 等待 provider 发布完成 if (holder != null && holder.provider == null && !holder.mLocal) { synchronized (key.mLock) { // 超时等待 key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS); // 这里可能因为超时被唤醒,获取的数据为空 // 也可以是因为provider发布完成,被AMS唤醒,holder 为AMS返回的数据 holder = key.mHolder; } // 确认是否是超时唤醒 if (holder != null && holder.provider == null) { // probably timed out holder = null; } } } } // ... }
超时等待 provider 发布时,如果一旦被唤醒,再次获取 key.mHolder,因为如果成功发布,holder.provider 是不为空的,因为它就是 provider binder,否则就是超时唤醒。
1.2 安装 provider
客户端如果成功从 AMS 获取到 provider,那么就会安装它,其实这里的操作是保存数据,其实最主要的就是保存 provider 接口,同时也是保存 provider binder.
private ContentProviderHolder installProvider(Context context, ContentProviderHolder holder, ProviderInfo info, boolean noisy, boolean noReleaseNeeded, boolean stable) { ContentProvider localProvider = null; IContentProvider provider; // 成功从 AMS 获取 provider,下面两个条件都是不成立 if (holder == null || holder.provider == null) { // ... } else { // 获取 provider 接口,其实就是获取 provider binder provider = holder.provider; } ContentProviderHolder retHolder; synchronized (mProviderMap) { // 从 provider 接口中获取 binder 对象 IBinder jBinder = provider.asBinder(); if (localProvider != null) { // ... } else { ProviderRefCount prc = mProviderRefCountMap.get(jBinder); if (prc != null) { // ... } else { // 1. 创建 provider 记录,并保存 ProviderClientRecord client = installProviderAuthoritiesLocked( provider, localProvider, holder); // persistent app 的 provider 是不需要释放的 if (noReleaseNeeded) { prc = new ProviderRefCount(holder, client, 1000, 1000); } else { prc = stable ? new ProviderRefCount(holder, client, 1, 0) : new ProviderRefCount(holder, client, 0, 1); } // 2. 保存 provider 计数 mProviderRefCountMap.put(jBinder, prc); } retHolder = prc.holder; } } return retHolder; } private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider, ContentProvider localProvider, ContentProviderHolder holder) { final String auths[] = holder.info.authority.split(";"); final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid); // ... // 创建一条 provider 记录 final ProviderClientRecord pcr = new ProviderClientRecord( auths, provider, localProvider, holder); // 一个 ContentProvider 可以声明多个 authority for (String auth : auths) { final ProviderKey key = new ProviderKey(auth, userId); // mProviderMap 保存 final ProviderClientRecord existing = mProviderMap.get(key); if (existing != null) { Slog.w(TAG, "Content provider " + pcr.mHolder.info.name + " already published as " + auth); } else { mProviderMap.put(key, pcr); } } return pcr; }
很简单,就是用两个数据结构保存数据。
2. 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 对象,然后用数据结构保存。
3. 两种 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 进程挂掉了,那么客户端也会受牵连,会被杀掉。
结束
以上就是ContentProvider客户端处理provider逻辑分析的详细内容,更多关于ContentProvider客户端provider的资料请关注脚本之家其它相关文章!
相关文章
Android MaterialButton使用实例详解(告别shape、selector)
我们平时写布局,当遇到按钮需要圆角、或者描边等,通常的方法是新建一个xml文件,在shape标签下写,然后通过android:background或setBackground(drawable)设置,这篇文章主要给大家介绍了关于Android MaterialButton使用详解的相关资料,需要的朋友可以参考下2022-09-09
最新评论