Android ContentProvider啓動流程

概述

ContentProvider的啓動其實是在App啓動時就自動啓動的,還不知道APP啓動流程的,推薦看一下Android App啓動過程,我們知道當一個App啓動時,經歷了以下步驟

  • 首先是點擊App圖標,此時是運行在Launcher進程,通過ActivityManagerServiceBinder IPC的形式向system_server進程發起startActivity的請求
  • system_server進程接收到請求後,通過Process.start方法向zygote進程發送創建進程的請求
  • zygote進程fork出新的子進程,即App進程
  • 然後進入ActivityThread.main方法中,這時運行在App進程中,通過ActivityManagerServiceBinder IPC的形式向system_server進程發起attachApplication請求
  • system_server接收到請求後,進行一些列準備工作後,再通過Binder IPC向App進程發送scheduleLaunchActivity請求
  • App進程binder線程(ApplicationThread)收到請求後,通過Handler向主線程發送LAUNCH_ACTIVITY消息
  • 主線程收到Message後,通過反射機制創建目標Activity,並回調ActivityonCreate

而我們的ContentProvider的啓動是在第四步的attachApplication中請求的開始的,下面我們就具體看源碼分析

ActivityManagerService.java

 public final void attachApplication(IApplicationThread thread, long startSeq) {
        synchronized (this) {
            int callingPid = Binder.getCallingPid();
            final int callingUid = Binder.getCallingUid();
            final long origId = Binder.clearCallingIdentity();
            attachApplicationLocked(thread, callingPid, callingUid, startSeq);
            Binder.restoreCallingIdentity(origId);
        }
    }
private final boolean attachApplicationLocked(IApplicationThread thread,
        int pid) {
    ....
    thread.bindApplication(processName, appInfo, providers,
            app.instr.mClass,
            profilerInfo, app.instr.mArguments,
            app.instr.mWatcher,
            app.instr.mUiAutomationConnection, testMode,
            mBinderTransactionTrackingEnabled, enableTrackAllocation,
            isRestrictedBackupMode || !normalMode, app.persistent,
            new Configuration(getGlobalConfiguration()), app.compat,
            getCommonServicesLocked(app.isolated),
            mCoreSettingsObserver.getCoreSettingsLocked(),
            buildSerial);
}

這裏調用了threadbindApplication方法,thread的類型是IApplicationThread,是一個binder用於跨進程通信,實現類是ActivityThread的內部類ApplicationThread

ApplicationThread.java


        public final void bindApplication(String processName, ApplicationInfo appInfo,
                List<ProviderInfo> providers, ComponentName instrumentationName,
                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection, int debugMode,
                boolean enableBinderTracking, boolean trackAllocation,
                boolean isRestrictedBackupMode, boolean persistent, Configuration config,
                CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
                String buildSerial, boolean autofillCompatibilityEnabled) {

            ...
            sendMessage(H.BIND_APPLICATION, data);
        }

這個方法其實最後發送了一個BIND_APPLICATION消息給ActivityThread的內部類H

ActivityThread.java

    public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;

這個方法調用了handleBindApplication方法

private void handleBindApplication(AppBindData data) {
    ...
    final InstrumentationInfo ii;
    ...
    // 創建 mInstrumentation 實例
    if (ii != null) {
        ...
        //創建ContextImpl
        final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);

        try {
            //創建mInstrumentation實例
            final ClassLoader cl = instrContext.getClassLoader();
            mInstrumentation = (Instrumentation)
                cl.loadClass(data.instrumentationName.getClassName()).newInstance();
        } catch (Exception e) {
            ...
        }
        ...
    } else {
        mInstrumentation = new Instrumentation();
    }
    ...
    Application app;
    ...
    try {
        ...
        // 創建 Application 實例
        app = data.info.makeApplication(data.restrictedBackupMode, null);
        mInitialApplication = app;
         if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    //啓動ContentProvider
                    installContentProviders(app, data.providers);
                    mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
                }
            }
        try {
            //調用Application的onCreate
            mInstrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            ...
        }
    } finally {
        ...
    }
    ...
}

我們從上向下分析這個方法

  • 首先創建了ContextImpl對象
  • 然後創建了mInstrumentation對象
  • 接着創建了Application對象
  • 然後啓動了ContentProvider
  • 最後調用了Application對象的onCreate

我們發現其實在調用ApplicationonCreate之前,就已經啓動了ContentProvider,這個也可以作爲啓動優化的一部分,如果不需要ContentProvider的話建議刪除,因爲他會自動啓動,之前我用TraceView測試過,大概佔用5ms,但是蚊子再小也是肉啊

我們繼續分析installContentProviders方法

 private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
        final ArrayList<ContentProviderHolder> results = new ArrayList<>();
        //註釋1
        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());
            }
            //註釋2
            ContentProviderHolder cph = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
            if (cph != null) {
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }

        try {
            //註釋3
            ActivityManager.getService().publishContentProviders(
                getApplicationThread(), results);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }
  • 註釋1處,遍歷當前應用程序的ProviderInfo列表,得到每個ContentProviderProviderInfo(存儲ContentProvider的信息)
  • 註釋2處,調用installProvider來啓動ContentProvider
  • 通過AMSpublishContentProviders方法,將這些ContentProvider儲存到AMSmProviderMap中,起到緩存作用,防止重複調用

我們繼續分析installProvider方法

 private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
            ···
            //註釋1
                final java.lang.ClassLoader cl = c.getClassLoader();
                LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
                localProvider = cl.loadClass(className).newInstance();
                provider = localProvider.getIContentProvider();
                ···
                //註釋2
                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);
                }
                
    }
  • 註釋1處,通過反射來創建ContentProvider
  • 註釋2處,調用了ContentProviderattachInfo方法
 private void attachInfo(Context context, ProviderInfo info, boolean testing) {
        mNoPerms = testing;

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

在這個方法中調用了ContentProvideronCreate,到這裏ContentProvider的啓動就完成了

參考:《Android進階解密》

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