一个ANR的log带来的优化

2021-09-29 15:08:28 浏览数 (2)

相对于crash,anr是更难定位的,特别是一些都是系统log的anr,比如下面这种

代码语言:javascript复制
#1 main
Input dispatching timed out
1 android.os.BinderProxy.transactNative(Native Method)
2 android.os.BinderProxy.transact(BinderProxy.java:510)
3 android.app.IActivityManager$Stub$Proxy.getCastPid(IActivityManager.java:9586)
4 android.hardware.SystemSensorManager$SensorEventQueue.dispatchSensorEvent(SystemSensorManager.java:831)
5 android.os.MessageQueue.nativePollOnce(Native Method)
6 android.os.MessageQueue.next(MessageQueue.java:336)
7 android.os.Looper.loop(Looper.java:181)
8 android.app.ActivityThread.main(ActivityThread.java:7520)
9 java.lang.reflect.Method.invoke(Native Method)
10 com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
11 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

主线程发了响应超时,但是相关的log都是系统代码,无法定位到问题,这个时候,可以看下其他线程的信息,常常会有意外的收获,比如下面这种

可以发现,出现anr的时候,这里有名称叫MTPrimaryEglEngine的线程正在执行判断文件是否可用的方法,连续看了多个anr的log,都是上报同样的情况,相关代码如下

代码语言:javascript复制
public static boolean isFileExist(String filePath) {
    if (TextUtils.isEmpty(filePath)) {
       return false;
    } else {
       return StorageUtils.isExternalStorageReadable() ? (new File(filePath)).exists() : false;
     }
    }
    
public static boolean isExternalStorageReadable() {
        String state = Environment.getExternalStorageState();
        return state.equals("mounted") || state.equals("mounted_ro");
    }

初步看代码,没发现什么问题,不过抱着怀疑的心态,新建一个demo跑下相关方法的耗时

time的后面是耗时,单位是纳秒,大吃一惊, isExternalStorageReadable方法平均耗时要3ms左右,而且isFileExist()方法一个高频调用的方法,每次调用判断,都会导致3ms左右的耗时,实在太严重了,同时,另外一个,判断文件存在的方法File.exist()只有0.1ms左右,就合理多了

接下来尝试手动关闭App的读取sd卡的权限,发现isExternalStorageReadable也是返回true,这个时候才突然醒悟

isExternalStorageReadable只判断当前手机是否有SD卡,跟是否拥有读SD卡的权限是两回事

这个代码也是很多年前的线上代码了,这么多年,一直在这里卡住了耗时,唏嘘不已,接下来就是立马做优化,基于最小改动原则,改成了一次性判断

代码语言:javascript复制
    private static final boolean isStorageReadable;

    static {
        /**
         * 这个方法在麒麟890手机上验证平均耗时3ms
         * SD卡是否可用,只需要判断一次就可以,优化性能
         */
        final String state = Environment.getExternalStorageState();
        isStorageReadable = state.equals(Environment.MEDIA_MOUNTED) || state.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
    }
    
public static boolean isExternalStorageReadable() {
        return isStorageReadable;
    }

用static修饰,保证只执行一次,把结果存在一个final值里面(本来想用kotlin的by lazy的,无奈这个库是多项目公用,还是用java)

这样的话,下次每次判断,只需要获取当前的boolean值,非常的轻量,问题修复,由于这个方法很多地方都会频繁调用,上线后,应该会对App整体的性能有一些优化

0 人点赞