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博客

相关推荐
kp0000011 分钟前
PHP弱类型安全漏洞解析与防范指南
android·开发语言·安全·web安全·php·漏洞
编程乐学(Arfan开发工程师)5 小时前
06、基础入门-SpringBoot-依赖管理特性
android·spring boot·后端
androidwork5 小时前
使用 Kotlin 和 Jetpack Compose 开发 Wear OS 应用的完整指南
android·kotlin
繁依Fanyi7 小时前
Animaster:一次由 CodeBuddy 主导的 CSS 动画编辑器诞生记
android·前端·css·编辑器·codebuddy首席试玩官
奔跑吧 android9 小时前
【android bluetooth 框架分析 02】【Module详解 6】【StorageModule 模块介绍】
android·bluetooth·bt·aosp13·storagemodule
田一一一12 小时前
Android framework 中间件开发(三)
android·中间件·framework·jni
androidwork17 小时前
掌握 Kotlin Android 单元测试:MockK 框架深度实践指南
android·kotlin
田一一一17 小时前
Android framework 中间件开发(二)
android·中间件·framework
追随远方17 小时前
FFmpeg在Android开发中的核心价值是什么?
android·ffmpeg
神探阿航18 小时前
HNUST湖南科技大学-安卓Android期中复习
android·安卓·hnust