Android 存儲選項之 ContentProvider 啟動存在的暗坑

閃存
Android 存儲優(yōu)化系列專題
  • SharedPreferences 系列

Android 之不要濫用 SharedPreferences
Android 之不要濫用 SharedPreferences(2)— 數(shù)據(jù)丟失

  • ContentProvider 系列(待更)

Android 存儲選項之 ContentProvider 的啟動性能
《Android 存儲選項之 ContentProvider 深入分析》

  • 對象序列化系列

Android 對象序列化之你不知道的 Serializable
Android 對象序列化之 Parcelable 取代 Serializable ?
Android 對象序列化之追求性能完美的 Serial

  • 數(shù)據(jù)序列化系列(待更)

《Android 數(shù)據(jù)序列化之 JSON》
《Android 數(shù)據(jù)序列化之 Protocol Buffer 源碼分析》

  • SQLite 存儲系列

Android 存儲選項之 SQLiteDatabase 創(chuàng)建過程源碼分析
Android 存儲選項之 SQLiteDatabase 源碼分析
數(shù)據(jù)庫連接池 SQLiteConnectionPool 源碼分析
SQLiteDatabase 啟用事務(wù)源碼分析
SQLite 數(shù)據(jù)庫 WAL 模式工作原理簡介
SQLite 數(shù)據(jù)庫鎖機制與事務(wù)簡介
SQLite 數(shù)據(jù)庫優(yōu)化那些事兒


前言

在 SharedPreferences 系列《Android 之不要濫用 SharedPreferences》和 《Android 之不要濫用 SharedPreferences(2)— 數(shù)據(jù)丟失》兩篇文章中,詳細(xì)分析了 SharedPreferences 的實現(xiàn)機制。簡單回顧下,Android 系統(tǒng)為什么不把 SharedPreferences 設(shè)計成跨進(jìn)程安全的呢?那是因為 Android 系統(tǒng)更希望我們在這個場景使用 ContentProvider 作為存儲方式。

ContentProvider 作為 Android 四大組件之一,為應(yīng)用開發(fā)者提供了不同進(jìn)程甚至是不同應(yīng)用程序之間共享數(shù)據(jù)的機制。

今天我們主要來聊聊 ContentProvider 這個存儲方法。


ContentProvider 的使用

Android 系統(tǒng)中比如相冊、日歷、音頻、視頻、通訊錄等模塊都提供了 ContentProvider 的訪問支持。它的使用非常簡單,你可以參考官方文檔

但是,ContentProvider 在使用過程中也存在一些“暗坑”需要我們特別注意。

  • 啟動性能

ContentProvider 的生命周期默認(rèn)在 Application onCreate() 之前,而且都是在主線程創(chuàng)建的。我們自定義的 ContentProvider 類的構(gòu)造函數(shù)、靜態(tài)代碼塊、onCreate 函數(shù)都盡量不要做耗時的操作,會拖慢啟動速度。

ContentProvider 啟動流程
  • 穩(wěn)定性

ContentProvider 在進(jìn)行跨進(jìn)程數(shù)據(jù)傳遞時,利用了 Android 的 Binder 和匿名共享內(nèi)存機制。簡單來說,就是通過 Binder 傳遞 CursorWindow 對象內(nèi)部的匿名共享內(nèi)存的文件描述符。這樣在跨進(jìn)程傳輸中,結(jié)果數(shù)據(jù)并不需要跨進(jìn)程傳輸,而是在不同進(jìn)程中通過傳輸?shù)哪涿蚕韮?nèi)存文件描述符來操作同一塊匿名內(nèi)存,這樣來實現(xiàn)不同進(jìn)程訪問相同數(shù)據(jù)的目的

CursorWindow

基于 mmap 的匿名共享內(nèi)存機制也是有代價的。當(dāng)傳輸?shù)臄?shù)據(jù)量非常小的時候,可能不一定劃算。所以 ContentProvider 提供了一種 call 函數(shù),它會直接通過 Binder 來傳輸數(shù)據(jù)。

Android 的 Binder 傳輸是有大小限制的,一般來說限制是 1 ~ 2MB。ContentProvider 的接口調(diào)用參數(shù)和 call 函數(shù)調(diào)用并沒有使用匿名共享機制,比如要批量插入很多數(shù)據(jù),那么就會出現(xiàn)一個插入數(shù)據(jù)的數(shù)組,如果這個數(shù)組太大了,那么這個操作就可能出現(xiàn)數(shù)據(jù)超大異常。

  • 安全性

雖然 ContentProvider 為應(yīng)用程序之間的數(shù)據(jù)共享提供了很好的安全機制,但是如果 ContentProvider 是 exported,當(dāng)支持執(zhí)行 SQL 語句時就需要注意 SQL 注入的問題。另外如果我們傳入的參數(shù)是一個文件路徑,然后返回文件內(nèi)容,這個時候也要校驗合法性,不然整個應(yīng)用的私有數(shù)據(jù)都有可能被別人拿到,在 Intent 傳遞參數(shù)的時候可能會經(jīng)常會犯這個錯誤。

今天我們先來聊聊 ContentProvider 的啟動性能,穩(wěn)定性和安全性放到系列后面文章進(jìn)行介紹。還是從源碼的角度出發(fā),分析自定義 ContentProvider 的創(chuàng)建過程和生命周期回調(diào)過程。


自定義 ContentProvider 的啟動過程分析

我們要從應(yīng)用程序啟動類 ActivityThread 開始,關(guān)于 ActivityThread 大家肯定不會感到陌生,它是我們應(yīng)用進(jìn)程的入口類,也就是 main 函數(shù)所在類。

 public static void main(String[] args) {
    //省略部分

    //主線程Looper創(chuàng)建
    Looper.prepareMainLooper();

    //創(chuàng)建ActivityThread對象
    ActivityThread thread = new ActivityThread();
    //調(diào)用自己的attach()方法
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    //省略
    
    //開啟出隊
    Looper.loop();
    //如果調(diào)用了主線程Looper的quit() 拋異常在這里
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

在 main 方法中創(chuàng)建了 ActivityThread 對象,并調(diào)用它的 attach 方法,然后在 attach 方法中通過 AMS(ActivityManagerService)遠(yuǎn)程調(diào)用了 attachApplicationLocked 方法,在該方法中完成自定義 ContentProvider 的收集工作,先來看下 ActivityThread 的 attach 方法。

 private void attach(boolean system, long startSeq) {
    sCurrentActivityThread = this;
    mSystemThread = system;
    if (!system) {
    
        ... 省略
        
         //我們重點關(guān)注這里
        //這里得到實例ActivityManagerService代理類Proxy
        final IActivityManager mgr = ActivityManager.getService();
        try {
            //然后通過代理類完成遠(yuǎn)程(跨進(jìn)程)調(diào)用
            //mAppThread是當(dāng)前進(jìn)程的ApplicationThread實例
           //在完成跨進(jìn)程條用完成之后要通過該對象回到當(dāng)前進(jìn)程
            mgr.attachApplication(mAppThread, startSeq);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
}

通過 ActivityManager 獲得 ActivityManagerService 的代理對象,完成跨進(jìn)程調(diào)用,由于 Binder 進(jìn)程間通信不是我們今天要分析的內(nèi)容,這里通過一張流程圖簡單說下該過程。

ActivityManagerService
ActivityManagerSerivce 的 attachApplication 方法分析

前面有簡單提到通過 AMS 遠(yuǎn)程調(diào)用該方法,在該方法內(nèi)開始收集注冊在 AndroidManifest.xml 中的 ContentProvider 信息,實際上該方法主要完成兩部分工作:

  1. generateApplicationProvidersLocked 方法,通過 PMS 完成 ContentProvider 注冊信息(ProviderInfo)的收集工作。
  2. 將收集 Providers 的信息集合,作為參數(shù)遠(yuǎn)程調(diào)用 ApplicationThread 的 bindApplication 方法。此時將重新回到應(yīng)用進(jìn)程啟動類 ActivityThread 中。

在 attachApplication 方法中調(diào)用了 attachApplicationLocked ,我們直接看下該方法:

private final boolean attachApplicationLocked(IApplicationThread thread,
        int pid, int callingUid, long startSeq) {

   // ...省略
   
    ProcessRecord app;
    long startTime = SystemClock.uptimeMillis();
    if (pid != MY_PID && pid >= 0) {
        synchronized (mPidsSelfLocked) {
            app = mPidsSelfLocked.get(pid);
        }
    } else {
        app = null;
    }

    // ...省略

    //通過PMS查找應(yīng)用在Manifest中注冊的ContentProvider
    List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;

    if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
        //這里啟動Provider的超時機制 10s鐘
        Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
        msg.obj = app;
        //通過發(fā)送延遲消息
        mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
    }

   //...省略
   
        //調(diào)用ApplicationThread的bindApplication(), 
        //重新回到啟動類 ActivityThread 中
        if (app.isolatedEntryPoint != null) {
            // This is an isolated process which should just call an entry point instead of
            // being bound to an application.
            thread.runIsolatedEntryPoint(app.isolatedEntryPoint, app.isolatedEntryPointArgs);
        } else if (app.instr != null) {
            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, isAutofillCompatEnabled);
        } else {
            thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
                    null, null, null, testMode,
                    mBinderTransactionTrackingEnabled, enableTrackAllocation,
                    isRestrictedBackupMode || !normalMode, app.persistent,
                    new Configuration(getGlobalConfiguration()), app.compat,
                    getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked(),
                    buildSerial, isAutofillCompatEnabled);
        }
    } catch (Exception e) {
        //...省略
        return false;
    }
   // ...省略
    return true;
}

先來跟蹤第一部分內(nèi)容,generateApplicationProviderslocked 方法開始收集 ContentProvider 的注冊信息。

private final List<ProviderInfo> generateApplicationProvidersLocked(ProcessRecord app) {
    List<ProviderInfo> providers = null;
    try {
        //通過PackageManager獲取 provider 注冊信息
        //這里通信過程與ActivityManager類似
        providers = AppGlobals.getPackageManager()
                .queryContentProviders(app.processName, app.uid,
                        STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS
                                | MATCH_DEBUG_TRIAGED_MISSING, /*metadastaKey=*/ null)
                .getList();
    } catch (RemoteException ex) {}
    // 省略
    return providers;
}

上述方法中 AppGlobals 的 getPackageManager 方法實際返回的是 PackageManagerService,然后調(diào)用它的 queryContentProviders 方法:

public @NonNull ParceledListSlice<ProviderInfo> queryContentProviders(String processName,
        int uid, int flags, String metaDataKey) {
    //...省略
    ArrayList<ProviderInfo> finalList = null;
    // reader
    synchronized (mPackages) {
        //遍歷mProviders
        //第一個mProviders是ProviderIntentResolver類型
        //第二個mProviders是一個ArrayMap
        final Iterator<PackageParser.Provider> i = mProviders.mProviders.values().iterator();
        while (i.hasNext()) {
            //PackageParser.Provider持有ProviderInfo
            final PackageParser.Provider p = i.next();
            PackageSetting ps = mSettings.mPackages.get(p.owner.packageName);
            if (ps != null && p.info.authority != null
                    && (processName == null
                            || (p.info.processName.equals(processName)
                                    && UserHandle.isSameApp(p.info.applicationInfo.uid, uid)))
                    && mSettings.isEnabledAndMatchLPr(p.info, flags, userId)) {

                // See PM.queryContentProviders()'s javadoc for why we have the metaData
                // parameter.
                if (metaDataKey != null
                        && (p.metaData == null || !p.metaData.containsKey(metaDataKey))) {
                    continue;
                }
                final ComponentName component =
                        new ComponentName(p.info.packageName, p.info.name);
                if (filterAppAccessLPr(ps, callingUid, component, TYPE_PROVIDER, userId)) {
                    continue;
                }
                if (finalList == null) {
                    finalList = new ArrayList<ProviderInfo>(3);
                }
                //ProviderInfo表示一個ContentProvider相關(guān)信息,包括其包名,類名等。
                //它繼承自ComponentInfo并實現(xiàn)了Parcalable
                ProviderInfo info = PackageParser.generateProviderInfo(p, flags,
                        ps.readUserState(userId), userId);
                if (info != null) {
                    finalList.add(info);
                }
            }
        }
    }

    if (finalList != null) {
        Collections.sort(finalList, mProviderInitOrderSorter);
        return new ParceledListSlice<ProviderInfo>(finalList);
    }
    return ParceledListSlice.emptyList();
}

該方法的主要工作是遍歷 ProviderIntentResolver 中 mProviders (Map)容器,該容器保存的是 PackageParse.Provider:

 private final ArrayMap<ComponentName, PackageParser.Provider> mProviders
            = new ArrayMap<ComponentName, PackageParser.Provider>();

每個 PackageParse.Provider 持有一個 ProviderInfo,ProviderInfo 代表一個 ContentProvider 相關(guān)信息。所以此時我們需要知道 mProviders 是在哪里被添加數(shù)據(jù)的?

經(jīng)過查找發(fā)現(xiàn)在 commitPackageSettings 方法中添加相關(guān)數(shù)據(jù)

private void commitPackageSettings(PackageParser.Package pkg,
                                   @Nullable PackageParser.Package oldPkg, PackageSetting pkgSetting, UserHandle user,
                                   final @ScanFlags int scanFlags, boolean chatty) {
    //...省略
    synchronized (mPackages) {
        // We don't expect installation to fail beyond this point

        // Add the new setting to mSettings
        mSettings.insertPackageSettingLPw(pkgSetting, pkg);
        // Add the new setting to mPackages
        mPackages.put(pkg.applicationInfo.packageName, pkg);
        // Make sure we don't accidentally delete its data.
        final Iterator<PackageCleanItem> iter = mSettings.mPackagesToBeCleaned.iterator();
        while (iter.hasNext()) {
            PackageCleanItem item = iter.next();
            if (pkgName.equals(item.packageName)) {
                iter.remove();
            }
        }

        // Add the package's KeySets to the global KeySetManagerService
        KeySetManagerService ksms = mSettings.mKeySetManagerService;
        ksms.addScannedPackageLPw(pkg);

        /**存儲ContentProvider信息*/
        int N = pkg.providers.size();
        StringBuilder r = null;
        int i;
        for (i = 0; i < N; i++) {
            PackageParser.Provider p = pkg.providers.get(i);
            p.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    p.info.processName);
            //在queryContentProviders()方法遍歷的便是該Providers中Map容器
            mProviders.addProvider(p);
            p.syncable = p.info.isSyncable;
            if (p.info.authority != null) {
                String names[] = p.info.authority.split(";");
                p.info.authority = null;
                for (int j = 0; j < names.length; j++) {
                    if (j == 1 && p.syncable) {
                        p = new PackageParser.Provider(p);
                        p.syncable = false;
                    }
                    if (!mProvidersByAuthority.containsKey(names[j])) {
                        mProvidersByAuthority.put(names[j], p);
                        if (p.info.authority == null) {
                            p.info.authority = names[j];
                        } else {
                            p.info.authority = p.info.authority + ";" + names[j];
                        }
                        if (DEBUG_PACKAGE_SCANNING) {
                            if (chatty)
                                Log.d(TAG, "Registered content provider: " + names[j]
                                        + ", className = " + p.info.name + ", isSyncable = "
                                        + p.info.isSyncable);
                        }
                    } else {
                        PackageParser.Provider other = mProvidersByAuthority.get(names[j]);
                        Slog.w(TAG, "Skipping provider name " + names[j] +
                                " (in package " + pkg.applicationInfo.packageName +
                                "): name already used by "
                                + ((other != null && other.getComponentName() != null)
                                ? other.getComponentName().getPackageName() : "?"));
                    }
                }
            }
            if (chatty) {
                if (r == null) {
                    r = new StringBuilder(256);
                } else {
                    r.append(' ');
                }
                r.append(p.info.name);
            }
        }
        if (r != null) {
            if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Providers: " + r);
        }

        /**存儲Service信息*/
        N = pkg.services.size();
        r = null;
        for (i = 0; i < N; i++) {
            PackageParser.Service s = pkg.services.get(i);
            s.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    s.info.processName);
            mServices.addService(s);
            if (chatty) {
                if (r == null) {
                    r = new StringBuilder(256);
                } else {
                    r.append(' ');
                }
                r.append(s.info.name);
            }
        }
        if (r != null) {
            if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Services: " + r);
        }

        /**存儲BrodcastReceiver信息*/
        N = pkg.receivers.size();
        r = null;
        for (i = 0; i < N; i++) {
            PackageParser.Activity a = pkg.receivers.get(i);
            a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    a.info.processName);
            mReceivers.addActivity(a, "receiver");
            if (chatty) {
                if (r == null) {
                    r = new StringBuilder(256);
                } else {
                    r.append(' ');
                }
                r.append(a.info.name);
            }
        }
        if (r != null) {
            if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Receivers: " + r);
        }

        /**存儲Activity信息*/
        N = pkg.activities.size();
        r = null;
        for (i = 0; i < N; i++) {
            PackageParser.Activity a = pkg.activities.get(i);
            a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    a.info.processName);
            mActivities.addActivity(a, "activity");
            if (chatty) {
                if (r == null) {
                    r = new StringBuilder(256);
                } else {
                    r.append(' ');
                }
                r.append(a.info.name);
            }
        }
     
        // ...省略
    }

    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}

從源碼中可以看出,commitPackageSettings 方法中不僅包含 ContentProvider 信息,還包括 Service、BroadcastReceiver、Activity 信息。這些信息都是解析 AndroidManifest.xml 文件得到。輔助 PMS 完成解析工作任務(wù)的是 PackageParser,完成這一過程的具體方法如下:

private boolean parseBaseApplication(Package owner, Resources res,
        XmlResourceParser parser, int flags, String[] outError)
    throws XmlPullParserException, IOException {
...省略

 //解析Manifest.xml
 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
            && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
            continue;
        }
        //獲取xml節(jié)點名稱
        String tagName = parser.getName();
        //解析到 activity 節(jié)點
        if (tagName.equals("activity")) {
            Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,
                    owner.baseHardwareAccelerated);
            if (a == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            owner.activities.add(a);
         //解析到廣播 receiver 節(jié)點
        } else if (tagName.equals("receiver")) {
            Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
                    true, false);
            if (a == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            owner.receivers.add(a);
          //解析到服務(wù) service 節(jié)點
        } else if (tagName.equals("service")) {
            Service s = parseService(owner, res, parser, flags, outError, cachedArgs);
            if (s == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            owner.services.add(s);
          //解析到ContentProvider 節(jié)點
        } else if (tagName.equals("provider")) {
            Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
            if (p == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            owner.providers.add(p);

        } else {
            if (!RIGID_PARSER) {
                Slog.w(TAG, "Unknown element under <application>: " + tagName
                        + " at " + mArchiveSourcePath + " "
                        + parser.getPositionDescription());
                XmlUtils.skipCurrentTag(parser);
                continue;
              } else {
                outError[0] = "Bad element under <application>: " + tagName;
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
              }
         }
     }
    ...省略

    return true;
}

解析過程中對應(yīng)的 tagName 與我們在 AndroidManifest.xml 中聲明一致,這也可以證明我們在 Manifest 中四大組件的聲明。

到此 attachApplication 方法工作的第一部分就算是分析完了,該部分內(nèi)容的主要工作是:通過 PMS 收集在 AndroidManifest 中注冊的 ContentProvider 信息,將其封裝成 ProviderInfo 集合。

ActivityManagerService attachApplication 方法第二部分分析:

將第一部分收集的 ProviderInfo 信息集合,作為參數(shù)遠(yuǎn)程調(diào)用 ApplicationThread 的 bindApplication 方法。此時將重新回到應(yīng)用進(jìn)程啟動類 ActivityThread 中:

ApplicationThread

ApplicationThread 是 ActivityThread 與系統(tǒng) PMS 進(jìn)程通信的橋梁,它本質(zhì)也是一個 Binder 對象。ApplicationThread bindApplication 方法:

    public final void bindApplication(String processName, ApplicationInfo appInfo,
                                      List<ProviderInfo> providers, ...省略) {

        ... 省略

        //將返回數(shù)據(jù)都封裝在AppBindData中
        AppBindData data = new AppBindData();
        data.processName = processName;
        data.appInfo = appInfo;
        //這是我們要跟蹤的ContentProvider集合
        data.providers = providers;
        data.instrumentationName = instrumentationName;
        data.instrumentationArgs = instrumentationArgs;
        data.instrumentationWatcher = instrumentationWatcher;
        data.instrumentationUiAutomationConnection = instrumentationUiConnection;
        data.debugMode = debugMode;
        data.enableBinderTracking = enableBinderTracking;
        data.trackAllocation = trackAllocation;
        data.restrictedBackupMode = isRestrictedBackupMode;
        data.persistent = persistent;
        data.config = config;
        data.compatInfo = compatInfo;
        data.initProfilerInfo = profilerInfo;
        data.buildSerial = buildSerial;
        data.autofillCompatibilityEnabled = autofillCompatibilityEnabled;
        //發(fā)送BIND_APPLICATION消息到主線程Handler
        sendMessage(H.BIND_APPLICATION, data);
    }

在 bindApplication 方法發(fā)送 BIND_APPLICATION 消息到當(dāng)前進(jìn)程的主線程 Handler 中。

小結(jié)

在分析 ContentProvider 的收集過程中,驗證了自定義 ContentProvider 必須在 AndroidManifest.xml 注冊,并且驗證了 Activity、Service、BroadcastReceiver
在 Manifest 中的注冊過程。


回到 ActivityThread

在 ApplicationThread 的 bindApplication 方法發(fā)送消息到主線程,此時來到 ActivityThread Handler 的 handleMessage 方法,先看下在 ActivityThread 中的聲明:

class H extends Handler {
    public static final int BIND_APPLICATION        = 110;
    public static final int EXIT_APPLICATION        = 111;
    public static final int RECEIVER                = 113;
    public static final int CREATE_SERVICE          = 114;
    public static final int SERVICE_ARGS            = 115;
    public static final int STOP_SERVICE            = 116;

    // 省略

    public void handleMessage(Message msg) {
        switch (msg.what) {
            //通過 ApplicationThread 發(fā)送的 BIND_APPLICATION
            case BIND_APPLICATION:
                AppBindData data = (AppBindData) msg.obj;
                //調(diào)用 handleBindApplication 開始真正創(chuàng)建 Application
                handleBindApplication(data);
                break;
                
                ...省略
        }
    }
}

不知道大家有沒有注意到,到現(xiàn)在為止我們應(yīng)用的 Application 對象還沒有被創(chuàng)建。先來跟蹤下 handleBindApplication 方法(這個方法超級長,可能是 ActivityThread 中最長的一個方法)。雖然該方法體較長,但是我們只分析主線部分也可以將它劃分成兩個部分:

  • 遍歷 PMS 收集到的所有 ContentProvider 集合信息(ProviderInfo),并創(chuàng)建所有 ContentProvider 實例。回調(diào)其 onCreate 方法。

  • 創(chuàng)建當(dāng)前進(jìn)程的 Application 對象,首先回調(diào)其 attach 方法,這步發(fā)生在遍歷 ContentProvider 集合之前,創(chuàng)建每個 ContentProvider 并回調(diào)其 onCreate 方法之后,回調(diào) Application 的 onCreate。

handleBindApplication 方法實現(xiàn)如下:

    private void handleBindApplication(AppBindData data) {
        ...省略
        
         /**
         * 這里創(chuàng)建了Application對象,并回調(diào)其attach()
         */
        app = data.info.makeApplication(data.restrictedBackupMode, null);
        
        if (!data.restrictedBackupMode) {
            //判斷Provider集合信息不為空
            if (!ArrayUtils.isEmpty(data.providers)) {
                /**
                 * 創(chuàng)建所有在Manifest中注冊的Provider,并回調(diào)onCreate
                 * */
                installContentProviders(app, data.providers);
                mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10 * 1000);
            }
        }
        
        ...省略
        
        /**
        * 回調(diào)Application的onCreate
         * */
        mInstrumentation.callApplicationOnCreate(app);
        
        ... 省略
    }

先來看下 Application 的創(chuàng)建過程,makeApplication 方法:

public Application makeApplication(boolean forceDefaultAppClass,
    Instrumentation instrumentation) {

... 省略

  try {
      java.lang.ClassLoader cl = getClassLoader();
      if (!mPackageName.equals("android")) {
          Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                  "initializeJavaContextClassLoader");
          initializeJavaContextClassLoader();
          Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
      }
      //創(chuàng)建ContextImpl將其關(guān)聯(lián)到Application
      ContextImpl appContext =     ContextImpl.createAppContext(mActivityThread, this);
      //通過Instrumentation創(chuàng)建Application 并回調(diào)其attach方法
      app = mActivityThread.mInstrumentation.newApplication(
              cl, appClass, appContext);
      appContext.setOuterContext(app);
  } catch (Exception e) {
      ... 省略
  }

  ... 省略

  return app;
}

通過 Instrumentation 的 newApplication 方法完成創(chuàng)建:

public Application newApplication(ClassLoader cl, String className, Context context)
        throws InstantiationException, IllegalAccessException, 
        ClassNotFoundException {
    //通過反射創(chuàng)建Application
    Application app = getFactory(context.getPackageName())
            .instantiateApplication(cl, className);
    //調(diào)用Application的attach
    app.attach(context);
    return app;
}

此時應(yīng)用進(jìn)程的 Application 對象才被創(chuàng)建,但是生命周期 onCreate 方法并沒有被回調(diào)。重新回到 handBindApplication 方法,看下 Provider 的創(chuàng)建過程。

installContentProviders 方法如下:
private void installContentProviders(
        Context context, List<ProviderInfo> providers) {
    final ArrayList<ContentProviderHolder> results = new ArrayList<>();

    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());
        }
        //遍歷創(chuàng)建所有的ContentProvider
        ContentProviderHolder cph = installProvider(context, null, cpi,
                false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
        if (cph != null) {
            cph.noReleaseNeeded = true;
            results.add(cph);
        }
    }
    // 省略
}

這里我們重點跟蹤 installProvider 方法如下:

private ContentProviderHolder installProvider(Context context,
                                              ContentProviderHolder holder, ProviderInfo info,
                                              boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = null;
    //這是一個Binder對象,實際類型是ContentProviderProxy
    IContentProvider provider;
    if (holder == null || holder.provider == null) {
        // 方法參數(shù)中holder傳遞進(jìn)來的就是null,故會走該邏輯
        //...省略
        Context c = null;
        ApplicationInfo ai = info.applicationInfo;
        //根據(jù)包名判斷該ContentProvider是否屬于當(dāng)前應(yīng)用
        //該部分if-else邏輯主要為獲得一個合適的Context,然后通過該Context獲得ClassLoader
        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) {
            }
        }
        if (c == null) {
            //如果獲取不到上下文,直接return。
            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;
            }

            //通過類加載器加載, 反射創(chuàng)建 Class.newInstance() ContentProvider實例
            localProvider = packageInfo.getAppFactory()
                    .instantiateProvider(cl, info.name);
            //這里得到是當(dāng)前ContentProvider的Binder對象,它的實際類型是ContentProviderProxy
            provider = localProvider.getIContentProvider();

            if (provider == null) {
                Slog.e(TAG, "Failed to instantiate class " +
                        info.name + " from sourceDir " +
                        info.applicationInfo.sourceDir);
                //如果未能正確初始化該ContentProvider的Binder,直接return。
                return null;
            }
            if (DEBUG_PROVIDER) Slog.v(
                    TAG, "Instantiating local provider " + info.name);

            //ContentProvider的attachInfo中會回調(diào)ContentProvider的onCreate()方法
            //在attachInfo中毀掉了ContentProvider的onCreate()方法。
            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);
    }

    //...省略
    
    return retHolder;
}

由于其方法參數(shù) holder 傳遞為 null,故會走 holder == null 的邏輯:

    packageInfo.getAppFactory().instantiateProvider(cl, info.name)

在該方法內(nèi)實際通過 ClassLoader 加載,并反射 newInstance 創(chuàng)建該 ContentProvider 實例。

 public @NonNull ContentProvider instantiateProvider(@NonNull ClassLoader cl,
        @NonNull String className)
        throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    //通過類加載加載,并反射創(chuàng)建該Provider實例
    return (ContentProvider) cl.loadClass(className).newInstance();
}

回到 installProvider 方法,創(chuàng)建并返回 ContentProvider 實例后會調(diào)用它的 attachInfo 方法:

private void attachInfo(Context context, ProviderInfo info, boolean testing) {
    mNoPerms = testing;
    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);
        }
        //這里回調(diào)了ContentProvider的onCreate方法
        ContentProvider.this.onCreate();
    }
}

首次創(chuàng)建 ConentProvider 時,該 mContext 變量肯定是為 null 。在方法的最后回調(diào)了 onCreate 方法,此時我們自定義的 ContextProvider 的 onCreate 就會被回調(diào)。

這里我們也得到驗證,自定義 ContentProvider 的創(chuàng)建過程以及生命周期默認(rèn)在 Application 的 onCreate 方法之前

重新回到 handleBindApplication 方法,Application 的生命周期還沒有回調(diào)呢!

/**
  * 回調(diào)Application的onCreate
  * */
mInstrumentation.callApplicationOnCreate(app);

知道此刻,我們的 Application 才算是真正創(chuàng)建完成。

handleBindApplication 方法中首先創(chuàng)建了當(dāng)前進(jìn)程的 Applicaiton 對象,并沒有立即回調(diào)其 onCreate 方法,而是創(chuàng)建所有在 Manifest 注冊的 ContentProvider 對象,并回調(diào)其生命周期 onCreate 完成之后,才重新回調(diào)Application 的 onCreate 方法。


總結(jié)

大家是否注意到 ContentProvider 加載和創(chuàng)建都是在主線程完成,并且還都是在應(yīng)用啟動過程完成,ContentProvider 的生命周期默認(rèn)在 Application onCreate 之前。這也驗證了文章開頭為大家介紹的啟動性能,在使用 ContentProvider 需要注意的“暗坑”,自定義 ContentProvider 類的構(gòu)造函數(shù)、靜態(tài)代碼塊、onCreate 函數(shù)都盡量不要做耗時的操作,會拖慢啟動速度


以上便是個人在學(xué)習(xí) ContentProvider 啟動性能的心得和體會,文中如有不妥或有更好的分析結(jié)果,歡迎大家指出!

文章如果對你有幫助,就請留個贊吧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 先給出一個需要注意的點:ContentProvider的onCreate方法比Application的onCrea...
    瀟風(fēng)寒月閱讀 539評論 0 1
  • ContentProvider是內(nèi)容提供者,對外提供數(shù)據(jù)。內(nèi)部運行依賴Binde機制。想要自己寫一個Content...
    sososeen09閱讀 1,348評論 0 3
  • 久違的晴天,家長會。 家長大會開好到教室時,離放學(xué)已經(jīng)沒多少時間了。班主任說已經(jīng)安排了三個家長分享經(jīng)驗。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,557評論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,607評論 0 11
  • 可愛進(jìn)取,孤獨成精。努力飛翔,天堂翱翔。戰(zhàn)爭美好,孤獨進(jìn)取。膽大飛翔,成就輝煌。努力進(jìn)取,遙望,和諧家園。可愛游走...
    趙原野閱讀 2,767評論 1 1