首先,我们简单写一个测试应用,手动制造一个ANR,代码如下
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
anrTest();
}
private void anrTest() {
Button anr_btn = findViewById(R.id.anr_btn);
anr_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Handler().post(new Runnable() {
@Override
public void run() {
String str="anr test";
for (int i = 0; i <= 15000; i++) {
str = str + i;
if (i == 0 || i == 15000) {
Log.d("MainActivity", "" + i);
}
}
}
});
}
});
}
}
运行程序,点击anr_btn后,在多次点击button2,由于anr_btn再进行一个耗时的循环操作,导致button2的输入事件无法响应,从而产生ANR,界面如下
接下来我们分析anr/traces.txt文件,看看是不是由于耗时操作引起的UI线程被阻塞。打开Android Studio,点击右下角的Device File Explorer找到traces.txt,或者通过adb pull 导出来分析
查看trace.txt文件,开头即为产生anr的进程、时间及包名
----- pid 8428 at 1970-01-02 07:25:00 -----
Cmd line: com.psp.anrtest1
然后我们再看堆栈信息
"Jit thread pool worker thread 0" prio=5 tid=2 Native (still starting up)
| group="" sCount=1 dsCount=0 obj=0x0 self=0x7f76e41400
| sysTid=8433 nice=9 cgrp=default sched=0/0 handle=0x7f76509450
| state=S schedstat=( 14509428 16964689 27 ) utm=1 stm=0 core=1 HZ=100
| stack=0x7f7640b000-0x7f7640d000 stackSize=1021KB
| held mutexes=
kernel: __switch_to+0x7c/0x88
kernel: futex_wait_queue_me+0xe4/0x160
kernel: futex_wait+0xfc/0x210
kernel: do_futex+0xdc/0x898
kernel: SyS_futex+0x114/0x1a0
kernel: el0_svc_naked+0x24/0x28
native: #00 pc 000000000001c12c /system/lib64/libc.so (syscall+28)
native: #01 pc 00000000000e7ac8 /system/lib64/libart.so (_ZN3art17ConditionVariable16WaitHoldingLocksEPNS_6ThreadE+160)
native: #02 pc 0000000000467d7c /system/lib64/libart.so (_ZN3art10ThreadPool7GetTaskEPNS_6ThreadE+252)
native: #03 pc 0000000000467238 /system/lib64/libart.so (_ZN3art16ThreadPoolWorker3RunEv+124)
native: #04 pc 0000000000466b68 /system/lib64/libart.so (_ZN3art16ThreadPoolWorker8CallbackEPv+116)
native: #05 pc 0000000000068470 /system/lib64/libc.so (_ZL15__pthread_startPv+196)
native: #06 pc 000000000001ddc0 /system/lib64/libc.so (__start_thread+16)
(no managed stack frames)
"main" prio=5 tid=1 Runnable
| group="main" sCount=0 dsCount=0 obj=0x753b8a10 self=0x7f76e40a00
| sysTid=8428 nice=-10 cgrp=default sched=0/0 handle=0x7f7ae00a98
| state=R schedstat=( 6123797127 320340821 21818 ) utm=186 stm=426 core=1 HZ=100
| stack=0x7fcfc1c000-0x7fcfc1e000 stackSize=8MB
| held mutexes= "mutator lock"(shared held)
at com.psp.anrtest1.MainActivity$1$1.run(MainActivity.java:35)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke!(Native method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
"JDWP" daemon prio=5 tid=4 WaitingInMainDebuggerLoop
| group="system" sCount=1 dsCount=0 obj=0x12c72ca0 self=0x7f76e42800
| sysTid=8435 nice=0 cgrp=default sched=0/0 handle=0x7f7630b450
| state=S schedstat=( 3374998 2467084 12 ) utm=0 stm=0 core=2 HZ=100
| stack=0x7f76211000-0x7f76213000 stackSize=1005KB
| held mutexes=
kernel: __switch_to+0x7c/0x88
kernel: poll_schedule_timeout+0x44/0x68
kernel: do_select+0x4dc/0x534
kernel: core_sys_select+0x208/0x324
kernel: SyS_pselect6+0x18c/0x238
kernel: el0_svc_naked+0x24/0x28
native: #00 pc 000000000006a944 /system/lib64/libc.so (__pselect6+8)
native: #01 pc 0000000000023430 /system/lib64/libc.so (select+156)
native: #02 pc 0000000000552c20 /system/lib64/libart.so (_ZN3art4JDWP12JdwpAdbState15ProcessIncomingEv+340)
native: #03 pc 0000000000303b4c /system/lib64/libart.so (_ZN3art4JDWP9JdwpState3RunEv+920)
native: #04 pc 0000000000303020 /system/lib64/libart.so (_ZN3art4JDWPL15StartJdwpThreadEPv+48)
native: #05 pc 0000000000068470 /system/lib64/libc.so (_ZL15__pthread_startPv+196)
native: #06 pc 000000000001ddc0 /system/lib64/libc.so (__start_thread+16)
(no managed stack frames)
找到代码出错的位置 at com.psp.anrtest1.MainActivity$1$1.run(MainActivity.java:35)
查看代码,果真是for循环导致,为了确保猜测,我们打印一下for循环所在的线程,是不是主线程
new Handler().post(new Runnable() {
@Override
public void run() {
Log.d("MainActivity","thread name = "+Thread.currentThread().getName());
String str="anr test";
for (int i = 0; i <= 15000; i++) {
str = str + i;
if (i == 0 || i == 15000) {
Log.d("MainActivity", "" + i);
}
}
}
});
输出如下:
01-02 08:02:38.504 8856-8856/com.psp.anrtest1 D/MainActivity: thread name = main
果然运行在主线程,我们做如下修改,使之运行在子线程
new Handler().post(new Runnable() {
@Override
public void run() {
Log.d("MainActivity","thread name = "+Thread.currentThread().getName());
new Thread(new Runnable() {
@Override
public void run() {
Log.d("MainActivity","sun thread name = "+Thread.currentThread().getName());
String str="anr test";
for (int i = 0; i <= 15000; i++) {
str = str + i;
if (i == 0 || i == 15000) {
Log.d("MainActivity", "" + i);
}
}
}
}).start();
}
});
点击anr_btn,循环确实运行在子线程了
01-02 08:08:11.000 9207-9207/com.psp.anrtest1 D/MainActivity: thread name = main
01-02 08:08:11.001 9207-9343/com.psp.anrtest1 D/MainActivity: sun thread name = Thread-2
此时,无论我们怎么操作界面,都不会产生ANR了。之前一直不知道,原来new Handler().post()这个方法一直是运行在主线程的。