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ù)丟失》兩篇文章中,詳細分析了 SharedPreferences 的實現(xiàn)機制。簡單回顧下芽隆,Android 系統(tǒng)為什么不把 SharedPreferences 設(shè)計成跨進程安全的呢咆蒿?那是因為 Android 系統(tǒng)更希望我們在這個場景使用 ContentProvider 作為存儲方式。

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

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


ContentProvider 的使用

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

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

  • 啟動性能

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

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

ContentProvider 在進行跨進程數(shù)據(jù)傳遞時应媚,利用了 Android 的 Binder 和匿名共享內(nèi)存機制严沥。簡單來說,就是通過 Binder 傳遞 CursorWindow 對象內(nèi)部的匿名共享內(nèi)存的文件描述符中姜。這樣在跨進程傳輸中消玄,結(jié)果數(shù)據(jù)并不需要跨進程傳輸,而是在不同進程中通過傳輸?shù)哪涿蚕韮?nèi)存文件描述符來操作同一塊匿名內(nèi)存丢胚,這樣來實現(xià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)定性和安全性放到系列后面文章進行介紹鸟妙。還是從源碼的角度出發(fā)焦人,分析自定義 ContentProvider 的創(chuàng)建過程和生命周期回調(diào)過程。


自定義 ContentProvider 的啟動過程分析

我們要從應(yīng)用程序啟動類 ActivityThread 開始重父,關(guān)于 ActivityThread 大家肯定不會感到陌生花椭,它是我們應(yīng)用進程的入口類,也就是 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)遠程調(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 {
            //然后通過代理類完成遠程(跨進程)調(diào)用
            //mAppThread是當(dāng)前進程的ApplicationThread實例
           //在完成跨進程條用完成之后要通過該對象回到當(dāng)前進程
            mgr.attachApplication(mAppThread, startSeq);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
}

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

ActivityManagerService
ActivityManagerSerivce 的 attachApplication 方法分析

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

  1. generateApplicationProvidersLocked 方法承冰,通過 PMS 完成 ContentProvider 注冊信息(ProviderInfo)的收集工作华弓。
  2. 將收集 Providers 的信息集合,作為參數(shù)遠程調(diào)用 ApplicationThread 的 bindApplication 方法困乒。此時將重新回到應(yīng)用進程啟動類 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ù)遠程調(diào)用 ApplicationThread 的 bindApplication 方法。此時將重新回到應(yīng)用進程啟動類 ActivityThread 中:

ApplicationThread

ApplicationThread 是 ActivityThread 與系統(tǒng) PMS 進程通信的橋梁俄占,它本質(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)前進程的主線程 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)前進程的 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)用進程的 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傳遞進來的就是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)建過程以及生命周期默認在 Application 的 onCreate 方法之前刁憋。

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

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

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

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


總結(jié)

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


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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末粹污,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子首量,更是在濱河造成了極大的恐慌壮吩,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件加缘,死亡現(xiàn)場離奇詭異鸭叙,居然都是意外死亡,警方通過查閱死者的電腦和手機拣宏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門沈贝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人勋乾,你說我怎么就攤上這事宋下。” “怎么了辑莫?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵学歧,是天一觀的道長。 經(jīng)常有香客問我各吨,道長撩满,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任绅你,我火速辦了婚禮伺帘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘忌锯。我一直安慰自己伪嫁,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布偶垮。 她就那樣靜靜地躺著张咳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪似舵。 梳的紋絲不亂的頭發(fā)上脚猾,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音砚哗,去河邊找鬼龙助。 笑死,一個胖子當(dāng)著我的面吹牛蛛芥,可吹牛的內(nèi)容都是我干的提鸟。 我是一名探鬼主播军援,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼称勋!你這毒婦竟也來了胸哥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤赡鲜,失蹤者是張志新(化名)和其女友劉穎空厌,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體银酬,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡嘲更,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了捡硅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哮内。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖壮韭,靈堂內(nèi)的尸體忽然破棺而出北发,到底是詐尸還是另有隱情,我是刑警寧澤喷屋,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布琳拨,位于F島的核電站,受9級特大地震影響屯曹,放射性物質(zhì)發(fā)生泄漏狱庇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一恶耽、第九天 我趴在偏房一處隱蔽的房頂上張望密任。 院中可真熱鬧,春花似錦偷俭、人聲如沸浪讳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淹遵。三九已至,卻和暖如春负溪,著一層夾襖步出監(jiān)牢的瞬間透揣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工川抡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辐真,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像拆祈,于是被迫代替她去往敵國和親恨闪。 傳聞我的和親對象是個殘疾皇子倘感,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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

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