Android ContentProvider 啟動分析

huansky發表於2020-11-22

對於 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>), 比如 authorityreadPermission 等。

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,有兩種啟動方式:
  1. 一種是啟動 App 的時候,啟動 CP;

  2. 另一種是需要訪問其他 App 的資料,如果對應的 App 並沒有啟動,這時候也會啟動 CP;

一旦 CP 啟動之後,就會將   ContentProviderHolder(內含 ICP 介面) 釋出到 AMS 中,這樣其他 App 或自身都可以通過 AMS 獲取到 ICP 介面,從而獲取資料。 此外,ContentProvider 的 onCreate 在 Application 的 onCreate 之前呼叫。 

參考文章

Android系統原始碼分析之-ContentProvider

ContentProvider原理分析

Android-ContentProvider原始碼解讀

相關文章