getExternalCacheDir 导致的ANR问题分析总结

在用户上报的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);
            }
        });
    }
发布了24 篇原创文章 · 获赞 3 · 访问量 6142

猜你喜欢

转载自blog.csdn.net/binghelonglong123/article/details/88681784
今日推荐