书本《Android工程化最佳实践》(作者金凯)中谈到SharedPreference会有ANR的可能,以及贴出关键源码(api19)和相关的log日志,读者可以先行看看。
目前可以确定的是,若写文件真的耗时,不管在哪个线程执行apply,都有可能引发ANR。
来个时序图,基于android api26
可以看到,如果work耗时长,UI线程必定会阻塞等待,越长越容易引发ANR
想要折腾验证,本人是通过一些代码,实现work做一些耗时的动作,在写文件时sleep当前线程,源码在最下方
日志:
看看thread14在做什么
可以看到queued-work-looper其实就是QueuedWork自己开的线程HandlerThread,这个线程还在执行耗时任务,任务是写数据到本地文件
这和书上的不太一样,书上的是阻塞在CountDownLatch.await,而这里却是阻塞在synchronize(sProcessWork)中,其实原因是androidApi26不一样了,那个版本开始变本人还没看,只看了androidApi19,Api19的QueuedWork源码和书本中介绍的是一样的,读者可以自行阅读一下。本人基于androidApi19实践后的结果:
日志:
而QueuedWork也不一样,用的是SingleThreadExecutor执行任务的
个人看法:
- 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<String, Object> 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<String, Object>) 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);
}