Android利用ContentProvider初始化組件的踩坑記錄
項(xiàng)目描述
先簡單描述一下遇到的問題。
項(xiàng)目比較龐大是以組件化的形式進(jìn)行構(gòu)建的,記錄崩潰日志是由專門的一個(gè)組件去做,這里且叫它c(diǎn)rash吧。而crash的核心邏輯如下:
//偽代碼
public class MyCrash implements UncaughtExceptionHandler {
private static UncaughtExceptionHandler defaultUncaughtExceptionHandler;
public static void init(String path) {
...
//獲取到默認(rèn)的ExceptionHandler
defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
//設(shè)置自己的ExceptionHandler
Thread.setDefaultUncaughtExceptionHandler(new MyCrash());
}
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
//日志記錄邏輯
} catch (IOException e) {
e.printStackTrace();
} finally {
//回調(diào)默認(rèn)的ExceptionHandler
if (defaultUncaughtExceptionHandler != null) {
defaultUncaughtExceptionHandler.uncaughtException(t, e);
}
}
}
}
然后該組件利用ContentProvider進(jìn)行初始化,大概如下所示:
class MyContentProvider : ContentProvider() {
override fun onCreate(): Boolean {
//偽代碼,初始化。
MyCrash.init("")
return true
}
......
}
問題排查
收到反饋說,部分手機(jī)不會(huì)記錄崩潰日志。這就是很奇怪了,因?yàn)槔碚撋蟻碚f,只要設(shè)置了ExceptionHandle都會(huì)捕獲到傳過來的異常呀。難道是沒有設(shè)置到ExceptionHandle?
后經(jīng)過斷點(diǎn)排查,不會(huì)上傳崩潰日志的手機(jī),在運(yùn)行階段Thread持有的defaultUncaughtExceptionHandler,不是我們設(shè)置的MyCrash,而是一個(gè)三方組件設(shè)置他們自己的CrashExceptionHandle且沒有回調(diào)我們的MyCrash,而他們也是利用ContentProvider初始化的。
所以這時(shí)候就牽扯到ContentProvider的初始化流程了,具體在ActivityThread中,下面放一下偽代碼。
ActivityThread
private void handleBindApplication(AppBindData data) {
...
//1.獲取到Application
app = data.info.makeApplication(data.restrictedBackupMode, null);
...
//2.初始化ContentProvider
installContentProviders(app, data.providers);
...
//3.調(diào)用Application的onCreate
mInstrumentation.callApplicationOnCreate(app);
...
}
整體順序是獲取Application->初始化ContentProvider->調(diào)用Application#onCreate。也就是說ContentProvider的初始化是要在Application之前的。其中ContentProvider的初始化就是循環(huán)便利儲(chǔ)存ContentProvider的集合調(diào)用它的onCreate方法。
private void installContentProviders(Context context, List<ProviderInfo> providers) {
...
for (ProviderInfo cpi : providers) {
//這里會(huì)獲取到ContentProvider,最終會(huì)調(diào)用到ContentProvider的attachInfo,在attachInfo中調(diào)用了onCreate
}
...
}
那ContentProvider的初始化順序就很清晰明了了。而我們的問題是部分手機(jī)記錄不了,也就是說ContentProvider在集合中的順序是不可保證的,這樣才能解釋部分手機(jī)有問題,部分手機(jī)正常,那這個(gè)順序是怎么來的呢?
起初我想到順序是不是和合并后的AndroidManifest.xml文件里面注冊的ContentProvider節(jié)點(diǎn)順序有關(guān)系,隨后就將該想法排除,因?yàn)樯傻腁PK是一樣的,所以AndroidManifest.xml文件里面注冊的ContentProvider節(jié)點(diǎn)順序是一定的。而有問題的是部分手機(jī),所以一定不是這里的問題,沒辦法只能繼續(xù)查看源碼,看看ContentProvider究竟是如何讀到內(nèi)存中的。
經(jīng)過一番查找,發(fā)現(xiàn)ContentProvider的集合是從ComponentResolver中private final ArrayMap<ComponentName, ParsedProvider> mProviders = new ArrayMap<>();取得。由于涉及到得源碼比較多,這里就不一一列舉了,下面放上源碼大致的調(diào)用鏈
ActivityManagerService#attachApplicationLocked -> ActivityManagerService#generateApplicationProvidersLocked -> PackageManagerService#queryContentProviders -> ComponentResolver#queryProviders -> ActivityThread#bindApplication -> ActivityThread#handleBindApplication
我們注意一下重點(diǎn),ContentProvider得信息是被儲(chǔ)存在ArrayMap中得,而ArrayMap肯定是無法保證順序的呀。不了解ArrayMap的下面我簡單介紹一下,
ArrayMap是Google專門提供的key-value映射集合,主要為了解決HashMap浪費(fèi)控件的問題,在小數(shù)據(jù)量上性能不錯(cuò),但是它底層是用數(shù)組來著,利用二分查找,而二分查找的順序是根據(jù)hash值來的,默認(rèn)的hash值是通過System.identityHashCode(key)來進(jìn)行獲取的,而這玩意兒又和對象的地址有關(guān)系。所以不同的手機(jī)順序肯定就不一樣了。
到這里,問題就分析結(jié)束了,最終解決方案是,去除了利用ContentProvider的初始化機(jī)制,改在Application中直接進(jìn)行初始化。
總結(jié)
上面的問題雖然解決了,但是利用ContentProvider解耦初始化組件真的好嗎?直觀的有以下幾個(gè)問題。
- 內(nèi)存泄漏。初始化完成之后ContentProvider會(huì)被系統(tǒng)直接持有,無用,但也不刪除
- 無法保證組件初始化的順序。這個(gè)就是我們上面分析的問題
- 會(huì)拉長啟動(dòng)時(shí)間。上面我們看到了,ContentProvider循環(huán)初始化完成之后,才會(huì)進(jìn)行Application#onCreate的調(diào)用,所以對于一些非必要在主線程初始化的組件,這無疑會(huì)拉長啟動(dòng)時(shí)間。
不過如果非要去解耦組件初始化,可以看一看Jetpack startup組件,它也是利用ContentProvider去初始化的,但是它利用AndroidManifest.xml合并的功能最終會(huì)合并成一個(gè)ContentProvider,而且內(nèi)存維持有集合可以保證組件初始化順序。
總之一句話,不要濫用ContentProvider僅僅去做一個(gè)初始化。
到此這篇關(guān)于Android利用ContentProvider初始化組件踩坑的文章就介紹到這了,更多相關(guān)Android ContentProvider初始化組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android數(shù)據(jù)持久化之SQLite數(shù)據(jù)庫用法分析
這篇文章主要介紹了Android數(shù)據(jù)持久化之SQLite數(shù)據(jù)庫用法,結(jié)合實(shí)例形式分析了SQLite概念、功能、相關(guān)操作類與使用技巧,需要的朋友可以參考下2017-05-05
為Android系統(tǒng)添加config.xml 新配置的設(shè)置
這篇文章主要介紹了為Android系統(tǒng)添加config.xml 新配置的設(shè)置,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03
基于linux與windows平臺(tái)下 如何下載android sdk源代碼的方法詳解
本文主要是介紹在linux和windows平臺(tái)下,如何下載android sdk的源代碼,注意是sdk的源代碼,而不是android的所有源代碼,同時(shí)介紹如何把sdk源代碼加入到eclipse里,使android 平臺(tái)手機(jī)開發(fā)者可以直接查看源代碼,通過閱讀SDK源碼,能更好的理解和運(yùn)用Android的API2013-05-05
android scrollview 滑動(dòng)到頂端或者指定位置的實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄猘ndroid scrollview 滑動(dòng)到頂端或者指定位置的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04
Android實(shí)現(xiàn)Flip翻轉(zhuǎn)動(dòng)畫效果
這篇文章主要介紹了Android實(shí)現(xiàn)Flip翻轉(zhuǎn)動(dòng)畫效果,對Android程序設(shè)計(jì)人員有很好的參考借鑒價(jià)值,需要的朋友可以參考下2014-08-08
解決android Listview的item中最外層Margin失效的問題
下面小編就為大家?guī)硪黄鉀Qandroid Listview的item中最外層Margin失效的問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04
Android實(shí)現(xiàn)為GridView添加邊框效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)為GridView添加邊框效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Android實(shí)現(xiàn)列表元素動(dòng)態(tài)效果
本文將利用AnimatedList組件實(shí)現(xiàn)列表元素的一些動(dòng)態(tài)效果,例如添加元素時(shí)的漸現(xiàn)效果,刪除元素逐漸消失的效果等,感興趣的小伙伴可以了解一下2022-03-03

