之前在做项目开发的时候曾经遇到过一个坑,我们的业务需求是点击相应的国家图标进行国家切换包含汇率、url等的切换,所以当时我们考虑的是切换的时候用SharedPreferences来对存储当前的国家代码,所以我们有了以下的代码:
SharedPreferences.Editor editor = PreferenceUtil.getDefaultPreference(DgApplication.getInstance()).edit();
//value是切换的国家代码
editor.putInt(PreferenceUtil.STRING_COUNTRY_CODE, value);
editor.apply();
在完成切换国家动作之后,为了清除之前的缓存数据更新服务器url,我们选择了像微信退出登录那样重启应用。那个时候我们只开放了两个国家,这个坑在最开始的时候并没有出现问题,后来我们开放了第三个国家、第四个国家…然后突然有一天这个潜在的隐患爆发了,测试人员跑来说为什么我切换到新加坡但是我的界面显示的是马币(马来西亚的货币)?那时候我们才开始注意到这个问题。后来一路追踪排查,发现是这句 editor.apply();
出的锅。解决办法就是将apply改为commit,也就是 editor.commit();
就可以了。那么现在来想想,都是提交,为什么用apply会出问题,而commit就可以成功处理呢?他们俩之间到底有什么区别呢?google一下发现大家的结论都是:
- apply没有返回值而commit返回boolean表明修改是否提交成功
- apply是将修改数据原子提交到内存,而后异步真正提交到硬件磁盘;而commit是同步的提交到硬件磁盘,因此,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。而apply只是原子的提交到内存,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。
- apply方法不会提示任何失败的提示
虽然这些说法都对,但是我们还是追其根源,看看他们各自的源码里都做了什么“手脚”。
apply和commit都是SharedPreferences的内部接口Editor的一个方法,而他们的实现都在SharedPreferencesImpl类里。
首先我们看看在SharedPreferences.Editor接口里commit方法的定义:
/**
* Commit your preferences changes back from this Editor to the
* {@link SharedPreferences} object it is editing. This atomically
* performs the requested modifications, replacing whatever is currently
* in the SharedPreferences.
*
* <p>Note that when two editors are modifying preferences at the same
* time, the last one to call commit wins.
*
* <p>If you don't care about the return value and you're
* using this from your application's main thread, consider
* using {@link #apply} instead.
*
* @return Returns true if the new values were successfully written
* to persistent storage.
*/
boolean commit();
这里注释的意思大概是这个提交方法会返回一个修改后的结果,会自动执行修改的请求,替换掉当前Shar