Android 滥用 SharedPreference 导致 ANR 问题

SharedPreference 是 Android 平台提供的一种轻量 级的数据存储 方式,它用于存储 应用的配置信息或者一些简单数据 。SharedPreference 基于键值对的存储,并且支持基本的数据类型,如整型、字符串、布尔值等。它的使用非常简单方便,适合保存 一些小量数据

ANR(Application Not Responding) 指的是应用程序无法在规定的时间内响应用户输入事件,导致应用失去响应无法继续 正常运行通常 情况下,ANR 出现在主线程 中执行耗时操作 或者发生死锁 的情况下,比如网络请求长时间没有响应、数据库操作耗时等。当 ANR 发生时系统 出一个错误对话框 ,告知用户应用程序无响应,用户 可以选择等待或者关闭应用。


SharedPreference 有两种提交方式:commit (同步) 和 apply(异步)

我相信应该很少人会使用 commit,因为 SharedPreference 的提交涉及读写文件,是耗时操作,所以如果放在主线程的话很有可能会导致 ANR

但是,你以为都使用 apply(异步)就完事大吉了吗,并不然,滥用 SharedPreference 的 apply(异步)也有可能会导致 ANR 的


apply() 的源码:

java 复制代码
package android.app;

final class SharedPreferencesImpl implements SharedPreferences {

    public final class EditorImpl implements Editor {
        @Override
        public void apply() {
            // 将所有事务整理成 MemoryCommitResult 对象
            final MemoryCommitResult mcr = commitToMemory(); 

            final Runnable awaitCommit = new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 阻塞当前线程 (UI线程)
                            mcr.writtenToDiskLatch.await();
                        } catch (InterruptedException ignored) {
                        }
                    }
                };

            QueuedWork.addFinisher(awaitCommit); // 保存到一个静态的数据结构

            Runnable postWriteRunnable = new Runnable() {
                    @Override
                    public void run() {
                        awaitCommit.run();
                        QueuedWork.removeFinisher(awaitCommit); // 从静态的数据结构中移除
                    }
                };

            // 异步执行写操作
            SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);

            // ....
        }
    }
}

在调用 apply() 之后,首先会将所有提交的事务整理成一个对象(mcr ),然后定义了一个 awaitCommit(Runnable),顾名思义就是等待提交,这个 Runnable 是一个 CountDownLatch 的 await(),作用是阻塞当前线程。

之后把 awaitCommit 加入 到一个静态数据结构(等会说)

下面定义了一个 postWriteRunnable(Runnable),哎里面是执行上面的 awaitCommit(Runnable)和移除静态数据结构的 awaitCommit。


这样子设计是因为 Android 系统为了保障在页面切换,也就是在多进程中 sp 文件能够存储成功,在 ActivityThread 的 handlePauseActivity 和 handleStopActivity 时会通过 waitToFinish 保证 这些异步任务都 已经被执行完成

java 复制代码
package android.app;

public final class ActivityThread extends ClientTransactionHandler
        implements ActivityThreadInternal {
    // ....

    @Override
    public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving,
            int configChanges, PendingTransactionActions pendingActions, String reason) {

        // Make sure any pending writes are now committed.
        if (r.isPreHoneycomb()) {
            QueuedWork.waitToFinish();
        }

        // ....
    }
}

OK 下面的 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); 就是把写任务放在子线程中去执行。

总结为什么滥用 SharedPreference 会导致 ANR 问题呢?
① commit 方法读写耗时操作放在主线程执行
② apply 方法主线程阻塞等待子线程读写执行完


SharedPreference 现在其实已经非常不建议去使用了,因为它是全量更新 ,所以保存的数据越多 ,所需要的耗时 ,越容易发生 ANR。这个时候需要有替代品(MMKV)

Android MMKV 原理简述_android mmkv原理-CSDN博客

相关推荐
每次的天空1 小时前
Android学习总结之应用启动流程(从点击图标到界面显示)
android·学习
一清三白2 小时前
Android Studio 连接雷电模拟器教程
android
姜行运3 小时前
数据结构【栈和队列附顺序表应用算法】
android·c语言·数据结构·算法
wang_peng3 小时前
android studio 基础
android·ide·android studio
〆、风神5 小时前
EasyExcel 数据字典转换器实战:注解驱动设计
android·java·注解
stevenzqzq6 小时前
Android studio xml布局预览中 Automotive和Autotive Distant Display的区别
android·xml·android studio
QING6186 小时前
Kotlin commonPrefixWith用法及代码示例
android·kotlin·源码阅读
QING6186 小时前
Kotlin groupByTo用法及代码示例
android·kotlin·源码阅读
兰琛12 小时前
Compose组件转换XML布局
android·xml·kotlin
水w13 小时前
【Android Studio】解决报错问题Algorithm HmacPBESHA256 not available
android·开发语言·android studio