Android ContentProvider 啟動分析
對於 ContentProvider 還不是很熟悉的同學,可以閱讀上一篇 Android ContentProvider 基本原理和使用詳解。本文主要是對 contentProvider 的原始碼進行分析,從而瞭解 ContentProvider 的實現原理。
本文分析基於 android 10 的原始碼, API 級別 29。
ContentProvider 啟動流程
ContentProvider (CP) 啟動流程圖如下:可以對著這個來閱讀下面的內容。
1、ActivityThread.handleBindApplication
對於瞭解 Activity 啟動流程的,可以知道 Application
ActivityThread
的 handleBindApplication
方法中建立。在講解這個方法時疏漏了一點,那就是 ContentProvider
會在這個方法中建立。
// ActivityThread private void handleBindApplication(AppBindData data) { // ...... Application app;try { // If the app is being launched for full backup or restore, bring it up in // a restricted environment with the base application class. app = data.info.makeApplication(data.restrictedBackupMode, null); // Propagate autofill compat state app.setAutofillOptions(data.autofillOptions); // Propagate Content Capture options app.setContentCaptureOptions(data.contentCaptureOptions); mInitialApplication = app; // don't bring up providers in restricted mode; they may depend on the // app's custom Application class if (!data.restrictedBackupMode) { if (!ArrayUtils.isEmpty(data.providers)) { installContentProviders(app, data.providers); } } // Do this after providers, since instrumentation tests generally start their // test thread at this point, and we don't want that racing. try {
// Instrumentation onCreate 方法 mInstrumentation.onCreate(data.instrumentationArgs); } catch (Exception e) { throw new RuntimeException( "Exception thrown in onCreate() of " + data.instrumentationName + ": " + e.toString(), e); } try {
// application 的 onCreate 方法 mInstrumentation.callApplicationOnCreate(app); } catch (Exception e) { if (!mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to create application " + app.getClass().getName() + ": " + e.toString(), e); } } } finally { // If the app targets < O-MR1, or doesn't change the thread policy // during startup, clobber the policy to maintain behavior of b/36951662 if (data.appInfo.targetSdkVersion < Build.VERSION_CODES.O_MR1 || StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) { StrictMode.setThreadPolicy(savedPolicy); } } // ...... }
上面簡化了大量程式碼,但重要部分還在。AppBindData 物件 data 的成員變數 providers 儲存了要在當前應用程式程序中啟動的 CP 元件,接下會呼叫 installContentProviders 方法。
可以看到 installContentProviders
在 Application
的 onCreate
之前呼叫,所以可以得出結論:
ContentProvider
的onCreate
在Application
的onCreate
之前呼叫。
因為 onCreate 是在啟動過程中關掉用的,因此儘量避免在裡面執行耗時的操作,例如與IO相關的操作;否則,就可能造成 Content Provider 元件啟動超時。
ActivityThread.installContentProviders
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 {
// 釋出到 AMS 中 ActivityManager.getService().publishContentProviders( getApplicationThread(), results); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } }
這個方法主要做了兩件事。
-
第一.通過迴圈變數 providerinfo 資訊,呼叫 installProvider 方法將 provider 資訊安裝完成並封裝成了一個 ContentProviderHolder 型別的物件,裡面包含 IContentProvider 介面。
-
第二.呼叫AMS服務的publishContentProviders方法,將這些安裝完成的 Provider 資訊釋出到AMS 服務,以便其他程序訪問。
ContentProviderHolder
它是什麼呢?它其實是一個可以在程序間傳遞的資料物件 (aidl),看一下它的定義:
public class ContentProviderHolder implements Parcelable { public final ProviderInfo info; public IContentProvider provider; public IBinder connection; ...
裡面包含了 CP 的很多資訊,所以 AMS 拿到 ContentProviderHolder (CPH),就等於拿到了所有 CP 的資訊,後面發不到 AMS 就是依賴該物件。
ActivityThread.installProvider
接下來會呼叫 ActivityThread 的 installProvider
方法,如果傳入的 holder 為 null,所以就會在 installProvider
中建立 ContentProvider
例項並加入 HashMap 中進行快取。
那就先來看 installProvider 的處理過程,在來看 AMS 釋出 ContentProvider 的過程。
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; } else if (mInitialApplication != null && mInitialApplication.getPackageName().equals(ai.packageName)) { c = mInitialApplication; } else { try { c = context.createPackageContext(ai.packageName, Context.CONTEXT_INCLUDE_CODE); } catch (PackageManager.NameNotFoundException e) { // Ignore } }
// 上下文為空直接返回 if (c == null) { Slog.w(TAG, "Unable to get context for package " + ai.packageName + " while loading content provider " + info.name); return null; } if (info.splitName != null) { try { c = c.createContextForSplit(info.splitName); } catch (NameNotFoundException e) { throw new RuntimeException(e); } } 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) { Slog.e(TAG, "Failed to instantiate class " + info.name + " from sourceDir " + info.applicationInfo.sourceDir); return null; } if (DEBUG_PROVIDER) Slog.v( TAG, "Instantiating local provider " + info.name); // XXX Need to create the correct context for this provider. 呼叫 attachInfo 方法來進行初始化,可以參看流程圖 localProvider.attachInfo(c, info); } catch (java.lang.Exception e) { if (!mInstrumentation.onException(null, e)) { throw new RuntimeException( "Unable to get provider " + info.name + ": " + e.toString(), e); } return null; } } else { provider = holder.provider; if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": " + info.name); } 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);
// 第一次建立 pr 為空 if (pr != null) { if (DEBUG_PROVIDER) { Slog.v(TAG, "installProvider: lost the race, " + "using existing local provider"); } 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) { if (DEBUG_PROVIDER) { Slog.v(TAG, "installProvider: lost the race, updating ref count"); } // 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; }
在 installContentProviders 方法中呼叫這個方法的時候,holder 引數傳遞的值為 Null,也是因為這些 ContentProvider 是第一次安裝。所以 holder 肯定為 Null。所以此時滿足 if 的條件。在 If 語句中,首先根據條件獲取相應的 Context 上下文資訊。
然後 ClassLoader 載入對應的 ContentProvider 類,並建立該類的物件,然後呼叫 ContentProvider 的 attachInfo 方法。該方法作用是將新建立的 ContentProvider 和 Context,ProviderInfo 關聯起來,最後呼叫該 Provider 的 onCreate 方法啟動 ContentProvider。這個一個 ContentProvider 就建立完成了,下一步就是將它儲存到應用程序的中,以方便查詢和管理。併發布到 AMS 服務中,方便其他程序呼叫。
獲取 ContetProvider 的 IContentProvider(ICP) 賦值給 provider 變數,IContentProvider 是 ContentProvider 客戶端和服務端通訊的介面,getIcontentProvider 理解為得到一個 Binder 型別的物件,用於ContentProvider 客戶端和服務端之間的通訊。
由於是第一次啟動 ContentProvider,所以該資訊還沒有儲存,所以變數 pr 為空,此時根據 ProviderInfo 的資訊和 Binder 型別 IContentProvider 物件,建立一個 ContentProviderHolder 物件,它裡邊封裝了這個 ContentProvider 的 ProviderInfo 和 IContentProvider 資訊。
方法最後返回建立的這個 ContentProviderHolder 的物件。
Transport
Transport 是 ContentProvider 一個內部類,繼承自 ContentProviderNative,是一個 binder, 具有遠端通訊能力。
getIcontentProvider 具體程式碼可以參看下面的程式碼。
// ContentProvider.java /** * Binder object that deals with remoting. 是一個 binder ,可以遠端通訊 */ class Transport extends ContentProviderNative { volatile AppOpsManager mAppOpsManager = null; volatile int mReadOp = AppOpsManager.OP_NONE; volatile int mWriteOp = AppOpsManager.OP_NONE; volatile ContentInterface mInterface = ContentProvider.this; } private Transport mTransport = new Transport(); /** * Returns the Binder object for this provider. * * @return the Binder object for this provider * @hide */ @UnsupportedAppUsage public IContentProvider getIContentProvider() { return mTransport; }
// ContentProviderNative.java
abstract public class ContentProviderNative extends Binder implements IContentProvider {}
IContentProvider 其實就是一個 binder 。
ContentProviderRecord
上面有個 ContentProviderRecord(CPR),
它是系統 (ActivityManagerService) 用來記錄一個 ContentProvider
相關資訊的物件。
final class ContentProviderRecord implements ComponentName.WithComponentName { final ActivityManagerService service; public final ProviderInfo info; final int uid; final ApplicationInfo appInfo; final ComponentName name; final boolean singleton; public IContentProvider provider; public boolean noReleaseNeeded; // All attached clients final ArrayList<ContentProviderConnection> connections = new ArrayList<ContentProviderConnection>(); //final HashSet<ProcessRecord> clients = new HashSet<ProcessRecord>(); // Handles for non-framework processes supported by this provider HashMap<IBinder, ExternalProcessHandle> externalProcessTokenToHandle; // Count for external process for which we have no handles. int externalProcessNoHandleCount; ProcessRecord proc; // if non-null, hosting process. ProcessRecord launchingApp; // if non-null, waiting for this app to be launched. String stringName; String shortStringName;
可以看到 record 裡面記錄了相當多的資訊。
ProviderInfo
用來儲存一個 ContentProvider
的資訊( manifest 中的 <provider>
), 比如 authority
、readPermission
等。
public final class ProviderInfo extends ComponentInfo implements Parcelable { /** The name provider is published under content:// */ public String authority = null; /** Optional permission required for read-only access this content * provider. */ public String readPermission = null; /** Optional permission required for read/write access this content * provider. */ public String writePermission = null; /** If true, additional permissions to specific Uris in this content * provider can be granted, as per the * {@link android.R.styleable#AndroidManifestProvider_grantUriPermissions * grantUriPermissions} attribute. */ public boolean grantUriPermissions = false; /** * If non-null, these are the patterns that are allowed for granting URI * permissions. Any URI that does not match one of these patterns will not * allowed to be granted. If null, all URIs are allowed. The * {@link PackageManager#GET_URI_PERMISSION_PATTERNS * PackageManager.GET_URI_PERMISSION_PATTERNS} flag must be specified for * this field to be filled in. */ public PatternMatcher[] uriPermissionPatterns = null; /** * If non-null, these are path-specific permissions that are allowed for * accessing the provider. Any permissions listed here will allow a * holding client to access the provider, and the provider will check * the URI it provides when making calls against the patterns here. */ public PathPermission[] pathPermissions = null; /** If true, this content provider allows multiple instances of itself * to run in different process. If false, a single instances is always * run in {@link #processName}. */ public boolean multiprocess = false;
ActivityThread.installProviderAuthoritiesLocked
接著看看一下 installProviderAuthoritiesLocked 的實現
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); if (provider != null) { // If this provider is hosted by the core OS and cannot be upgraded, // then I guess we're okay doing blocking calls to it. for (String auth : auths) { switch (auth) { case ContactsContract.AUTHORITY: case CallLog.AUTHORITY: case CallLog.SHADOW_AUTHORITY: case BlockedNumberContract.AUTHORITY: case CalendarContract.AUTHORITY: case Downloads.Impl.AUTHORITY: case "telephony": Binder.allowBlocking(provider.asBinder()); } } } final ProviderClientRecord pcr = new ProviderClientRecord( auths, provider, localProvider, holder); for (String auth : auths) { final ProviderKey key = new ProviderKey(auth, userId); 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; }
根據 Provider 的資訊建立了一個 ProviderClientRecord (PCR) 物件,authority 是一個多屬性值,變數這個 Provider 對應的所有 authority,每個 authority 屬性為 key,儲存這個 ProviderClientReocrd 到 mProviderMap 描述的 HashMap 中。
在一個應用程序中 (ActivityThread) 有三個列表來儲存本程序中的 ContentProvider 的資訊。
-
ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
- 主要以 authority 為 key,儲存 providerClientRecord 資訊
-
ArrayMap<IBinder, ProviderClientRecord> mLocalProviders
- 以通訊的介面 Binder 物件為 key 儲存 ProviderClientRecord 物件。主要儲存了本程序的 ContentProvider 的資訊
-
ArrayMap<ComponentName,ProviderClientRecord> mLocalProvidersByName
-
以 Provider 的 ComponentName 資訊為key 儲存 ProviderClientRecord 物件。主要儲存了本程序的 ContentProvider 的資訊
-
通過 installProvider 方法將 ContentProvider 的類載入到記憶體中來,並建立了 ContentProvider 的物件,呼叫了 ContentProvider的onCreate 來啟動它。然後將它按照不同的儲存型別分別儲存不同的 ContentProvider 集合中。
AMS.publishContentProviders
ContentProvider 本地建立完成並儲存後,將它封裝成立一個 ContentProviderHolder 物件返回,然後我們呼叫 AMS 的 publishContentProviders 方法(實際上是通過 AMP (ActivityManagerProxy) 傳送一個型別為 PUBLISH_CONTENT_PROVIDERS_TRANSACTION的程序間通訊請求),將這些 Holder 物件傳送給 AMS 服務將他們釋出到 AMS 服務中。
// AMS
public final void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) { if (providers == null) { return; } enforceNotIsolatedCaller("publishContentProviders"); synchronized (this) { final ProcessRecord r = getRecordForAppLocked(caller); if (DEBUG_MU) Slog.v(TAG_MU, "ProcessRecord uid = " + r.uid); if (r == null) { throw new SecurityException( "Unable to find app for caller " + caller + " (pid=" + Binder.getCallingPid() + ") when publishing content providers"); } final long origId = Binder.clearCallingIdentity(); 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; }
// 獲取 cpr ContentProviderRecord dst = r.pubProviders.get(src.info.name); if (DEBUG_MU) Slog.v(TAG_MU, "ContentProviderRecord uid = " + dst.uid); if (dst != null) { ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
// 元件名字為 key mProviderMap.putProviderByClass(comp, dst); String names[] = dst.info.authority.split(";"); for (int j = 0; j < names.length; j++) { mProviderMap.putProviderByName(names[j], dst); } 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); } // Make sure the package is associated with the process. // XXX We shouldn't need to do this, since we have added the package // when we generated the providers in generateApplicationProvidersLocked(). // But for some reason in some cases we get here with the package no longer // added... for now just patch it in to make things happy. r.addPackage(dst.info.applicationInfo.packageName, dst.info.applicationInfo.longVersionCode, mProcessStats); 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); } }
引數 caller 是一個型別為 ApplicationThread 的 binder 代理物件,它引用了執行在新建立的應用程式程序中的一個 ApplicationThread 物件,第 8 行程式碼通過它來獲得用來描述新建立的應用程式程序的一個 ProcessRecord 物件。
新建立的應用程式程序在啟動時,會將需要在它裡面執行的 Content Provider 元件啟動起來。Content provider 元件在 AMS 中使用一個 ContentProviderRecord 物件來描述,它們儲存在用來描述新建立的應用程式程序的一個 ProcessRecord 物件 r 成員變數 pubProviders 中。
引數 providers 包含了要釋出到 AMS 中的 Content provider 元件,每一個 content provider 元件都使用 ContentProviderHolder 物件來描述,它裡面包含了要釋出的 content provider 元件的一個 IContentProvider 介面。
程式碼中的第一個大 for 迴圈,首先去除儲存在引數 Providers 中的每一個 ContentProviderHolder 物件 src,然後在 AMS 中找到與對應的一個CPR 物件 dst ,最後將 ContentProviderHolder 物件 src 所描述的一個 CP 元件的一個 IContentProvider 訪問介面儲存在 CPR 物件 dst 的成員變數 provider 中。
關於 ContentProvider
隨著應用的啟動而載入、初始化的流程到這裡就結束了。
下面就來看使用 ContentProvider
的工作流程。
資料傳輸過程
前面講了 content Provider 的啟動過程,接下來看看其資料是如何傳輸的。
通常我們獲取 ContentResolver 的程式碼如下:
ContentResolver cr = context.getContentResolver(); //獲取ContentResolver
Context 的所有實現都是在 ContextImpl 中,所以 context.getContentResolver() 方法的實現也是一樣。
ContextImpl.getContentResolver().query()
我們從 ContextImpl.getContentResolver().query()
開始看:
// ContentResolver.java
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri, @Nullable String[] projection, @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) { Preconditions.checkNotNull(uri, "uri");
// 獲取訪問介面 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 {
// 呼叫 query 方法獲取資料 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(); long durationMillis = SystemClock.uptimeMillis() - startTime; maybeLogQueryToEventLog(durationMillis, uri, projection, queryArgs); // 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 { if (qCursor != null) { qCursor.close(); } if (cancellationSignal != null) { cancellationSignal.setRemote(null); } if (unstableProvider != null) { releaseUnstableProvider(unstableProvider); } if (stableProvider != null) { releaseProvider(stableProvider); } } }
該方法主要是獲取到 IContentProvider,從而根據 uri 拿到資料。
要想理解上述過程的具體實現細節,需要先分析 ContentResolver 類的 acquireProvider 方法的呼叫過程,然後分析 IContentProvider 介面方法 query 的實現。
ContentResolver 的 acquireProvider 是一個抽象方法,具體實現可以看 ApplicationContentResolver:
private final ActivityThread mMainThread;
protected IContentProvider acquireProvider(Context context, String auth) { return mMainThread.acquireProvider(context, ContentProvider.getAuthorityWithoutUserId(auth), resolveUserIdFromAuthority(auth), true); }
ApplicationContentResolver 成員變數 mMainThread 指向了一個 ActivityThread 物件,它是在建構函式裡面初始化的。因此,實際上是通過 ActivityThread 來獲取 contentProvider 的代理物件。
ActivityThread.acquireProvider
來看下具體的實現:
public final IContentProvider acquireProvider( Context c, String auth, int userId, boolean stable) {
// 如果已經存在了,就直接返回,這裡是從前面提到的 mProviderMap 來獲取 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)) {
// 通過 ams 來獲取 holder holder = ActivityManager.getService().getContentProvider( getApplicationThread(), auth, userId, stable); } } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); }
// 獲取失敗 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; }
即在本地沒有獲得過 IContentProvider
時,直接向 ActivityManagerService
發起 getContentProvider
的請求,最終呼叫ActivityManagerService.getContentProviderImpl()
, 這個方法就是 ContentProvider
例項化邏輯的核心了:
首先來看一下這個方法的宣告:
ContentProviderHolder getContentProviderImpl(IApplicationThread caller,String name, IBinder token, boolean stable, int userId)
即最終是返回一個 ContentProviderHolder,前面已經解釋了。
getContentProviderImpl()
,這個方法比較長,所以接下來我們分段來看這個方法, 順序是(1)、(2)、(3)... 這種 :
(1)ActivityManagerService.getContentProviderImpl()
//三個關鍵物件 ContentProviderRecord cpr; ContentProviderConnection conn = null; ProviderInfo cpi = null; ... cpr = mProviderMap.getProviderByName(name, userId); // 看看系統是否已經快取了這個ContentProvider
這裡主要需要解釋下 ContentProviderConnection
/** * Represents a link between a content provider and client. */ public final class ContentProviderConnection extends Binder { public final ContentProviderRecord provider; public final ProcessRecord client; public final long createTime; public int stableCount; public int unstableCount; // The client of this connection is currently waiting for the provider to appear. // Protected by the provider lock. public boolean waiting; // The provider of this connection is now dead. public boolean dead;
它是一個 Binder
。連線服務端 (ActivityManagerService) 和客戶端 (我們的app)。裡面記錄著一個 ContentProvider
的狀態,比如是否已經死掉了等。其他幾個都已經解釋過了。
(2)ActivityManagerService.getContentProviderImpl()
cpr = mProviderMap.getProviderByName(name, userId); // 看看系統是否已經快取了這個ContentProvider boolean providerRunning = cpr != null && cpr.proc != null && !cpr.proc.killed; if (providerRunning) { ... } if (!providerRunning) { ... }
即根據 ContentProvider
所在的程序是否是活躍
、這個 ContentProvider 是否被啟動過(快取下來)
兩個狀態來進行不同的處理 :
ContentProvider 已被載入並且所在的程序正在執行
即: if(providerRunning){ ... }
中的程式碼
ProcessRecord r = getRecordForAppLocked(caller); //獲取客戶端(獲得content provider的發起者)的程序資訊 if (r != null && cpr.canRunHere(r)) { //如果請求的ContentProvider和客戶端位於同一個程序 ContentProviderHolder holder = cpr.newHolder(null); //ContentProviderConnection引數傳null holder.provider = null; //注意,這裡置空是讓客戶端自己去例項化!! return holder; } //客戶端程序正在執行,但是和ContentProvider並不在同一個程序 conn = incProviderCountLocked(r, cpr, token, stable); // 直接根據 ContentProviderRecord和ProcessRecord 構造一個 ContentProviderConnection ...即如果請求的是同進程的
ContentProvider
則直接回到程序的主執行緒去例項化 ContentProvider
。否則使用 ContentProviderRecord
和 ProcessRecord
構造一個ContentProviderConnection。
ContentProvider所在的程序沒有執行並且服務端(ActivityManagerService)沒有載入過它
即: if(!providerRunning){ ... }
中的程式碼
//先解析出來一個ProviderInfo cpi = AppGlobals.getPackageManager().resolveContentProvider(name, STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId); ... ComponentName comp = new ComponentName(cpi.packageName, cpi.name); cpr = mProviderMap.getProviderByClass(comp, userId); //這個content provider 沒有被載入過 final boolean firstClass = cpr == null; if (firstClass) { ... cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton); // 構造一個 ContentProviderRecord } ... final int N = mLaunchingProviders.size(); // mLaunchingProviders它是用來快取正在啟動的 ContentProvider的集合的 int i; for (i = 0; i < N; i++) { if (mLaunchingProviders.get(i) == cpr) { // 已經請求過一次了,provider正在啟動,不重複走下面的邏輯 break; } } //這個 ContentProvider 不是在啟動狀態,也就是還沒啟動 if (i >= N) { ProcessRecord proc = getProcessRecordLocked(cpi.processName, cpr.appInfo.uid, false); ... if (proc != null && proc.thread != null && !proc.killed) { //content provider所在的程序已經啟動 proc.thread.scheduleInstallProvider(cpi); //安裝這個 Provider , 即客戶端例項化它 } else { //啟動content provider 所在的程序, 並且喚起 content provider proc = startProcessLocked(cpi.processName,cpr.appInfo, false, 0, "content provider",new ComponentName(cpi.applicationInfo.packageName,cpi.name)...); } cpr.launchingApp = proc; mLaunchingProviders.add(cpr); //新增到正在啟動的佇列 } //快取 ContentProvider資訊 if (firstClass) { mProviderMap.putProviderByClass(comp, cpr); } mProviderMap.putProviderByName(name, cpr); //構造一個 ContentProviderConnection conn = incProviderCountLocked(r, cpr, token, stable); if (conn != null) { conn.waiting = true; //設定這個connection }
(3)ActivityManagerService.getContentProviderImpl()
// Wait for the provider to be published... synchronized (cpr) { while (cpr.provider == null) { .... if (conn != null) { conn.waiting = true; } cpr.wait(); } } return cpr != null ? cpr.newHolder(conn) : null; //返回給請求這個客戶端的程序
根據前面的分析,ContentProvider 所在的程序沒有執行或者不是和獲取者
同一個程序,就建立了一個 ContentProviderConnection,
那麼服務端就會掛起,啟動 ContentProvider 所在的程序,並等待它例項化 ContentProvider
。ok,通過前面的分析我們知道 ContentProvider
最終是在它所在的程序例項化的。
接下來就看一下客戶端相關程式碼,前面分析我們知道,如果客戶端程序
和請求的 ContentProvider
位於同一個程序,則 ActivityManager.getService().getContentProvider(...);
會返回一個內容為空的 ContentProviderHolder
, 我們再拿剛開始客戶端向服務端請求 ContentProvider 的程式碼看一下:
holder = ActivityManager.getService().getContentProvider( getApplicationThread(), auth, userId, stable); //在向服務端獲取holder,服務端如果發現ContentProvider的程序和當前客戶端程序是同一個程序就會讓客戶端程序來例項化ContentProvider,具體細節可以在下面分析中看到 holder = installProvider(c, holder, holder.info, true /*noisy*/, holder.noReleaseNeeded, stable);
不在同一個程序中的 ContentProvider 例項化過程
如果客戶端程序
和請求的 ContentProvider
不在同一個程序,根據前面我們分析 ActivityManagerService
的邏輯可以知道,ActivityManagerService
會呼叫ContentProvider
所在程序的 proc.thread.scheduleInstallProvider(cpi)
, 其實最終呼叫到 ActivityThread.installContentProviders
private void installContentProviders(Context context, List<ProviderInfo> providers) { final ArrayList<ContentProviderHolder> results = new ArrayList<>(); //ActivityManagerService 讓客戶端啟動的是一個ContentProvider列表 for (ProviderInfo cpi : providers) { ContentProviderHolder cph = installProvider(context, null, cpi,false, true ,true); if (cph != null) { cph.noReleaseNeeded = true; results.add(cph); } } ActivityManager.getService().publishContentProviders(getApplicationThread(), results); //通知服務端,content provider ok啦 }
到這裡,就跟前面的啟動邏輯基本一樣了。 那麼在獲取到介面後,又是怎麼通過 query 拿到資料的呢?
query 過程解析
從前面的分析可知,IContentProvider 是 Transport 的例項,所以具體 query 的邏輯需要看
@Override public Cursor query(String callingPkg, Uri uri, @Nullable String[] projection, @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) { uri = validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { if (projection != null) { return new MatrixCursor(projection, 0); } Cursor cursor; final String original = setCallingPackage(callingPkg); try {
// 呼叫的是 mInterface cursor = mInterface.query( uri, projection, queryArgs, CancellationSignal.fromTransport(cancellationSignal)); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { setCallingPackage(original); } if (cursor == null) { return null; } // Return an empty cursor for all columns. return new MatrixCursor(cursor.getColumnNames(), 0); } Trace.traceBegin(TRACE_TAG_DATABASE, "query"); final String original = setCallingPackage(callingPkg); try { return mInterface.query( uri, projection, queryArgs, CancellationSignal.fromTransport(cancellationSignal)); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } finally { setCallingPackage(original); Trace.traceEnd(TRACE_TAG_DATABASE); } }
這裡是通過 mInterface 來呼叫 query () 方法
volatile ContentInterface mInterface = ContentProvider.this;而實際上就是呼叫的自己實現的 provider 方法。具體實現其實是通過 db 來查詢的。到這裡關於 CP 的資料傳輸過程也講完了。 但是對於跨程序,其實應該是通過 ContentProviderNative 的內部類 ContentProviderProxy 來進行跨程序呼叫,最終會回撥到 ContentProviderNative 的 onTransact 方法,然後再呼叫 mInterface 的 query 方法來實現查詢。
class ContentProviderProxy implements IContentProvider{}
ContentProviderProxy 是實現了 IContentProvider 介面的。那麼是什麼時候轉成 ContentProviderProxy 呢?
// ContentProviderHolder.java
private ContentProviderHolder(Parcel source) { info = ProviderInfo.CREATOR.createFromParcel(source); provider = ContentProviderNative.asInterface( source.readStrongBinder()); connection = source.readStrongBinder(); noReleaseNeeded = source.readInt() != 0; }
到這裡才知道原來是建立 CPH 的時候,會根據是當前程序還是跨程序來返回對應的例項。
// ContentProviderNative.java static public IContentProvider asInterface(IBinder obj) { if (obj == null) { return null; } IContentProvider in = (IContentProvider)obj.queryLocalInterface(descriptor); if (in != null) { return in; } return new ContentProviderProxy(obj); }
到這裡相信你應該能理解 CP 的啟動以及其跨程序通訊能力了。
總結
對於 ContentProvider,有兩種啟動方式:-
一種是啟動 App 的時候,啟動 CP;
-
另一種是需要訪問其他 App 的資料,如果對應的 App 並沒有啟動,這時候也會啟動 CP;
一旦 CP 啟動之後,就會將 ContentProviderHolder(內含 ICP 介面) 釋出到 AMS 中,這樣其他 App 或自身都可以通過 AMS 獲取到 ICP 介面,從而獲取資料。 此外,ContentProvider
的 onCreate
在 Application
的 onCreate
之前呼叫。