ContentProvider客戶端處理provider邏輯分析
引言
前面一篇文章分析了 AMS 端處理 provider 的邏輯,請讀者務(wù)必仔細閱讀前面一篇文章,否則看本文,你可能有很多疑惑。
以查詢 provider 為例來分析客戶端是如何處理 provider,它調(diào)用的是 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. 執(zhí)行操作 qCursor = unstableProvider.query(mContext.getAttributionSource(), uri, projection, queryArgs, remoteCancellationSignal); } catch (DeadObjectException e) { // 處理 unstable provider 進程掛掉的情況 // 通知 AMS,provider 進程掛掉了 unstableProviderDied(unstableProvider); // 獲取 stable provider,再次嘗試獲取數(shù)據(jù) 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. 返回數(shù)據(jù) return wrapper; } catch (RemoteException e) { return null; } finally { // ... } }
縱觀整個 provider 的查詢過程,其實就是三步
- 獲取 provider。
- 從獲取到的 provider 執(zhí)行查詢操作。
- 返回查詢的結(jié)果。
我們注意到,代碼中出現(xiàn)了兩種 provider,unstable provider 和 stable provider。這兩者的區(qū)別是,如果 provider 進程掛掉了,對于 stable provider,會殺死客戶端進程,而 unstable 不會。這個我們會在后面分析。
現(xiàn)在我們要抓住重點,來分析如何獲取 provider 。unstable provider 和 stable provider 的獲取方式其實是一樣的,本文只分析獲取 unstbale provider。
1. 獲取 provider
對于 app 進程來說,ContentResolver 接口的實現(xiàn)類為 ApplicationContentResolver,獲取 unstable provider 的操作最終會調(diào)用 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 發(fā)布完成 // holder != null 表示 provider 存在 // holder.provider == null 表示 provider 正在發(fā)布中 // holder.mLocal 為 false,表示 provider 不是安裝在客戶端 if (holder != null && holder.provider == null && !holder.mLocal) { synchronized (key.mLock) { // 2.1 超時等等 provider 發(fā)布 // 超時時間一般為 20s key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS); // 這里可能因為超時被喚醒,獲取的數(shù)據(jù)為空 // 也可以是因為provider發(fā)布完成,被AMS喚醒,holder 為AMS返回的數(shù)據(jù) holder = key.mHolder; } // 2.2 確認(rèn)是否是超時喚醒 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. 成功從服務(wù)端獲取 provider,本地安裝它 holder = installProvider(c, holder, holder.info, true /*noisy*/, holder.noReleaseNeeded, stable); return holder.provider; }
客戶端獲取 provider 的過程大致分為如下幾步
- 從 AMS 獲取 provider。
- 如果 provider 還是發(fā)布的過程中,那么就超時等待它發(fā)布完成。 但是等待是有時間限制的,大約為 20s。超時等待的過程中被喚醒,有兩種可能,一種是因為超時了,另外一種是因為 provider 成功發(fā)布,AMS 喚醒了客戶端。因此需要判斷到底是哪一種情況,檢測的條件是被喚醒后,是否獲取到 provider binder,也就是 holder.provider。詳見【1.1 等待 provider 發(fā)布】
- 從 AMS 成功獲取到 provider 后,那就在本地“安裝”。這個方法的命令起的并不是很好,如果成功從 AMS 獲取到 provider,其實這里的邏輯是保存數(shù)據(jù)。而如果 AMS 通知客戶端,provider 可以安裝在客戶端進程中,客戶端會在這個方法中創(chuàng)建 ContentProvider 對象并保存,這才叫安裝。詳見【1.2 安裝 provider】
1.1 等待 provider 發(fā)布
從前面的文章可知,當(dāng) provider 發(fā)布超時 或者 成功發(fā)布時,都會調(diào)用 ContentProviderRecord#onProviderPublishStatusLocked(boolean status) 來通知客戶端 provider 的發(fā)布狀態(tài)。參數(shù) status 如果為 true,表示發(fā)布成功,如果為 false,表示發(fā)布超時。
// ContentProviderRecord.java void onProviderPublishStatusLocked(boolean status) { final int numOfConns = connections.size(); for (int i = 0; i < numOfConns; i++) { // 遍歷所有等待 provider 發(fā)布的客戶端連接 final ContentProviderConnection conn = connections.get(i); if (conn.waiting && conn.client != null) { final ProcessRecord client = conn.client; // 記錄發(fā)布超時的日志 if (!status) { // 從這里可以看出status為false時,不一定表示發(fā)布超時,還可能因為進程掛掉了 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 發(fā)布的客戶端連接,然后通過客戶端 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) { // 保存服務(wù)端傳過來的數(shù)據(jù) 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 發(fā)布完成 if (holder != null && holder.provider == null && !holder.mLocal) { synchronized (key.mLock) { // 超時等待 key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS); // 這里可能因為超時被喚醒,獲取的數(shù)據(jù)為空 // 也可以是因為provider發(fā)布完成,被AMS喚醒,holder 為AMS返回的數(shù)據(jù) holder = key.mHolder; } // 確認(rèn)是否是超時喚醒 if (holder != null && holder.provider == null) { // probably timed out holder = null; } } } } // ... }
超時等待 provider 發(fā)布時,如果一旦被喚醒,再次獲取 key.mHolder,因為如果成功發(fā)布,holder.provider 是不為空的,因為它就是 provider binder,否則就是超時喚醒。
1.2 安裝 provider
客戶端如果成功從 AMS 獲取到 provider,那么就會安裝它,其實這里的操作是保存數(shù)據(jù),其實最主要的就是保存 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. 創(chuàng)建 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 計數(shù) 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); // ... // 創(chuàng)建一條 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; }
很簡單,就是用兩個數(shù)據(jù)結(jié)構(gòu)保存數(shù)據(jù)。
2. provider 實現(xiàn)多進程實例
前面我們總是隱隱約約地提到,provider 可以安裝在客戶端進程,那么什么樣的條件下,provider 可以安裝在客戶端進程中? 前面一篇文章的分析中有提到過,現(xiàn)在展示出部分代碼
// 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 是否已經(jīng)運行,都有機會在客戶端進程中創(chuàng)建 provider 實例,而這個機會就在 ContentProviderRecord#canRunHere()
provider 已經(jīng)運行,居然還可以運行在客戶端進程中,也就是在客戶端進程中創(chuàng)建 ContentProvider 實例,這樣的設(shè)計又是為了什么呢?
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 的進程名。
現(xiàn)在 provider 怎樣運行在客戶端進程中,大家會玩了嗎?如果會玩了,那么繼續(xù)看下客戶端如何安裝 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. 通過反射創(chuàng)建 ContentProvider 對象 localProvider = packageInfo.getAppFactory() .instantiateProvider(cl, info.name); // 獲取 provider 接口,其實就是獲取 provider binder provider = localProvider.getIContentProvider(); if (provider == null) { return null; } // 2. 為 ContentProvider 對象保存 provider 信息,并且調(diào)用 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 { // 本地創(chuàng)建 ContentProviderHolder holder = new ContentProviderHolder(info); // 保存 provider binder holder.provider = provider; // 本地安裝的 provider,不需要釋放 holder.noReleaseNeeded = true; // 3. 創(chuàng)建 provider 記錄,并保存 pr = installProviderAuthoritiesLocked(provider, localProvider, holder); mLocalProviders.put(jBinder, pr); mLocalProvidersByName.put(cname, pr); } retHolder = pr.mHolder; } else { // ... } } return retHolder; }
其實這一部分代碼在前面文章中已經(jīng)分析過,這里簡單介紹下過程
- 客戶端自己創(chuàng)建 ContentProvider 對象,然后保存 provider 信息,并調(diào)用 ContentProvider#onCreate() 方法。
- 創(chuàng)建 provider 記錄,也就是 ContentProviderRecord 對象,然后用數(shù)據(jù)結(jié)構(gòu)保存。
3. 兩種 provider 區(qū)別
前面我們提到過 unstable provider 和 stable provider 的區(qū)別,現(xiàn)在我們用代碼來解釋下這兩者的區(qū)別
假設(shè)我們通過 ActivityManager#forceStopPackage() 來殺掉 provider 進程,在 AMS 的調(diào)用如下
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); // 發(fā)送廣播 finishForceStopPackageLocked(packageName, pkgUid); } } } } finally { Binder.restoreCallingIdentity(callingId); } }
最終調(diào)用如下代碼
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 { // 通知客戶端移除數(shù)據(jù) 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 進程掛掉了,客戶端只是移除了保存了的數(shù)據(jù)而已,并不會被殺掉。
最后,我們再來看看文章開頭獲取 provider 時關(guān)于兩種 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; // 返回數(shù)據(jù) return wrapper; } catch (RemoteException e) { return null; } finally { // ... } }
我們可以看到,查詢的時候使用的是 unstable provier,但是返回的結(jié)果 Curosr 使用的是 stable provider。這說明了什么? 它說明了,在 Cursor 沒有被 close 之前,只要 provider 進程掛掉了,那么客戶端也會受牽連,會被殺掉。
結(jié)束
以上就是ContentProvider客戶端處理provider邏輯分析的詳細內(nèi)容,更多關(guān)于ContentProvider客戶端provider的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android?MaterialButton使用實例詳解(告別shape、selector)
我們平時寫布局,當(dāng)遇到按鈕需要圓角、或者描邊等,通常的方法是新建一個xml文件,在shape標(biāo)簽下寫,然后通過android:background或setBackground(drawable)設(shè)置,這篇文章主要給大家介紹了關(guān)于Android?MaterialButton使用詳解的相關(guān)資料,需要的朋友可以參考下2022-09-09Android系統(tǒng)自帶樣式 (android:theme)
Android系統(tǒng)中自帶樣式分享,需要的朋友可以參考下2013-01-01Android 自定義view模板并實現(xiàn)點擊事件的回調(diào)
這篇文章主要介紹了Android 自定義view模板并實現(xiàn)點擊事件的回調(diào)的相關(guān)資料,需要的朋友可以參考下2017-01-01