Android ANR分析实践(二):由输入事件无响应产生的ANR分析及解决

首先,我们简单写一个测试应用,手动制造一个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()这个方法一直是运行在主线程的。

猜你喜欢

转载自blog.csdn.net/pshiping2014/article/details/82186521