在用户上报的ANR问题当中,有一类是调用系统api时导致的ANR
今天分析一下调用context.getExternalCacheDir()方法导致的ANR
一般发生这种ANR 时打印的堆栈是这样
kernel: __switch_to+0x94/0xa0
kernel: binder_thread_read+0x3ac/0x10bc
kernel: binder_ioctl_write_read.constprop.41+0x1c8/0x2f8
kernel: binder_ioctl+0x1f8/0x650
kernel: compat_SyS_ioctl+0x124/0xf90
kernel: cpu_switch_to+0x31c/0x2360
native: #00 pc 000496d0 /system/lib/libc.so (__ioctl+8)
native: #01 pc 0001dea9 /system/lib/libc.so (ioctl+32)
native: #02 pc 0004a25b /system/lib/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+202)
native: #03 pc 0004ac2f /system/lib/libbinder.so (android::IPCThreadState::waitForResponse(android::Parcel*, int*)+250)
native: #04 pc 00043541 /system/lib/libbinder.so (android::BpBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+36)
native: #05 pc 000b967b /system/lib/libandroid_runtime.so (???)
native: #06 pc 00776cad /system/framework/arm/boot-framework.oat (Java_android_os_BinderProxy_transactNative__ILandroid_os_Parcel_2Landroid_os_Parcel_2I+132)
at android.os.BinderProxy.transactNative(Native method)
at android.os.BinderProxy.transact(Binder.java:802)
at android.os.storage.IStorageManager$Stub$Proxy.mkdirs(IStorageManager.java:1594)
at android.app.ContextImpl.ensureExternalDirsExistOrFilter(ContextImpl.java:2488)
at android.app.ContextImpl.getExternalCacheDirs(ContextImpl.java:690)
- locked <0x063c811c> (a java.lang.Object)
at android.app.ContextImpl.getExternalCacheDir(ContextImpl.java:682)
at android.content.ContextWrapper.getExternalCacheDir(ContextWrapper.java:272)
at com.xx.xxxx.core.utils.g$1.run(SourceFile:32)
如果你尝试新开一个线程来执行getExternalCacheDir()来规避这个ANR时会发现毫无效果 而且ANR堆栈变成这样
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x73b52490 self=0xed3da000
| sysTid=7719 nice=0 cgrp=default sched=0/0 handle=0xf1af94a4
| state=S schedstat=( 65213743 28149120 113 ) utm=2 stm=4 core=2 HZ=100
| stack=0xff43b000-0xff43d000 stackSize=8MB
| held mutexes=
at android.app.ContextImpl.getPreferencesDir(ContextImpl.java:519)
- waiting to lock <0x063c811c> (a java.lang.Object) held by thread 11
at android.app.ContextImpl.getSharedPreferencesPath(ContextImpl.java:718)
at android.app.ContextImpl.getSharedPreferences(ContextImpl.java:372)
- locked <0x0ec1b125> (a java.lang.Class<android.app.ContextImpl>)
at android.content.ContextWrapper.getSharedPreferences(ContextWrapper.java:167)
at com.xx.xxxx.core.e.a.a(SourceFile:20)
at com.xx.xxxx.core.e.b.a(SourceFile:31)
at com.xx.xxxx.core.ui.base.BaseApplication.h(SourceFile:60)
at com.xx.xxxx.core.ui.base.BaseApplication.onCreate(SourceFile:53)
at com.xx.xxxx.xxxxxApplication.onCreate(SourceFile:23)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1125)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6004)
at android.app.ActivityThread.-wrap1(ActivityThread.java:-1)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1806)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:192)
at android.app.ActivityThread.main(ActivityThread.java:6801)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:825)
什么情况,怎么getSharedPreferences 也ANR了
仔细分析堆栈 发现main线程被0x063c811c这个对象锁住了
再从堆栈中搜索这把锁,你会发现持有这把锁的正是调用了getExternalCacheDir()的线程
进一步查看context.getExternalCacheDir()的源码
@Override
public File getExternalCacheDir() {
// Operates on primary external storage
return getExternalCacheDirs()[0];
}
@Override
public File[] getExternalCacheDirs() {
synchronized (mSync) {
File[] dirs = Environment.buildExternalStorageAppCacheDirs(getPackageName());
return ensureExternalDirsExistOrFilter(dirs);
}
}
原来是mSync这把锁锁住主线程,最终追踪到 这个binder调用的地方一直没有响应
咨询了框架组的同事 得到如下回复
1:调用getPhoneStorageState()和getExternalStorageState()等接口时
此接口存在I0耗时且容易卡在PKMS\MountService binder返回
2:插入损坏的SD卡时,调用此类接口有很大的几率会被卡住
解决方案
前面已经说了 将方法放在子线程里面也无济于事
那么只能尽量避免调用这些方法,这里提供一个初始化系统SD卡缓存目录和系统缓存目录的方式。
public static void init() {
// 线程异步获取 防止出现ANR
ThreadPoolUtil.runOnThread(new Runnable() {
@Override
public void run() {
// 优先自己组装缓存目录 防止ANR
APP_CACHE_DIR = Environment.getExternalStorageDirectory() + "/Android/data/"
+ CommonVersionUtil.getAppPackName(BaseApplication.getApplicationInstance()) + "/cache";
File file = new File(APP_CACHE_DIR);
if (!file.exists()) {
boolean isMake = file.mkdirs();
// 创建失败再用系统方式获取
if (!isMake) {
file = BaseApplication.getApplicationInstance().getExternalCacheDir();
if (file != null) {
APP_CACHE_DIR = file.getAbsolutePath();
}
}
}
File cacheDir = null;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// getDataDir不会被mSync锁住
File dataDir = BaseApplication.getApplicationInstance().getDataDir();
cacheDir = new File(dataDir, "cache");
if (!cacheDir.exists()) {
// 这里不去mkdirs了
cacheDir = BaseApplication.getApplicationInstance().getCacheDir();
}
} else {
cacheDir = BaseApplication.getApplicationInstance().getCacheDir();
}
if (cacheDir != null) {
APP_SYSTEM_CACHE_DIR = cacheDir.getAbsolutePath();
}
VLog.d(TAG, "APP_CACHE_DIR = " + APP_CACHE_DIR);
VLog.d(TAG, "APP_SYSTEM_CACHE_DIR = " + APP_SYSTEM_CACHE_DIR);
}
});
}