Android系统ANR错误实战分析

转载请注明出处:https://blog.csdn.net/weixin_42072033/article/details/109622923

 

大家好,今天给大家带来Android系统ANR错误实战分析。在开始之前请允许我说几句不相干的话。

 

我叫麻锦荣,你们叫我锦荣就好了,在深圳从接触Android到现在已经有7个年头了,从最早的ADT(那会儿还没有AS)开发app,到后来定制手机ROM,再到智能家居,智能硬件,车载产品,电视机,商显等Android开发工作,一路走来深感Android的发展迅速,对于Android程序员的要求也从APP开发提高到系统Framework层,机器学习,机器视觉等更广的领域。一路上,我碰到过无数的难题,相信以后也一样会碰到,但是互联网的知识共享给了我力量,成为了我前进道路上的好帮手。但是我却以工作太忙或者其他的借口,其实就是自己的懒散为由从来没有把自己开发中的心得分享给大家,共同学习,共同进步。这是我第一篇博客,很多表达或许词不达意,但是我相信万事开头难,以后一定会渐入佳境。

 

好了,废话就说这么多,下面开始进入今天的主题吧,相信看完这篇文章的你对ANR问题的解决又会更加充满信心。

 

Android开发中经常碰到一个叫做ANR(Application Not Responding)的问题,如果这个apk是你们自己开发的,报ANR,有经验的程序员自己肯定会较为容易的检查出代码哪里出了问题。好,那么问题来了,如果是原厂(MTK,全志,RK,amlogic等)系统报出ANR,我们怎么解决?下面就用我公司实际碰到的问题为例,为大家实战讲解。

 

相信大家都知道有个东西叫小部件Widget,我们手机上的时钟小部件可以选择大小,开机的时候加载到我们的桌面上显示时间。前几天公司发现一个bug,直接上图。

Android系统ANR错误实战分析_第1张图片

 

如图,时钟Widget一直显示正在加载,几分钟后依然不显示时间,抓取log,发现ANR错误,如下:

Android系统ANR错误实战分析_第2张图片

使用的板子和源码均为amlogic平台,Android版本为9.0 。  由于是编译完直接烧录,可以100%确定是由源码引起ANR,从log信息看,似乎是com.android.phone 这个应用包下的VvmSimStateTracker这个类引起的,那是这样么?我们找到这个类

Android系统ANR错误实战分析_第3张图片

Android系统ANR错误实战分析_第4张图片

可以看出,VvmSimStateTracker就是一个广播接收器,就是用来监听一些sim卡热插拔的工具类,从刚才的log中看,是由于开机广播的处理出现了问题导致ANR,好,那么我们把这个android.intent.action.BOOT_COMPLETED给它注释掉,编译,看下结果。结果发现竟然正常了,时钟部件开机5秒内正常显示时间。好多人有疑问了,为什么只是简单接收了一个开机广播,就报ANR了呢?我们又不是第一天接收它了,留个疑问在这里。

 

Android中,主线程(UI线程)如果在规定时内没有处理完相应工作,就会出现ANR。具体来说,ANR会在以下几种情况中出现

  1. 输入事件(按键和触摸事件)5s内没被处理: Input event dispatching timed out
  2. BroadcastReceiver的事件(onRecieve方法)在规定时间内没处理完(前台广播为10s,后台广播为60s):Timeout of broadcast BroadcastRecord
  3. service 前台20s后台200s未完成启动 Timeout executing service
  4. ContentProvider的publish在10s内没进行完:timeout publishing content providers

看完这个很多人就会恍然大悟,第二条这个报的错和上面日志的那个不是一样么

那这下原因找到了,就是因为UI线程阻塞导致了VvmSimStateTracker类没有在规定时间内处理完BroadcastReceiver事件。^_^ 那问题又来了,UI线程为什么会阻塞?谁把它搞阻塞了?这里我就要告诉大家怎么分析解决系统ANR。

 

首先,adb shell 进入 data/system 目录下会看到一个叫做dropbox 的文件夹(adb操作我就不多赘述了,可以查看其他博客)

Android系统ANR错误实战分析_第5张图片

只要你的Android系统出现ANR或者Crash等,系统就会保存日志到这个文件夹中,对你分析问题的产生有巨大帮助。话不多说,我们把这个dropbox拷出来,看看里面的内容

 

Android系统ANR错误实战分析_第6张图片

好家伙,东西还挺多,可以看到里面有很多的日志,那么我们所需要分析ANR的日志正是 system_app_anr@*******.txt.gz,不同平台的命名规则可能大同小异,总之大家看anr这几个关键字就可以了。解压,打开看看。

Android系统ANR错误实战分析_第7张图片

前面几行我们刚才见过了,下面红色标注的这里就是系统在发生ANR时CPU的实时使用情况

Android系统ANR错误实战分析_第8张图片

可以看到,我们的CPU在发生ANR 的时候总共才使用了12%左右的性能。这里要注意了,

1.如果发生ANR的进程CPU占用较高,如到了80%或90%以上,则可以怀疑是应用内一些代码不合理消耗掉了CPU资源,比如死循环或者一些算法库进行大量高精度复杂运算导致CPU长期占用较高,这就要结合trace和ANR前后的log进一步分析了。

2.如果某些进程的CPU占用百分比较高,几乎占用了所有CPU资源,而发生ANR的进程CPU占用为0%或非常低,则认为CPU资源被占用,进程没有被分配足够的资源,从而发生了ANR。这种情况多数可以认为是系统状态的问题,并不是由本应用造成的。

3.如果CPU总用量不高,那么很有可能是一些耗时操作或者锁的问题使主线程被阻塞 ,导致ANR。

4.如果iowait 占用率过高,很可能是系统等待I/O耗时操作,导致ANR。

这里我们明显看到CPU使用情况在各项指标中都表现正常,那么我们怀疑是第三条,也就是一些耗时操作或者锁的问题使主线程被阻塞 ,既然怀疑是主线程阻塞,那么我们往下看,找到主线程的相关日志。

"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x75429ee0 self=0xaea49000
  | sysTid=4078 nice=0 cgrp=default sched=0/0 handle=0xb2d34494
  | state=S schedstat=( 168928298 466851782 531 ) utm=6 stm=10 core=0 HZ=100
  | stack=0xbb365000-0xbb367000 stackSize=8MB
  | held mutexes=
  native: #00 pc 00019d58  /system/lib/libc.so (syscall+32)
  native: #01 pc 0001d215  /system/lib/libc.so (__futex_wait_ex(void volatile*, bool, int, bool, timespec const*)+88)
  native: #02 pc 000633b1  /system/lib/libc.so (pthread_cond_timedwait+84)
  native: #03 pc 0004a005  /system/lib/libc++.so (_ZNSt3__118condition_variable15__do_timed_waitERNS_11unique_lockINS_5mutexEEENS_6chrono10time_pointINS5_12system_clockENS5_8durationIxNS_5ratioILx1ELx1000000000EEEEEEE+124)
  native: #04 pc 0001ebf1  /system/lib/libhidltransport.so (android::hardware::details::Waiter::wait()+224)
  native: #05 pc 0001f31f  /system/lib/libhidltransport.so (android::hardware::details::getRawServiceInternal(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, bool, bool)+826)
  native: #06 pc 000b07c5  /system/lib/libandroid_runtime.so (JHwBinder_native_getService(_JNIEnv*, _jclass*, _jstring*, _jstring*, unsigned char)+168)
  at android.os.HwBinder.getService(Native method)
  at android.hardware.radio.V1_0.IRadio.getService(IRadio.java:40)
  at com.android.internal.telephony.RIL.getRadioProxy(RIL.java:369)
  at com.android.internal.telephony.RIL.getHardwareConfig(RIL.java:3367)
  at com.android.internal.telephony.TelephonyDevController.registerRIL(TelephonyDevController.java:112)
  at com.android.internal.telephony.RIL.(RIL.java:484)
  at com.android.internal.telephony.PhoneFactory.makeDefaultPhone(PhoneFactory.java:172)
  - locked <0x051877b9> (a java.lang.Object)
  at com.android.internal.telephony.PhoneFactory.makeDefaultPhones(PhoneFactory.java:99)
  at com.android.phone.PhoneGlobals.onCreate(PhoneGlobals.java:286)
  at com.android.phone.PhoneApp.onCreate(PhoneApp.java:41)
  at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1154)
  at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5871)
  at android.app.ActivityThread.access$1100(ActivityThread.java:199)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1650)
  at android.os.Handler.dispatchMessage(Handler.java:106)
  at android.os.Looper.loop(Looper.java:193)
  at android.app.ActivityThread.main(ActivityThread.java:6669)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

"main" 这个标志就代表主线程的相关日志,我们看到在 PhoneFactory 类中 makeDefaultPhone方法是有Object对象锁的。不急,我们找到日志中显示的几个类看看,

PhoneApp类 onCreate 时会调用 PhoneGlobals的 onCreate

public class PhoneApp extends Application {
    PhoneGlobals mPhoneGlobals;
    TelephonyGlobals mTelephonyGlobals;

    public PhoneApp() {
    }

    @Override
    public void onCreate() {
        if (UserHandle.myUserId() == 0) {
            // We are running as the primary user, so should bring up the
            // global phone state.
            mPhoneGlobals = new PhoneGlobals(this);
            mPhoneGlobals.onCreate();

            mTelephonyGlobals = new TelephonyGlobals(this);
            mTelephonyGlobals.onCreate();
        }
    }
}

PhoneGlobals类 onCreate时会调用 PhoneFactory类的 makeDefaultPhones

public void onCreate() {
        if (VDBG) Log.v(LOG_TAG, "onCreate()...");

        ContentResolver resolver = getContentResolver();

        // Cache the "voice capable" flag.
        // This flag currently comes from a resource (which is
        // overrideable on a per-product basis):
        sVoiceCapable =
                getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
        // ...but this might eventually become a PackageManager "system
        // feature" instead, in which case we'd do something like:
        // sVoiceCapable =
        //   getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_VOICE_CALLS);

        if (mCM == null) {
            // Initialize the telephony framework
            PhoneFactory.makeDefaultPhones(this);

            // Start TelephonyDebugService After the default phone is created.
            Intent intent = new Intent(this, TelephonyDebugService.class);
            startService(intent);

            mCM = CallManager.getInstance();
            for (Phone phone : PhoneFactory.getPhones()) {
                mCM.registerPhone(phone);
            }

而PhoneFactory类的 makeDefaultPhones方法是有对象锁的

Android系统ANR错误实战分析_第9张图片

Android系统ANR错误实战分析_第10张图片

那么问题迎刃而解,就是由于开机启动的时候,PhoneApp类调用PhoneFactory类的makeDefaultPhones 方法,而makeDefaultPhones还是被对象锁住的状态,导致主线程阻塞,进而导致VvmSimStateTracker类没有在规定时间内处理完开机广播,发生ANR。那么我们试试解决这个问题,把PhoneAPP初始化操作放在一个子线程中去进行。

@Override
    public void onCreate() {
        if (UserHandle.myUserId() == 0) {
            // We are running as the primary user, so should bring up the
            // global phone state.
           /* mPhoneGlobals = new PhoneGlobals(this);
            mPhoneGlobals.onCreate();

            mTelephonyGlobals = new TelephonyGlobals(this);
            mTelephonyGlobals.onCreate(); */

			new Thread(new Runnable() {
				@Override
				public void run() {

                    Looper.prepare();
					
					mPhoneGlobals = new PhoneGlobals(PhoneApp.this);
					mPhoneGlobals.onCreate();

					mTelephonyGlobals = new TelephonyGlobals(PhoneApp.this);
                    mTelephonyGlobals.onCreate(); 
            
                    Looper.loop();
					
				}
			}).start();

        }
    }

编译下,试试效果

Android系统ANR错误实战分析_第11张图片

开机5秒内时钟部件正常显示了时间,没有ANR错误报出,问题解决。(这里篇幅过大就不做动图了,后面的博客我尽量把动态图加进去,使大家阅读体验更加好)

 

至此,我的第一篇博客就结束了,后续会给大家分享一些APK开发或者系统Framework层的一些问题,而且也会把代码或者项目开源,放在GitHub上与大家一起分享,共同进步,希望我的文章对你们有所帮助。

 

 

你可能感兴趣的:(android,移动开发,安卓,app,framework)