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

相关推荐
换元不配限2 小时前
从 AsyncTask 到现代并发:Android 异步方案的演进与替代实践
android·asynctask·异步编程·方案对比·asynctask替代方案
用户2018792831672 小时前
你的 App 为什么是「电量刺客」?——从一条 WakeLock 追到 Linux 内核的 Android 省电原理全解
android
mmsx2 小时前
Android 安全校验下沉到原生层:防 patch 与构建期自动化
android·安全·自动化
老牛日常随笔3 小时前
Google把Android测试搬进AI对话:ARTEMIS如何用自然语言操作你的手机
android·人工智能·智能手机
朱涛的自习室4 小时前
预算 1000 美金的开源软件,DeepSeek 只花 1 美金就做完了(Day 6)
android·前端·人工智能
mmsx5 小时前
Android APK 加固方案怎么落地:从加壳到校验的组合取舍
android·网络·安全·签名
hunterandroid5 小时前
Navigation Component 工程落地:从单 Activity 架构到深度链接配置
android·前端
撩得Android一次心动5 小时前
Jetpack Compose 知识点整理3【个人用】
android·笔记·学习·kotlin·android compose
Kapaseker6 小时前
别再只会写 <T>:Kotlin 泛型的实战用法
android·kotlin