在SharedPreferences中,commit()和apply()有什么不同?

511

我在我的安卓应用程序中使用了SharedPreferences。 我同时使用了共享首选项的commit()apply()方法。当我使用AVD 2.3时,没有出现错误,但是当我在AVD 2.1中运行代码时,apply()方法会出错。

那么这两个方法有什么区别呢?如果只使用commit()方法,可以在不出现任何问题的情况下存储首选项值吗?


132
虽然这篇文章已经有一年的历史了,但我还是要评论一下。虽然可能显而易见,但没有一个答案提到:apply()函数会异步执行磁盘 I/O 操作,而 commit() 函数则是同步的。因此,在UI线程中不应该调用 commit() 函数。 - okonomichiyaki
需要注意的是,当有多个SharedPreferences.Editor对象在使用时,最后一个调用apply()的对象会胜出。因此,如果您确保应用程序只使用一个SharedPreferences.Editor,则可以安全地使用apply()代替commit() - aoeu
2
根据Android Studio Lint警告:commit()将立即同步保存数据。然而,apply()将异步(在后台)保存它,从而提高了一些性能。这就是为什么如果您不关心其返回类型(数据是否成功保存),则优先使用apply()而不是commit()的原因。 - Rahul Raina
在使用commit()时,有没有一种方法可以禁用Lint警告? - QED
9个回答

732

8
这个回答是正确的,但我猜@spacemanaki上面的评论也是正确的,包含有价值的信息。 - Aksel Fatih
70
commit() 立即将数据写入持久性存储,而 apply() 则会在后台处理它。 - capt.swag
23
它会创建竞态条件吗? - ChrisMcJava
49
如果我使用apply()写入一些内容并立即尝试读取它,会发生什么?读取是否保证给我最新的值?文档表示,如果在触发apply()后发生另一个commit(),那么该commit()将阻塞直到apply()被持久化到磁盘上,这使得当涉及到“写入”操作时,此问题不会发生,但是如果您正在写入并立即读取呢?从我的测试中,最新的值被返回,但我想知道这是否100%保证。 - Bitcoin Cash - ADA enthusiast
28
可以安全地将任何commit()方法的实例替换为apply()方法,请参见http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#apply()。 - Tigran Sarkisian
显示剩余9条评论

272

简而言之:

  • commit()同步地将数据写入(阻塞调用该方法的线程)。然后它会通知您操作的成功与否。
  • apply()会以异步方式安排数据的写入。它不会通知您操作的成功与否。
  • 如果您使用apply()保存并立即通过任何getX-方法读取,则会返回新的值!
  • 如果您曾经调用过apply()且它仍在执行,则对commit()的所有调用都将被阻塞,直到所有过去的apply调用以及当前的commit调用完成为止。

来自SharedPreferences.Editor文档的更详细信息:

commit()会将其首选项同步写入持久性存储,而apply()会立即将其更改提交给内存中的SharedPreferences,但开始一个异步提交到磁盘,您不会收到任何失败的通知。如果在一个apply()仍未完成时,此SharedPreferences上的另一个编辑器执行了常规的commit(),则该commit()将会阻塞,直到所有异步提交和commit()本身完成为止。

由于SharedPreferences实例在进程内是单例模式,所以如果您已经忽略了返回值,可以将任何一个commit()实例替换为apply()。

不应直接实现SharedPreferences.Editor接口。但是,如果您之前已经实现了它并且现在正在收到有关缺少apply()的错误,则可以从apply()调用commit()即可。


24
这个答案更好,因为它提到apply()是异步的,而且未决的写操作会阻塞对commit()的后续调用。 - spaaarky21

26

我在使用apply()而不是commit()时遇到了一些问题。正如其他回答中所述,apply()是异步的。我的问题是,对“字符串集”偏好所做的更改从未写入持久内存。

如果你强制终止程序,或者在我安装在Android 4.1设备上的ROM中,当系统由于内存需求而杀死进程时,就会发生这种情况。

我建议如果想让您的偏好保持有效,请使用“commit()”而不是“apply()”。


你确定你的问题不是由于并发线程引起的吗?在调用apply()之后,你必须等一段时间才能读取你添加的内容,否则UI线程会在apply()的工作线程提交更改之前尝试读取。 - Marco Altran
关于字符串集,https://dev59.com/-3LYa4cB1Zd3GeqParGp - J.G.Sebring
@JoseLSegura - 文档显示不同:http://developer.android.com/intl/ja/reference/android/content/SharedPreferences.Editor.html#apply() “您无需担心Android组件生命周期及其与apply()写入磁盘的交互。框架确保在切换状态之前,来自apply()的正在进行的磁盘写入已完成。” 我想知道你看到的是不是Android中的一个错误,如果是的话,它是否已在更新版本中得到修复。 - ToolmakerSteve
我使用“ProcessPhoenix”库重置我的应用程序时遇到了同样的问题。在执行重置之前,我保存了一个偏好设置,但“apply”无法工作。 - ElYeante

15
  • commit() 同步执行,apply() 异步执行

  • apply() 是一个无返回值函数。

  • commit() 会返回true,如果新的值成功写入持久化存储。

  • apply() 确保在状态切换之前完成,您不需要担心Android组件的生命周期。

如果您不使用从主线程调用 commit() 的返回值,请改用 apply()


1
为了清晰起见,“switching states”是什么? - carloswm85
@carloswm85 2岁了,但需要澄清的是:“切换状态”通常意味着诸如屏幕方向更改之类的事件。 - mrahimygk

15

使用apply()。

它会立即将更改写入RAM,然后在稍后将其写入内部存储(实际的偏好文件)。而commit则是同步地直接将更改写入文件。


13

这份文档对apply()commit()的区别有很好的解释:

commit()会同步地将其偏好设置写入持久存储,而apply()会立即将更改提交到内存中的SharedPreferences,但会启动异步提交到磁盘,并且您不会收到任何失败通知。如果在apply()尚未完成的情况下,该SharedPreferences上的另一个编辑器执行常规的commit(),则commit()将被阻塞,直到所有异步提交以及提交本身都已完成。由于SharedPreferences实例在进程中是单例的,因此,如果您已经忽略了返回值,则可以将任何commit()实例替换为apply()


12

commit()和apply()的区别

在使用SharedPreference时,我们可能会混淆这两个术语。基本上它们可能是相同的,因此让我们澄清一下commit()和apply()之间的区别。

1.返回值:

apply()提交操作后不会返回成功或失败的布尔值。commit()返回true表示保存成功,false则表示保存失败。

  1. 速度:

apply()更快。commit()较慢。

  1. 异步与同步:

apply():异步。commit():同步。

  1. 原子性:

apply():原子操作。commit():原子操作。

  1. 错误通知:

apply():无。commit():有。


1
apply()commit()更快的原因是什么?它们本质上代表了将被放置在线程的Looper中的相同任务。commit()将该任务放置在主Looper中,而apply()则将其放置在后台,从而使主Looper免于磁盘I/O任务的负担。 - Taseer
1
与 commit() 不同,它会同步将其首选项写入持久存储,apply() 立即将其更改提交到内存中的 SharedPreferences,但会开始异步提交到磁盘,并且您不会收到任何失败通知。如果此 SharedPreferences 上的另一个编辑器执行常规 commit(),而 apply() 仍未完成,则 commit() 将阻塞,直到所有异步提交以及 commit() 本身完成,请参见文档 https://developer.android.com/reference/android/content/SharedPreferences.Editor#apply()。 - Chanaka Weerasinghe

7

从javadoc中得知:

与commit()不同,该方法会立即将更改提交到内存中的SharedPreferences,但会异步地将更改提交到磁盘,您无法收到任何失败通知。如果在apply()仍未完成时,此SharedPreferences上的另一个编辑器执行了常规的commit(),则commit()将阻塞,直到所有异步提交以及提交本身都已完成。


0
在使用Web API时,我注意到在注销之前使用共享首选项时apply()的主要缺点。 请看以下场景: 用户登录并通过POST传递了令牌(用于自动重新登录)。
SharedPreferences preferences = App.getContext().getSharedPreferences("UserCredentials", Context.MODE_PRIVATE);

SharedPreferences.Editor editor = preferences.edit();

editor.putString("TOKEN",token);

editor.apply();

Token在所有会话中都可用,无需担心,自动重新登录也可以在各种状态下无缝完成。

编写注销功能时,我按以下方式清除了共享首选项

SharedPreferences preferences = App.getContext().getSharedPreferences("UserCredentials", Context.MODE_PRIVATE);

SharedPreferences.Editor editor = preferences.edit();

editor.clear();

editor.apply()
简而言之,您可以简单地写成:
preferences.edit().clear().apply()
为了确保我清除了缓存,我会在注销之前进行日志记录,preferences.getString("TOKEN"); 显示为 null。 在重新启动主活动(因为它以登录片段开始)之后 - 我会再次检查令牌,使用:
SharedPreferences preferences = App.getContext().getSharedPreferences("UserCredentials", MODE_PRIVATE);
        
String retrievedToken = preferences.getString("TOKEN",null); // Second param = default value

Log.d(TAG, "RETRIEVING TOKEN: " + retrievedToken);

即使在注销之前清除了令牌,该令牌实际上还是出现了。

(导致注销 ->“使用令牌登录循环)

只有在设置和清除令牌时都添加editor.commit();,令牌才会真正消失。

请注意,我使用的是模拟器。

这种行为实际上是有道理的,因为内存中的存储已更新,但异步调用在应用程序重新启动之前未完成。

因此,commit();将强制应用程序等待(同步地)执行实际的后续命令,如system.exit()等,而apply()如果其他命令强制应用程序进行某种状态更改,则可能失败。

为了确保您击中所有正确的位置,您始终可以连续使用apply()和commit()。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,