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ù)都盡量不要做耗時的操作割以,會拖慢啟動速度。
- 穩(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ù)的目的
基于 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)容,這里通過一張流程圖簡單說下該過程扇售。
ActivityManagerSerivce 的 attachApplication 方法分析
前面有簡單提到通過 AMS 遠程調(diào)用該方法前塔,在該方法內(nèi)開始收集注冊在 AndroidManifest.xml 中的 ContentProvider 信息,實際上該方法主要完成兩部分工作:
- generateApplicationProvidersLocked 方法承冰,通過 PMS 完成 ContentProvider 注冊信息(ProviderInfo)的收集工作华弓。
- 將收集 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 是 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é)果足绅,歡迎大家指出捷绑!
文章如果對你有幫助,就請留個贊吧氢妈!