Android12 解决应用使用Settings异常断电数据丢失的问题

本文探讨了Android应用在大屏设备异常断电时,使用Settings进行持久化存储可能导致数据丢失的问题。在Android12中,尽管AtomicFile能确保文件写入的原子性,但无法防止数据丢失。为解决此问题,文章提出了两种方法:在文件系统挂载时启用MS_DIRSYNC参数,或通过ioctl命令设置DIRSYNC模式。这两种方法分别具有性能损失和文件系统兼容性的权衡。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在应用开发过程中,经常会做持久化保存,持久化一般分为SharedPreferences和Settings。但是遇到大屏等产品异常断电时,往往存储的数据会丢失甚至损坏。手机或平板没有这个问题是因为该类产品一般都有电池,不会出现异常断电的情况。

修改在最下面,主要修改底层文件系统。

对于SharedPreferences而言,只需要执行sync()操作就能强制把内容写到真是的屋里内存。但是Settings这种方式就不行,往往需要修改驱动才能解决。

在Android12中,Settings写操作在以下路径下,它是采用了AtomicFile的方式,这种方式可以避免断电文件损坏,但是避免不了刚写入的数据丢失。

路径:frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java

    private void doWriteState() {
        boolean wroteState = false;
        final int version;
        final ArrayMap<String, Setting> settings;
        final 
<think>嗯,用户问的问题是关于Android关机流程中,使用Settings.System.putInt写入数据后执行关机,有时候数据没保存的问题。首先,我需要回忆一下Settings.System.putInt的工作机制。用户之前已经了解过这个方法是同步的,所以理论上调用后数据应该已经写入数据库了。但现在出现数据保存的情况,可能存在其他原因。 首先,我应该考虑Android系统的关机流程是怎样的。关机时系统会终止各个进程,可能包括系统服务,比如负责处理设置写入的ContentProvider或者SystemServer中的相关部分。如果关机流程在putInt调用之后很快开始,可能导致ContentProvider还没完成写入就被终止了,尽管putInt是同步的,但可能系统层面的处理有延迟或者异步步骤。 另外,ContentResolver的apply和commit的区别也可能相关。虽然putInt内部可能使用的是commit,但需要确认其具体实现。如果是apply的话,虽然是异步的,但根据文档,putInt应该是同步的。所以这里可能需要查看源码确认。 另外,系统服务可能在关机时被快速终止,导致即使putInt返回成功,但数据还没持久化到磁盘。因为数据库写入可能先到内存,再异步刷盘。这时候如果系统突然断电数据就会丢失。这种情况即使在同步方法中也可能发生,因为同步只保证数据提交到数据库事务,但事务的持久化可能需要时间。 用户可能没有意识到,即使同步方法返回成功,数据可能还在内核的页面缓存中,没有真正写入磁盘存储。因此,在关机时,如果系统立即断电,这部分数据可能丢失。需要解释数据库写入的机制和持久化的保证。 此外,AndroidSettings Provider可能有自己的缓存机制,或者某些设置需要额外的步骤才能生效,比如广播或者重启服务。如果在写入后没有正确触发这些步骤,即使数据保存了,可能在下一次启动时没有正确加载。 可能的解决方案包括在写入后调用ContentResolver的update或者确保数据库立即刷盘,或者使用更底层的API,或者在关机流程中增加延迟等待数据持久化完成。但需要指出这些方法在普通应用中的可行性,因为普通应用可能没有权限修改关机流程或者访问底层API。 需要验证putInt是否确实同步写入磁盘,或者是否依赖系统服务的响应。可能SystemServer在关机时处理请求的队列被中断,导致尽管putInt返回成功,但实际上系统服务能处理该请求。 总结,可能的原因包括:1. 系统服务在关机时被终止,导致写入完成;2. 数据库写入到内存缓存但刷盘;3. 设置需要额外步骤生效。需要逐一分析,并给出相应的解释和建议。</think>`Settings.System.putInt()` 确实是同步方法,理论上调用后数据会立即写入系统数据库 (`settings.db`),但在**关机流程中仍可能出现数据保存**的情况。以下是可能原因及技术细节分析: --- ### 一、同步方法的局限性 #### 1. 数据库事务的持久化延迟 - **同步 ≠ 立即刷盘** `putInt()` 的同步性仅表示调用线程会等待数据库事务提交完成,但数据库底层可能仍存在**内核页面缓存(Page Cache)**真正写入物理存储。Android 的 SQLite 默认使用 `PRAGMA synchronous = FULL`,但事务提交后仍需依赖系统调度将缓存数据写入磁盘。 - **关机断电导致缓存丢失** 若在数据从缓存刷入磁盘前断电,写入会丢失。这是所有文件系统面临的共性问题,并非 Android 特有。 #### 2. 系统服务的生命周期 - **Settings Provider 进程被提前终止** `Settings.System.putInt()` 的底层实现通过 Binder 调用系统服务 `SettingsProvider`(运行在 `system_server` 进程)。在关机流程中,`system_server` 可能优先被终止,导致尚完成的数据库操作被中断。 --- ### 二、关机流程的特殊性 #### 1. 关机时序问题 Android 关机流程会触发以下关键步骤: 1. 广播 `ACTION_SHUTDOWN`,通知应用和服务保存数据; 2. 终止非关键系统服务(可能包括 `SettingsProvider`); 3. 卸载文件系统,同步缓存到磁盘; 4. 内核关闭硬件。 若 `putInt()` 调用发生在步骤 2 之后,`SettingsProvider` 已终止,写入操作将直接失败。 #### 2. 文件系统卸载的不可逆性 - **卸载文件系统后无法写入** 在关机后期,文件系统会进入只读模式或直接卸载。此时即使 `putInt()` 成功返回,底层文件操作也会因存储不可写而静默失败。 --- ### 三、验证与解决方案 #### 1. 验证数据是否真正持久化 可通过以下命令检查 `settings.db` 的修改时间: ```bash adb shell su root ls -l /data/system/users/0/settings_system.xml ``` 若关机后时间戳更新,说明数据写入。 #### 2. 解决方案 1. **强制同步文件系统** 在 `putInt()` 后调用 `sync` 命令强制刷盘: ```java Runtime.getRuntime().exec("sync"); ``` *注意:需要 `root` 权限,普通应用无法使用。* 2. **提前触发关机前的数据保存** 在接收到 `ACTION_SHUTDOWN` 广播时尽早执行关键数据写入: ```java BroadcastReceiver shutdownReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Settings.System.putInt(resolver, "KEY", value); } }; context.registerReceiver(shutdownReceiver, new IntentFilter(Intent.ACTION_SHUTDOWN)); ``` 3. **使用更底层的 API(需系统权限)** 系统应用可通过直接修改 `settings_system.xml` 或调用 `Settings.Global.putString()` 的 `makeDefault` 方法,强制生成默认配置文件。 --- ### 四、总结表 | 现象 | 根本原因 | 解决方案 | |--------------------|----------------------------|-----------------------------------| | 数据写入后丢失 | 内核缓存刷盘 | 调用 `sync`(需 root) | | `putInt()` 返回失败 | SettingsProvider 进程已终止 | 提前在 `ACTION_SHUTDOWN` 阶段保存 | | 设置生效 | 依赖服务的重启逻辑触发 | 发送广播通知系统(如 `AIRPLANE_MODE_CHANGED`)| --- ### 示例:改进后的关机数据保存逻辑 ```java public void safeShutdownWrite(Context context) { ContentResolver resolver = context.getContentResolver(); // 1. 同步写入设置 Settings.System.putInt(resolver, "SENSITIVE_KEY", 1); // 2. 强制刷盘(需要系统权限) if (hasRootPermission()) { Runtime.getRuntime().exec("sync"); } // 3. 延迟关机确保操作完成 new Handler(Looper.getMainLooper()).postDelayed(() -> { PowerManager pm = context.getSystemService(PowerManager.class); pm.shutdown(false, "user request", false); }, 1000); // 延迟 1 秒 } ``` *注意:此代码需要系统签名或 root 权限,普通应用无法实现完整流程。* 若您需要进一步分析具体场景(如系统定制开发),可提供更多日志细节。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Frame Not Work

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值