[學習筆記]Android開發藝術探索:四大組件的工作過程之ContentProvider

ContentProvider是一個內存共享型組件,他通過Binder向其他組件乃至其他應用提供數據,當ContentProvider所在的進程啓動的時候,ContentProvider會同時啓動並且發佈到AMS中,需要注意的是,這個時候ContentProvider的onCreate要先於Application的onCreate執行,這是四大組件一個少有的現象

一個應用啓動的時候,入口方法在ActivityThread中的main裏,main是一個靜態方法,在main中會創建ActivityThread的實例並創建主線程的消息隊列,然後在ActivityThread的attach中遠程調用AMS的attachApplication方法並將ActivityThread提供給AMS,ActivityThread是一個Binder對象,他的Binder接口是IApplicationThread,他主要用於ActivityThread和AMS之間的通信,會調用ApplicationThread的bindApplication方法,注意這個過程同樣是跨進程完成的,bindApplication的邏輯是經過ActivityThread中的mH Hander切換到ActivityThread執行,具體是方法是handlerBindApplication,在handlerApplication方法中,ActivityThread會加載一個ContentProvider,然後再調用Application的onCreate。

ContentProvider啓動後,外界就可以通過他所提供的增刪改查四個接口來操作ContentProvider的數據源,insert,delete,update,query,這四個方法都是通過Binder來調用的,外界無法直接訪問ContentProvider,他只能通過AMS提供的Uri來獲取對應的ContentProvider的Binder接口和IContentProvider,然後通過IContentProvider來訪問ContentProvider的數據源。

一般來說,ContentProvider都應該是單實例的,ContentProvider到底是不是單實例的,這是由他的android:multiprocess來決定的,當他爲false的時候爲多實例,默認爲true,在實際的開發當中,我們並沒有發現多實例的案例,官方文檔的解釋是避免進程間通信的開銷,但是在實際開發中仍然缺少使用價值,因此我們可以簡單認爲ContentProvider就是單實例,接下來我們來分析下ContentProvider的啓動過程

黨文ContentProvider需要通過ContentResolver,ContentResolver是一個抽象類,通過Context的getContentResolver方法獲取的實際上是ApplicationContentResolver對象,當ContentProvider所在的進程未啓動的時候,第一次訪問他時就會觸發ContentProvider的創建,這也就伴隨着ContentProvider所在的進程啓動,通過ContentProvider的第四個方法的任何一個都能啓動,這裏我們通過query來講解

ContentProvider的query方法,首先獲取的IContentProvider對象,不管是通過acquireUnstableProvider方法還是直接通過acquireProvider方法,他們的本質都是一樣的,最終都是通過acquireProvider的方法來獲取ContentProvider,下面是ApplicationContentProvider的acquireProvider具體的實現方式:

		protected IContentProvider acquireProvider(Context context,String auth){
       return mMainThread.acquireProvider(context,
               ContentResolver.getAuthorityWithoutUserId(auth),
               resolveUserIdFromAuthority(auth),true);
   }

ApplicationContentResolver的acquireProvider方法並沒有處理任何邏輯,他直接調用了ActivityThread的acquireProvider方法,acquireProvider的這個方法源碼如下

   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;
        }

        // 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.
        IActivityManager.ContentProviderHolder holder = null;
        try {
            holder = ActivityManagerNative.getDefault().getContentProvider(
                    getApplicationThread(), auth, userId, stable);
        } catch (RemoteException ex) {
        }
        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;
    }

上面的代碼首先會從ActivityThread中超找是否已經存在目標的ContentProvider,如果存在就直接返回,ActivityThread中通過mProviderMap來存儲已經啓動的ContentProvider

 		// The lock of mProviderMap protects the following variables.
    final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
        = new ArrayMap<ProviderKey, ProviderClientRecord>();

如果目前ContentProvider並沒有啓動,那麼久發送一個進程間請求給AMS讓其啓動目標ContentProvider,最後通過installProvider方法來修改引用計數,那麼AMS是如何啓動ContentProvider的呢?我們知道,ContentProvider被啓動時伴隨着進程也會被啓動,那麼AMS重,首先會啓動ContentProvider所在的進程,然後再ContentProvider,啓動進程是由AMS的startProcessLocked方法來完成的,其內部主要是通過Process的start方法來完成一系列的新進程啓動,新進程啓動後其入口方法爲ActivityThread的main方法:

    public static void main(String[] args) {
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        Security.addProvider(new AndroidKeyStoreProvider());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    } 

可以看到,ActivityThread的main方法是一個靜態方法,他在內部先創建了一個實例並且調用了attach方法來進行一系列的初始化,接着開始消息循環了,最終會將ApplicationThread對象通過AMS的attchApplication方法跨進程傳遞給AMS,最終AMS會將ContentProvider的創建過程

          try {
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                // Ignore
            } 

AMS的attachApplication方法又調用attachApplicationLocked方法,attachApplicationLocked中又調用了bindApplication,注意這個過程也是進程間調用

ActivityThread的bindApplication會發送一個BIND_APPLICATION類型的消息給mH,他是一個Handler,他收到消息後會調用ActivityThread,過程如下:

    				AppBindData data = new AppBindData();
            data.processName = processName;
            data.appInfo = appInfo;
            data.providers = providers;
            data.instrumentationName = instrumentationName;
            data.instrumentationArgs = instrumentationArgs;
            data.instrumentationWatcher = instrumentationWatcher;
            data.instrumentationUiAutomationConnection = instrumentationUiConnection;
            data.debugMode = debugMode;
            data.enableOpenGlTrace = enableOpenGlTrace;
            data.restrictedBackupMode = isRestrictedBackupMode;
            data.persistent = persistent;
            data.config = config;
            data.compatInfo = compatInfo;
            data.initProfilerInfo = profilerInfo;
            sendMessage(H.BIND_APPLICATION, data);

ActivityThread的bindBaseApplication則完成了Application的創建以及ContentProvider的創建,可以分爲如下四個步驟:

1.創建ContextImpl和instrumentation

            ContextImpl instrContext = ContextImpl.createAppContext(this, pi);

            try {
                java.lang.ClassLoader cl = instrContext.getClassLoader();
                mInstrumentation = (Instrumentation)
                    cl.loadClass(data.instrumentationName.getClassName()).newInstance();
            } catch (Exception e) {
                throw new RuntimeException(
                    "Unable to instantiate instrumentation "
                    + data.instrumentationName + ": " + e.toString(), e);
            }

            mInstrumentation.init(this, instrContext, appContext,
                   new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher,
                   data.instrumentationUiAutomationConnection);

2.創建Application對象

            Application app = data.info.makeApplication(data.restrictedBackupMode, null);
            mInitialApplication = app;

3.啓動當前進程的ContentProvider並調用onCreate方法

       		if (!data.restrictedBackupMode) {
                List<ProviderInfo> providers = data.providers;
                if (providers != null) {
                    installContentProviders(app, providers);
                    // For process that contains content providers, we want to
                    // ensure that the JIT is enabled "at some point".
                    mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
                }
            }

installContentProviders完成了ContentProvider的啓動工作,他的實現,首先是遍歷ProviderInfo的列表並一一調用installProvider方法來啓動他們,接着將以及啓動的ContentProvider發送到AMS中,AMS會把他們存儲在ProviderMap中,這樣一來外部調用者可以直接從AMS中獲取ContentProvider了

  private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
        final ArrayList<IActivityManager.ContentProviderHolder> results =
            new ArrayList<IActivityManager.ContentProviderHolder>();

        for (ProviderInfo cpi : providers) {
            if (DEBUG_PROVIDER) {
                StringBuilder buf = new StringBuilder(128);
                buf.append("Pub ");
                buf.append(cpi.authority);
                buf.append(": ");
                buf.append(cpi.name);
                Log.i(TAG, buf.toString());
            }
            IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
            if (cph != null) {
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }

        try {
            ActivityManagerNative.getDefault().publishContentProviders(
                getApplicationThread(), results);
        } catch (RemoteException ex) {
        }
    }

下面看一下ContentProvider對象的創建過程,在installProvider方法中有下面一段代碼,並通過類加載器完成了ContentProvider對象的創建:

					 try {
                final java.lang.ClassLoader cl = c.getClassLoader();
                localProvider = (ContentProvider)cl.
                    loadClass(info.name).newInstance();
                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.
                localProvider.attachInfo(c, info); 

在上述代碼中,除了完成ContentProvider對象的創建,還會通過ContentProvider的attachInfo方法來調用他的onCreate方法,如下所示:

    private void attachInfo(Context context, ProviderInfo info, boolean testing) {
        mNoPerms = testing;

        /*
         * Only allow it to be set once, so after the content service gives
         * this to us clients can't change it.
         */
        if (mContext == null) {
            mContext = context;
            if (context != null) {
                mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
                        Context.APP_OPS_SERVICE);
            }
            mMyUid = Process.myUid();
            if (info != null) {
                setReadPermission(info.readPermission);
                setWritePermission(info.writePermission);
                setPathPermissions(info.pathPermissions);
                mExported = info.exported;
                mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
                setAuthorities(info.authority);
            }
            ContentProvider.this.onCreate();
        }
    } 

到此爲止,ContentProvider已經被創建並且其onCreate方法也被調用了,這意味着他已經啓動完成了

4.調用Application的onCreate方法

            try {
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                if (!mInstrumentation.onException(app, e)) {
                    throw new RuntimeException(
                        "Unable to create application " + app.getClass().getName()
                        + ": " + e.toString(), e);
                }
            }

經過上面的四個步驟,ContentProvider已經啓動了,並且在其所在進程的Application也已經啓動了,這意味着ContentProvider所在的進程已經完成了整個的啓動過程,然後其他應用通過AMS來訪問這個ContentProvider,我們來看下query的實現

        @Override
        public Cursor query(String callingPkg, Uri uri, String[] projection,
                String selection, String[] selectionArgs, String sortOrder,
                ICancellationSignal cancellationSignal) {
            validateIncomingUri(uri);
            uri = getUriWithoutUserId(uri);
            if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
                return rejectQuery(uri, projection, selection, selectionArgs, sortOrder,
                        CancellationSignal.fromTransport(cancellationSignal));
            }
            final String original = setCallingPackage(callingPkg);
            try {
                return ContentProvider.this.query(
                        uri, projection, selection, selectionArgs, sortOrder,
                        CancellationSignal.fromTransport(cancellationSignal));
            } finally {
                setCallingPackage(original);
            }
        }

很顯然,ContentProvider.Transport的query方法調用了ContentProvider的query方法,query方法的執行結果再通過Binder返回給調用者,這樣一來整個調用過程就完成了,除了query方法,其他方法也是類似的,這裏就不去深究了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章