欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Android系统ANR错误实战分析

程序员文章站 2022-06-19 09:43:00
大家好,今天给大家带来Android系统ANR错误实战分析。在开始之前请允许我说几句不相干的话。我叫麻锦荣,你们叫我锦荣就好了,在深圳从接触Android到现在已经有7个年头了,从最早的ADT(那会儿还没有AS)开发app,到后来定制手机ROM,再到智能家居,智能硬件,车载产品,电视机,商显等Android开发工作,一路走来深感Android的发展迅速,对于Android程序员的要求也从APP开发提高到系统Framework层,机器学习,机器视觉等更广的领域。一路上,我碰到过无数的难题,相信以后也....

转载请注明出处: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错误实战分析

 

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

Android系统ANR错误实战分析

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

Android系统ANR错误实战分析

Android系统ANR错误实战分析

可以看出,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
    07-27 19:18:47.448 1707 1766 W BroadcastQueue: Receiver during timeout: ResolveInfo{ccd831e com.example.qintong.myapplication/.MyBroadCastReciever m=0x108000}
    07-27 19:18:47.502 3513 3728 I WtEventController: ANR com.example.qintong.myapplication 7573
  3. service 前台20s后台200s未完成启动 Timeout executing service
  4. ContentProvider的publish在10s内没进行完:timeout publishing content providers

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

Android系统ANR错误实战分析

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

 

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

Android系统ANR错误实战分析

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

 

Android系统ANR错误实战分析

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

Android系统ANR错误实战分析

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

Android系统ANR错误实战分析

可以看到,我们的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<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> 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.<init>(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错误实战分析

Android系统ANR错误实战分析

那么问题迎刃而解,就是由于开机启动的时候,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() {
					
					mPhoneGlobals = new PhoneGlobals(PhoneApp.this);
					mPhoneGlobals.onCreate();

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

        }
    }

编译下,试试效果

Android系统ANR错误实战分析

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

 

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

 

 

 

 

 

 

 

 

 

 

 

                

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

本文地址:https://blog.csdn.net/weixin_42072033/article/details/109622923

相关标签: android