Android中synchronized代码块引发ANR问题的修复方案咨询
修复ANR问题的方案
问题根源
这段代码引发ANR的核心原因:
- 主线程执行同步阻塞操作:
readLocalResource在主线程调用,synchronized(this)会导致主线程在锁竞争时进入Blocked状态,无法响应UI事件 - 耗时操作占用主线程:
parseJson如果是解析大JSON的耗时操作,加上同步锁的竞争,会直接让主线程占用时间超过ANR阈值(通常5秒)
修复方案1:异步化耗时操作+双重检查锁
把读取SharedPreferences和JSON解析逻辑移到子线程,同时用双重检查锁减少锁竞争,保证线程安全:
@Nullable private volatile T data; // volatile保证多线程可见性 @NonNull public T get() { // 第一次无锁检查,避免每次进入同步块 if (data == null) { synchronized (this) { // 第二次检查,防止多线程重复初始化 if (data == null) { // 子线程执行耗时操作 new Thread(() -> { String resourceAsString = sharedPreferences.getString(key, null); T parsedData = TextUtils.isEmpty(resourceAsString) ? initialResource : parseJson(resourceAsString); data = parsedData; }).start(); // 临时返回默认值,或根据业务用回调返回结果 return initialResource; } } } return data; }
修复方案2:线程安全的懒加载单例(推荐)
如果ResourceCache是单例,用Initialization-on-demand holder模式,完全避免同步锁竞争:
private static class ResourceCacheHolder { private static final ResourceCache INSTANCE = new ResourceCache(); } public static ResourceCache getInstance() { return ResourceCacheHolder.INSTANCE; } // 构造函数中异步初始化数据 private ResourceCache() { new Thread(() -> { String resourceAsString = sharedPreferences.getString(key, null); data = TextUtils.isEmpty(resourceAsString) ? initialResource : parseJson(resourceAsString); }).start(); } @NonNull public T get() { // 未初始化完成时返回默认值,或添加等待逻辑 return data != null ? data : initialResource; }
修复方案3:用DataStore替代SharedPreferences
DataStore是Google推荐的SharedPreferences替代方案,天然支持异步操作,从根源避免主线程阻塞:
private final DataStore<Preferences> dataStore; public ResourceCache(Context context) { dataStore = PreferencesDataStore.create(context, "resource_cache"); } // 用回调返回结果 public void getResource(Consumer<T> resultCallback) { dataStore.data .map(preferences -> { String resourceAsString = preferences.get(stringPreferencesKey(key)); return TextUtils.isEmpty(resourceAsString) ? initialResource : parseJson(resourceAsString); }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( resultCallback, throwable -> resultCallback.accept(initialResource) // 异常时返回默认值 ); }
注意事项
- 禁止在主线程执行IO、JSON解析等耗时操作,哪怕是SharedPreferences的读操作,高频调用+锁竞争也会阻塞主线程
- 用
volatile修饰data变量,保证多线程下的内存可见性,避免脏读 - 若业务需要立即获取数据,可在Application启动阶段提前异步初始化,或提供合理的默认临时值
内容的提问来源于stack exchange,提问作者amodkanthe
相关产品推荐
相关产品推荐

