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 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
前行的小黑炭2 小时前
一篇搞定Android 实现扫码支付:如何对接海外的第三方支付;项目中的真实经验分享;如何高效对接,高效开发
android
落落落sss3 小时前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
代码敲上天.4 小时前
数据库语句优化
android·数据库·adb
GEEKVIP6 小时前
手机使用技巧:8 个 Android 锁屏移除工具 [解锁 Android]
android·macos·ios·智能手机·电脑·手机·iphone
model20058 小时前
android + tflite 分类APP开发-2
android·分类·tflite
彭于晏6898 小时前
Android广播
android·java·开发语言
与衫9 小时前
掌握嵌套子查询:复杂 SQL 中 * 列的准确表列关系
android·javascript·sql
500了16 小时前
Kotlin基本知识
android·开发语言·kotlin
人工智能的苟富贵16 小时前
Android Debug Bridge(ADB)完全指南
android·adb