相对于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整体的性能有一些优化