網站首頁 編程語言 正文
項目描述
先簡單描述一下遇到的問題。
項目比較龐大是以組件化的形式進行構建的,記錄崩潰日志是由專門的一個組件去做,這里且叫它crash吧。而crash的核心邏輯如下:
//偽代碼
public class MyCrash implements UncaughtExceptionHandler {
private static UncaughtExceptionHandler defaultUncaughtExceptionHandler;
public static void init(String path) {
...
//獲取到默認的ExceptionHandler
defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
//設置自己的ExceptionHandler
Thread.setDefaultUncaughtExceptionHandler(new MyCrash());
}
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
//日志記錄邏輯
} catch (IOException e) {
e.printStackTrace();
} finally {
//回調默認的ExceptionHandler
if (defaultUncaughtExceptionHandler != null) {
defaultUncaughtExceptionHandler.uncaughtException(t, e);
}
}
}
}
然后該組件利用ContentProvider進行初始化,大概如下所示:
class MyContentProvider : ContentProvider() {
override fun onCreate(): Boolean {
//偽代碼,初始化。
MyCrash.init("")
return true
}
......
}
問題排查
收到反饋說,部分手機不會記錄崩潰日志。這就是很奇怪了,因為理論上來說,只要設置了ExceptionHandle都會捕獲到傳過來的異常呀。難道是沒有設置到ExceptionHandle?
后經過斷點排查,不會上傳崩潰日志的手機,在運行階段Thread持有的defaultUncaughtExceptionHandler,不是我們設置的MyCrash,而是一個三方組件設置他們自己的CrashExceptionHandle且沒有回調我們的MyCrash,而他們也是利用ContentProvider初始化的。
所以這時候就牽扯到ContentProvider的初始化流程了,具體在ActivityThread中,下面放一下偽代碼。
ActivityThread
private void handleBindApplication(AppBindData data) {
...
//1.獲取到Application
app = data.info.makeApplication(data.restrictedBackupMode, null);
...
//2.初始化ContentProvider
installContentProviders(app, data.providers);
...
//3.調用Application的onCreate
mInstrumentation.callApplicationOnCreate(app);
...
}
整體順序是獲取Application->初始化ContentProvider->調用Application#onCreate。也就是說ContentProvider的初始化是要在Application之前的。其中ContentProvider的初始化就是循環便利儲存ContentProvider的集合調用它的onCreate方法。
private void installContentProviders(Context context, List<ProviderInfo> providers) {
...
for (ProviderInfo cpi : providers) {
//這里會獲取到ContentProvider,最終會調用到ContentProvider的attachInfo,在attachInfo中調用了onCreate
}
...
}
那ContentProvider的初始化順序就很清晰明了了。而我們的問題是部分手機記錄不了,也就是說ContentProvider在集合中的順序是不可保證的,這樣才能解釋部分手機有問題,部分手機正常,那這個順序是怎么來的呢?
起初我想到順序是不是和合并后的AndroidManifest.xml文件里面注冊的ContentProvider節點順序有關系,隨后就將該想法排除,因為生成的APK是一樣的,所以AndroidManifest.xml文件里面注冊的ContentProvider節點順序是一定的。而有問題的是部分手機,所以一定不是這里的問題,沒辦法只能繼續查看源碼,看看ContentProvider究竟是如何讀到內存中的。
經過一番查找,發現ContentProvider的集合是從ComponentResolver中private final ArrayMap<ComponentName, ParsedProvider> mProviders = new ArrayMap<>();取得。由于涉及到得源碼比較多,這里就不一一列舉了,下面放上源碼大致的調用鏈
ActivityManagerService#attachApplicationLocked -> ActivityManagerService#generateApplicationProvidersLocked -> PackageManagerService#queryContentProviders -> ComponentResolver#queryProviders -> ActivityThread#bindApplication -> ActivityThread#handleBindApplication
我們注意一下重點,ContentProvider得信息是被儲存在ArrayMap中得,而ArrayMap肯定是無法保證順序的呀。不了解ArrayMap的下面我簡單介紹一下,
ArrayMap是Google專門提供的key-value映射集合,主要為了解決HashMap浪費控件的問題,在小數據量上性能不錯,但是它底層是用數組來著,利用二分查找,而二分查找的順序是根據hash值來的,默認的hash值是通過System.identityHashCode(key)來進行獲取的,而這玩意兒又和對象的地址有關系。所以不同的手機順序肯定就不一樣了。
到這里,問題就分析結束了,最終解決方案是,去除了利用ContentProvider的初始化機制,改在Application中直接進行初始化。
總結
上面的問題雖然解決了,但是利用ContentProvider解耦初始化組件真的好嗎?直觀的有以下幾個問題。
- 內存泄漏。初始化完成之后ContentProvider會被系統直接持有,無用,但也不刪除
- 無法保證組件初始化的順序。這個就是我們上面分析的問題
- 會拉長啟動時間。上面我們看到了,ContentProvider循環初始化完成之后,才會進行Application#onCreate的調用,所以對于一些非必要在主線程初始化的組件,這無疑會拉長啟動時間。
不過如果非要去解耦組件初始化,可以看一看Jetpack startup組件,它也是利用ContentProvider去初始化的,但是它利用AndroidManifest.xml合并的功能最終會合并成一個ContentProvider,而且內存維持有集合可以保證組件初始化順序。
總之一句話,不要濫用ContentProvider僅僅去做一個初始化。
原文鏈接:https://blog.csdn.net/weixin_44235109/article/details/124228437
相關推薦
- 2024-07-15 SpringBoot使用Apache Poi導出word文檔
- 2022-07-21 shardingjdbc+mybatisP+Seata 報錯 throw new ShouldNev
- 2023-10-14 C/C++ 批量梯度下降法實現一元線性回歸
- 2022-10-28 Go語言開發保證并發安全實例詳解_Golang
- 2022-08-23 Python中更優雅的日志記錄方案詳解_python
- 2022-10-10 NumPy?數組屬性的具體使用_python
- 2022-11-17 python數學模塊(math/decimal模塊)_python
- 2023-03-11 Tensorflow的DataSet的使用詳解_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支