[小折腾] SharedPreferenceImpl$EditorImpl#apply引发的ANR场景

书本《Android工程化最佳实践》(作者金凯)中谈到SharedPreference会有ANR的可能,以及贴出关键源码(api19)和相关的log日志,读者可以先行看看。

目前可以确定的是,若写文件真的耗时,不管在哪个线程执行apply,都有可能引发ANR。

来个时序图,基于android api26

[小折腾] SharedPreferenceImpl$EditorImpl#apply引发的ANR场景_第1张图片

可以看到,如果work耗时长,UI线程必定会阻塞等待,越长越容易引发ANR

想要折腾验证,本人是通过一些代码,实现work做一些耗时的动作,在写文件时sleep当前线程,源码在最下方

日志:

[小折腾] SharedPreferenceImpl$EditorImpl#apply引发的ANR场景_第2张图片

看看thread14在做什么

[小折腾] SharedPreferenceImpl$EditorImpl#apply引发的ANR场景_第3张图片

可以看到queued-work-looper其实就是QueuedWork自己开的线程HandlerThread,这个线程还在执行耗时任务,任务是写数据到本地文件

 

这和书上的不太一样,书上的是阻塞在CountDownLatch.await,而这里却是阻塞在synchronize(sProcessWork)中,其实原因是androidApi26不一样了,那个版本开始变本人还没看,只看了androidApi19,Api19的QueuedWork源码和书本中介绍的是一样的,读者可以自行阅读一下。本人基于androidApi19实践后的结果:

日志:

[小折腾] SharedPreferenceImpl$EditorImpl#apply引发的ANR场景_第4张图片

而QueuedWork也不一样,用的是SingleThreadExecutor执行任务的

[小折腾] SharedPreferenceImpl$EditorImpl#apply引发的ANR场景_第5张图片

 

个人看法:

  • apply会阻塞一下ui线程,哪怕数据小影响不大,目前本人能力限制还不知道如何保证避免太大影响,所以还是在子线程中做commit的操作,避免使用apply吧。除非心中有数。
  • 自行封装SharedPreferences或者使用第三方库,避免直接在ui中commit。

 

折腾用的源码:

protected void onCreate(Bundle savedInstance) {
    super.onCreate(savedInstance);
    
    new Thread(() -> {
            try {
                SharedPreferences spImpl = getSharedPreferences("test", 0);
                spImpl.getAll();
                Field mMapFid = Class.forName("android.app.SharedPreferencesImpl").getDeclaredField("mMap");
                mMapFid.setAccessible(true);
                Log.d("testProxyHandler", "mapField: " + mMapFid.getType());
                Map map = new HashMap<>();
                ProxyHandler handler = new ProxyHandler(map);
                Class[] interfaces = HashMap.class.getInterfaces();
                boolean containsMapInterf = false;
                for (Class interf : interfaces) {
                    if (interf.toString().equals(Map.class.toString())) {
                        containsMapInterf = true;
                        break;
                    }
                }
                if (!containsMapInterf) {// it will be false on androidApi19
                    Log.d("testProxyHandler", "containsMapInterf:" + containsMapInterf);
                    Class[] newInterfaces = new Class[interfaces.length + 1];
                    System.arraycopy(interfaces, 0, newInterfaces, 1, interfaces.length);
                    newInterfaces[0] = Map.class;
                    interfaces = newInterfaces;
                }
                ClassLoader cl = handler.getClass().getClassLoader();
                StringBuilder interfsStr = new StringBuilder();
                for (int i = 0; i < interfaces.length; ++i) {
                    interfsStr.append(interfaces[i].toString());
                    interfsStr.append(";");
                }
                Log.d("testProxyHandler", "interfaces: " + interfsStr.toString());
                mMapFid.set(spImpl, (Map) Proxy.newProxyInstance(cl, interfaces, handler));

                spImpl.edit().putBoolean("test", false).apply();
            } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }).start();

        getWindow().getDecorView().postDelayed(() -> {
            startService(new Intent(this, MyService.class));
        }, 1000);
}

 

你可能感兴趣的:(android,android,anr)