網站首頁 編程語言 正文
前言
ANR(Application Not Response)應用程序未響應,當主線程被阻塞時,就會彈出如下彈窗
要么關閉當前app,要么就等待,其實這個時候沒有挽救的措施,選擇等待最終的結果也是ANR,最終都需要殺掉應用進程,我們看下日志,原因是Input dispatching timed out,點擊事件處理超時導致ANR。
2022-08-27 16:11:53.168 2057-2080/system_process E/ActivityManager: ANR in com.lay.image_process (com.lay.image_process/.MainActivity)
PID: 31848
Reason: Input dispatching timed out (Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago. Wait queue length: 6. Wait queue head age: 5525.3ms.)
其實相對于其他的錯誤,ANR比較棘手在于,沒有崩潰日志,定位問題比較困難,而且ANR是必須要解決的問題,這個用戶體驗極差,因此本章的核心在于攻堅ANR問題。
1 ANR原因總結
從上面的日志中,我們看到造成ANR的原因是Input dispatching timed out,那么除此之外,還有什么其他的錯誤。
1.1 KeyDispatchTimeout
input事件在5s之內沒有處理,產生ANR;這種異常是比較常見的問題,常發生在點擊事件處理中,logcat的關鍵字就是Input dispatching timed out
這里需要說明一點,Input事件導致ANR跟下面幾種不同的是,看下面的代碼,點擊按鈕5s后,才彈出toast,這種情況下會發生ANR嗎?
btnSend.setOnClickListener {
Thread.sleep(5 * 1000)
ToastUtils.setText(this)
}
我們可以在私下測試一下,其實是不會的,如果用戶后續沒有繼續輸入事件,那么就不會產生ANR
1.2 BroadCastTimeout
class MyBroadCast : BroadcastReceiver(){
override fun onReceive(context: Context?, intent: Intent?) {
//TODO 接收廣播
}
}
我們在使用廣播接收器接收廣播時,需要重寫BroadcastReceiver的onReceive方法,當前方法是在主線程中,如果在10s內沒有處理彎沉,就會ANR。
因此,在onReceive方法中不能做耗時操作,如果需要則需要創建新的線程。logcat關鍵字是?Timeout of broadcast BroadcastRecord
1.3 ServiceTimeout
class MyService : Service(){
override fun onCreate() {
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return super.onStartCommand(intent, flags, startId)
}
override fun onBind(intent: Intent?): IBinder? {
TODO("Not yet implemented")
}
}
同樣,在Service中依然不能做耗時操作,如onCreate、onStartCommand、onBind方法中如果超過20s沒有處理完成,就會ANR。
所以如果需要在服務中執行耗時操作,建議使用IntentService,logcat關鍵字是?Timeout executing service
1.4 ContentProviderTimeout
四大組件最后一個組件,如果ContentProvider在10內沒有處理就會導致ANR,這個組件使用很少,暫時先不分析
綜上所述,如果出現ANR,主要原因就是在主線程執行了耗時操作,導致UI線程被阻塞發生ANR;那么在我們的實際項目中,有哪些操作,可能會導致ANR呢?
1. 主線程進行頻繁的IO操作
不知道我們還有多少在使用SP存儲的,其實它底層就是通過IO讀寫操作文件,如果頻繁地在主線程進行SP讀寫可能會造成卡頓或者ANR,之前就有過線上的ANR事故,建議大家都是用MMKV,讀寫速度秒殺SP;
除此之外,主線程進行網絡操作也會導致ANR
2. 多線程爭奪資源導致死鎖3. CPU資源耗盡
等等......
2 ANR問題解決
2.1 線下問題解決
如果在我們實際的開發過程中,如果出現ANR,那么很簡單,打開logcat窗口就可以查看,還有一種方式,就是查看trace日志,路徑為 /data/anr/xxx
?打開trace日志,通過這一部分就能猜到具體原因了,就是因為在主線程中,響應點擊事件時,線程進入休眠阻塞
線下出問題對于我們來說永遠都是最簡單的,難的就是在線上出了問題,用戶隔你十萬八千里,該如何處理?
2.2 線上問題解決
2.2.1 Bugly
可能很多小伙伴的項目中都集成了bugly,確實bugly是很不錯的線上監控組件,像Crash、ANR都能夠檢測到,但是很多時候,日志是不全的,堆棧信息不全就沒法定位問題,bugly可以作為兜底方案,具體的監控方案,我們可以自己實現。
2.2.2 FileObserver
對于線上監控,往往有兩種方式,我這邊先講解第一種,通過FileObserver監聽某個目錄下文件是否發生變化,這里不言而喻了,就是/data/anr/xxx,如果當前文件夾中的文件發生變化,那么意味著ANR發生了,首先我們先了解一個這個類。
@Deprecated
public FileObserver(String path) {
this(new File(path));
}
/**
* Equivalent to FileObserver(file, FileObserver.ALL_EVENTS).
*/
public FileObserver(@NonNull File file) {
this(Arrays.asList(file));
}
/**
* Equivalent to FileObserver(paths, FileObserver.ALL_EVENTS).
*
* @param files The files or directories to monitor
*/
public FileObserver(@NonNull List<File> files) {
this(files, ALL_EVENTS);
}
我們先看一下其中比較核心的構造方法,FileObserver能夠監聽某個路徑下的文件、某個文件或者文件集合的變化,FileObserver是一個抽象類,那么我們可以實現它來監聽anr目錄文件的變化
@IntDef(flag = true, value = {
ACCESS,
MODIFY,
ATTRIB,
CLOSE_WRITE,
CLOSE_NOWRITE,
OPEN,
MOVED_FROM,
MOVED_TO,
CREATE,
DELETE,
DELETE_SELF,
MOVE_SELF
})
具體的文件狀態有以上這些,包括ACCESS(當前文件被訪問了)、MODIFY(當前文件被修改了)、CREATE(當前文件被創建了)、DELETE(當前文件被刪除了)等等
class ANRFileObserver(
val anrPath: String
) : FileObserver(anrPath) {
override fun onEvent(event: Int, path: String?) {
when(event){
ACCESS->{
}
MODIFY->{
}
DELETE->{
}
CREATE->{
}
}
}
}
這里主要是檢測這4種狀態,當前文件夾下內容有修改時,就將全部的trace文件上傳到服務端進行日志查看。
val observer = ANRFileObserver("/data/anr/")
observer.startWatching()
但是這里需要注意的就是,很多高版本的ROM已經不支持當前文件夾的查看,甚至需要Root,因此此策略暫時不能應用,那么除此之外,還可以通過WatchDog來監控線程狀態,從而判斷是否發生ANR。
2.2.3 WatchDog
從字面意思上看,就是看門狗,其實這個是Android系統中的一種監控機制,當SystemServer進程啟動,調用start方法之后,WatchDog也就啟動了run方法
從上面這張圖可以理解WatchDog的原理:首先WatchDog是一個線程,每隔5s發送一個Message消息到主線程的MessageQueue中,主線程Looper從消息隊列中取出Message,如果沒有阻塞,那么在5s內會執行這個Message任務,就沒有ANR;如果超過5s沒有執行,那么就有可能出現ANR。
原文鏈接:https://juejin.cn/post/7136534172096528398
相關推薦
- 2022-05-10 FactoryBean配置文件定義的 類型 調用時返回 不同的類型
- 2022-11-12 Python?sklearn分類決策樹方法詳解_python
- 2022-05-22 利用DOSBox運行匯編的詳細步驟_匯編語言
- 2023-05-08 Python實現統計文章閱讀量的方法詳解_python
- 2022-03-27 .NET?Core利用動態代理實現AOP(面向切面編程)_實用技巧
- 2022-07-07 Asp.Net上傳文件并配置可上傳大文件的方法_基礎應用
- 2023-07-13 react 非授控組件和授控組件的區別
- 2022-12-23 React使用Echarts/Ant-design-charts的案例代碼_React
- 最近更新
-
- 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同步修改后的遠程分支