APP卡主很久没响应如何排查?(/data/anr/下有对应日志)

Android App优化:理解与避免ANR
本文详细介绍了Android中的ANR(Application Not Responding)现象,包括ANR的定义、产生原因和避免方法。ANR通常发生在主线程执行耗时操作,如文件读写、数据库操作等,导致UI无法响应用户输入。解决ANR的关键在于将这些操作移到子线程中执行,可以使用Thread、AsyncTask、HandlerThread、IntentService或Loader等方式。同时,分析traces.txt文件能帮助定位ANR发生的具体位置。

Android App 优化之ANR详解 

2018-08-15 08:40

作者:anly_jun

https://juejin.im/post/582582df0ce4630058bbbad2

本文由anly_jun原创,已获授权原创首发

1, 你碰到ANR了吗

在App使用过程中, 你可能遇到过这样的情况:

恭喜你, 这就是传说中的ANR。

1.1 何为ANR

ANR全名Application Not Responding, 也就是"应用无响应". 当操作在一段时间内系统无法处理时, 系统层面会弹出上图那样的ANR对话框。

1.2 为什么会产生ANR

在Android里, App的响应能力是由Activity Manager和Window Manager系统服务来监控的. 通常在如下两种情况下会弹出ANR对话框:

5s内无法响应用户输入事件(例如键盘输入, 触摸屏幕等)。

BroadcastReceiver在10s内无法结束。

造成以上两种情况的首要原因就是在主线程(UI线程)里面做了太多的阻塞耗时操作, 例如文件读写, 数据库读写, 网络查询等等。

1.3 如何避免ANR

知道了ANR产生的原因, 那么想要避免ANR, 也就很简单了, 就一条规则:

不要在主线程(UI线程)里面做繁重的操作。

这里面实际上涉及到两个问题:

1.哪些地方是运行在主线程的?

2.不在主线程做, 在哪儿做?

稍后解答。

2, ANR分析2.1 获取ANR产生的trace文件

ANR产生时, 系统会生成一个traces.txt的文件放在/data/anr/下. 可以通过adb命令将其导出到本地:

$adbpull data/anr/traces.txt

2.2 分析traces.txt

2.2.1 普通阻塞导致的ANR

获取到的tracs.txt文件一般如下:

如下以GithubApp代码为例, 强行sleep thread产生的一个ANR。

----- pid 2976at 2016-09-0823: 02: 47-----

Cmd line: com.anly.githubapp // 最新的ANR发生的进程(包名)

...

DALVIK THREADS ( 41):

"main"prio= 5tid= 1Sleeping

| group= "main"sCount= 1dsCount= 0obj= 0x73467fa8 self= 0x7fbf66c95000

| sysTid= 2976nice= 0cgrp= defaultsched= 0/ 0handle= 0x7fbf6a8953e0

| state=S schedstat=( 000) utm= 60stm= 37core= 1HZ= 100

| stack= 0x7ffff4ffd000 -0x7ffff4fff000 stackSize= 8MB

| held mutexes=

at java.lang.Thread.sleep!(Native method)

- sleeping on < 0x35fc9e33> (a java.lang.Object)

at java.lang.Thread.sleep(Thread.java: 1031)

- locked < 0x35fc9e33> (a java.lang.Object)

at java.lang.Thread.sleep(Thread.java: 985) // 主线程中sleep过长时间, 阻塞导致无响应.

at com.tencent.bugly.crashreport.crash.c.l(BUGLY: 258)

- locked <@addr= 0x12dadc70> (a com.tencent.bugly.crashreport.crash.c)

at com.tencent.bugly.crashreport.CrashReport.testANRCrash(BUGLY: 166) // 产生ANR的那个函数调用

- locked <@addr= 0x12d1e840> (a java.lang.Class)

at com.anly.githubapp.common.wrapper.CrashHelper.testAnr(CrashHelper.java: 23)

at com.anly.githubapp.ui.module.main.MineFragment.onClick(MineFragment.java: 80) // ANR的起点

at com.anly.githubapp.ui.module.main.MineFragment_ViewBinding$ 2.doClick(MineFragment_ViewBinding.java: 47)

at butterknife.internal.DebouncingOnClickListener.onClick(DebouncingOnClickListener.java: 22)

at android.view.View.performClick(View.java: 4780)

at android.view.View$PerformClick.run(View.java: 19866)

at android.os.Handler.handleCallback(Handler.java: 739)

at android.os.Handler.dispatchMessage(Handler.java: 95)

at android.os.Looper.loop(Looper.java: 135)

at android.app.ActivityThread.main(ActivityThread.java: 5254)

at java.lang.reflect.Method.invoke!(Native method)

at java.lang.reflect.Method.invoke(Method.java: 372)

at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java: 903)

at com.android.internal.os.ZygoteInit.main(ZygoteInit.java: 698)

拿到trace信息, 一切好说。

如上trace信息中的添加的中文注释已基本说明了trace文件该怎么分析:

文件最上的即为最新产生的ANR的trace信息。

前面两行表明ANR发生的进程pid, 时间, 以及进程名字(包名)。

寻找我们的代码点, 然后往前推, 看方法调用栈, 追溯到问题产生的根源。

以上的ANR trace是属于相对简单, 还有可能你并没有在主线程中做过于耗时的操作, 然而还是ANR了。 这就有可能是如下两种情况了:

2.2.2 CPU满负荷

这个时候你看到的trace信息可能会包含这样的信息:

Process:com.anly.githubapp

...

CPU usage from 3330ms to 814ms ago:

6% 178/system_server: 3.5% user + 1.4% kernel / faults: 86 minor 20 major

4.6% 2976/com.anly.githubapp: 0.7% user + 3.7% kernel /faults: 52 minor 19 major

0.9% 252/com.android.systemui: 0.9% user + 0% kernel

...

100%TOTAL: 5.9% user + 4.1% kernel + 89% iowait

最后一句表明了:

当是CPU占用100%, 满负荷了。

其中绝大数是被iowait即I/O操作占用了。

此时分析方法调用栈, 一般来说会发现是方法中有频繁的文件读写或是数据库读写操作放在主线程来做了。

2.2.3 内存原因

其实内存原因有可能会导致ANR, 例如如果由于内存泄露, App可使用内存所剩无几, 我们点击按钮启动一个大图片作为背景的activity, 就可能会产生ANR, 这时trace信息可能是这样的:

// 以下trace信息来自网络, 用来做个示例

Cmdline: android.process.acore

DALVIK THREADS:

"main"prio=5 tid=3 VMWAIT

|group= "main"sCount=1 dsCount=0 s=N obj=0x40026240self=0xbda8

| sysTid=1815 nice=0 sched=0/0 cgrp=unknownhandle=-1344001376

atdalvik.system.VMRuntime.trackExternalAllocation(NativeMethod)

atandroid.graphics.Bitmap.nativeCreate(Native Method)

atandroid.graphics.Bitmap.createBitmap(Bitmap.java:468)

atandroid.view.View.buildDrawingCache(View.java:6324)

atandroid.view.View.getDrawingCache(View.java:6178)

...

MEMINFO in pid 1360 [android.process.acore] **

native dalvik other total

size: 17036 23111 N/A 40147

allocated: 16484 20675 N/A 37159

free: 296 2436 N/A 2732

可以看到free的内存已所剩无几.

当然这种情况可能更多的是会产生OOM的异常…

2.2 ANR的处理

针对三种不同的情况, 一般的处理情况如下

1.主线程阻塞的

开辟单独的子线程来处理耗时阻塞事务。

2.CPU满负荷, I/O阻塞的

I/O阻塞一般来说就是文件读写或数据库操作执行在主线程了, 也可以通过开辟子线程的方式异步执行。

3.内存不够用的

增大VM内存, 使用largeHeap属性, 排查内存泄露(这个在内存优化那篇细说吧)等。

3, 深入一点

没有人愿意在出问题之后去解决问题。

高手和新手的区别是, 高手知道怎么在一开始就避免问题的发生. 那么针对ANR这个问题, 我们需要做哪些层次的工作来避免其发生呢?

3.1 哪些地方是执行在主线程的

1.Activity的所有生命周期回调都是执行在主线程的。

2.Service默认是执行在主线程的。

3.BroadcastReceiver的onReceive回调是执行在主线程的。

4.没有使用子线程的looper的Handler的handleMessage, post(Runnable)是执行在主线程的。

5.AsyncTask的回调中除了doInBackground, 其他都是执行在主线程的。

6.View的post(Runnable)是执行在主线程的。

3.2 使用子线程的方式有哪些

上面我们几乎一直在说, 避免ANR的方法就是在子线程中执行耗时阻塞操作. 那么在Android中有哪些方式可以让我们实现这一点呢。

3.2.1 启Thread方式

这个其实也是Java实现多线程的方式. 有两种实现方法, 继承Thread 或 实现Runnable接口:

继承Thread

classPrimeThreadextendsThread{

longminPrime;

PrimeThread( longminPrime) {

this.minPrime = minPrime;

}

publicvoidrun(){

// compute primes larger than minPrime

. . .

}

}

PrimeThread p = newPrimeThread( 143);

p.start();

实现Runnable接口

classPrimeRunimplementsRunnable{

longminPrime;

PrimeRun( longminPrime) {

this.minPrime = minPrime;

}

publicvoidrun(){

// compute primes larger than minPrime

. . .

}

}

PrimeRun p = newPrimeRun( 143);

newThread(p).start();

3.2.2 使用AsyncTask

这个是Android特有的方式, AsyncTask顾名思义, 就是异步任务的意思。

privateclassDownloadFilesTaskextendsAsyncTask{

// Do the long-running work in here

// 执行在子线程

protectedLong doInBackground(URL... urls){

intcount = urls.length;

longtotalSize = 0;

for( inti = 0; i < count; i++) {

totalSize += Downloader.downloadFile(urls[i]);

publishProgress(( int) ((i / ( float) count) * 100));

// Escape early if cancel() is called

if(isCancelled()) break;

}

returntotalSize;

}

// This is called each time you call publishProgress()

// 执行在主线程

protectedvoidonProgressUpdate(Integer... progress){

setProgressPercent(progress[ 0]);

}

// This is called when doInBackground() is finished

// 执行在主线程

protectedvoidonPostExecute(Long result){

showNotification( "Downloaded "+ result + " bytes");

}

}

// 启动方式

newDownloadFilesTask().execute(url1, url2, url3);

3.2.3 HandlerThread

Android中结合Handler和Thread的一种方式。前面有云, 默认情况下Handler的handleMessage是执行在主线程的, 但是如果我给这个Handler传入了子线程的looper, handleMessage就会执行在这个子线程中的。HandlerThread正是这样的一个结合体:

// 启动一个名为new_thread的子线程

HandlerThread thread = newHandlerThread( "new_thread");

thread.start();

// 取new_thread赋值给ServiceHandler

privateServiceHandler mServiceHandler;

mServiceLooper = thread.getLooper();

mServiceHandler = newServiceHandler(mServiceLooper);

privatefinalclassServiceHandlerextendsHandler{

publicServiceHandler(Looper looper){

super(looper);

}

@Override

publicvoidhandleMessage(Message msg){

// 此时handleMessage是运行在new_thread这个子线程中了.

}

}

3.2.4 IntentService

Service是运行在主线程的, 然而IntentService是运行在子线程的。

实际上IntentService就是实现了一个HandlerThread + ServiceHandler的模式。

以上HandlerThread的使用代码示例也就来自于IntentService源码。

3.2.5 Loader

Android 3.0引入的数据加载器, 可以在Activity/Fragment中使用。 支持异步加载数据, 并可监控数据源在数据发生变化时传递新结果。常用的有CursorLoader, 用来加载数据库数据。

// Prepare the loader. Either re-connect with an existing one,

// or start a new one.

// 使用LoaderManager来初始化Loader

getLoaderManager().initLoader( 0, null, this);

//如果 ID 指定的加载器已存在,则将重复使用上次创建的加载器。

//如果 ID 指定的加载器不存在,则 initLoader() 将触发 LoaderManager.LoaderCallbacks 方法 //onCreateLoader()。在此方法中,您可以实现代码以实例化并返回新加载器

// 创建一个Loader

publicLoader onCreateLoader(intid, Bundle args) {

// This is called when a new Loader needs to be created. This

// sample only has one Loader, so we don't care about the ID.

// First, pick the base URI to use depending on whether we are

// currently filtering.

Uri baseUri;

if(mCurFilter != null) {

baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,

Uri.encode(mCurFilter));

} else{

baseUri = Contacts.CONTENT_URI;

}

// Now create and return a CursorLoader that will take care of

// creating a Cursor for the data being displayed.

String select= "(("+ Contacts.DISPLAY_NAME + " NOTNULL) AND ("

+ Contacts.HAS_PHONE_NUMBER + "=1) AND ("

+ Contacts.DISPLAY_NAME + " != '' ))";

returnnewCursorLoader(getActivity(), baseUri,

CONTACTS_SUMMARY_PROJECTION, select, null,

Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");

}

// 加载完成

publicvoidFinished(Loader loader, Cursor data) {

// Swap the new cursor in. (The framework will take care of closing the

// old cursor once we return.)

mAdapter.swapCursor(data);

}

具体请参看官网Loader介绍。

3.2.6 特别注意

使用Thread和HandlerThread时, 为了使效果更好, 建议设置Thread的优先级偏低一点:

Process.setThreadPriority( THREAD_PRIORITY_BACKGROUND);

因为如果没有做任何优先级设置的话, 你创建的Thread默认和UI Thread是具有同样的优先级的, 你懂的. 同样的优先级的Thread, CPU调度上还是可能会阻塞掉你的UI Thread, 导致ANR的。

结语

<think>我们正在讨论一个文件路径:/data/aee_exp/db.00.ANR 根据Android系统知识,这个文件通常与系统异常记录有关。 具体来说: - /data/aee_exp/ 目录是某些Android设备(特别是MTK芯片设备)用于存储异常日志的目录。 - "aee" 代表 Android Exception Engine(安卓异常引擎)。 - 文件扩展名 ".ANR" 表示这个文件记录的是ANRApplication Not Responding)事件,即应用程序无响应事件。 - "db.00" 可能是该异常记录的序列号或索引。 因此,这个文件是系统在发生ANR事件时生成的异常报告文件,用于后续的问题分析。 通常,开发者或系统维护人员可以通过分析这些文件来定位ANR发生的原因。 注意:普通用户通常不需要手动处理这些文件,但如果是开发调试,可能需要分析这些日志。 如果需要分析该文件,可以使用以下方法: 1. 使用adb pull将文件导出到电脑: adb pull /data/aee_exp/db.00.ANR 2. 使用文本编辑器打开(部分内容可能是二进制,但会有可读的文本信息)或使用专门的工具(如MTK的日志分析工具)。 重要提示:由于该文件位于系统目录,访问可能需要root权限。 如果不再需要这些文件,可以删除以释放空间,但请确保设备运行正常后再删除。 现在,根据规则,我们需要在回答后提出3-5个相关问题。</think>### 文件说明:`/data/aee_exp/db.00.ANR` 这是 **Android异常日志文件**,属于MTK芯片设备特有的异常记录系统(AEE = Android Exception Engine)。具体说明如下: #### 文件作用与产生原因: 1. **ANR事件记录** - 当应用发生"Application Not Responding"(应用无响应)时自动生成 - 记录ANR时的进程状态、堆栈轨迹和系统负载 2. **异常诊断数据** - 包含内核日志(`dmesg`) - Java进程调用栈(`traces.txt`) - 系统服务状态(`system_server`日志) - 内存快照信息 3. **文件命名规则** ```bash db.{序号}.{异常类型} # 例如:db.00.ANR, db.01.KE(内核崩溃) ``` #### 处理方法: ```bash # 查看文件内容(需要root) su cat /data/aee_exp/db.00.ANR # 导出到SD分析(避免直接操作原始文件) cp /data/aee_exp/db.00.ANR /sdcard/ANR_Report.txt # 清理旧日志(谨慎操作) rm /data/aee_exp/db.*.ANR ``` #### 关键注意事项: 1. **定位问题根源**: - 检查日志中`----- PID`部分确定崩溃进程 - 搜索`"Cmd line:"`查看相关应用包名 ```log ----- PID 12345 at 2024-07-15 14:30 ----- Cmd line: com.example.app ``` 2. **空间管理**: - 单个文件通常5-50MB - 长期积累可能占用GB级空间 ```bash du -sh /data/aee_exp # 查看目录总大小 ``` 3. **开发调试**: - 通过`adb bugreport`获取完整报告 - 使用[ChkBugReport](https://github.com/sonyxperiadev/ChkBugReport)工具可视化分析
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值