赞
踩
1、SharePreferences是线程安全的 里面的方法有大量的synchronized来保障。
2、SharePreferences不是进程安全的即使你用了MODE_MULTI_PROCESS 。
3、第一次getSharePreference会读取磁盘文件,异步读取,写入到内存中,后续的getSharePreference都是从内存中拿了,所有的get都是从内存中读取。
4、第一次读取完毕之前 所有的get/set请求都会被卡住等待读取完毕后再执行,所以第一次读取会有ANR风险。
5、提交都是写入到内存和磁盘中 。apply跟commit的区别在于
commit:内存同步 只不过要等待磁盘写入结束才返回 直接返回写入成功状态 true or false
apply:是内存同步 然后磁盘异步写入任务放到一个单线程队列中 等待调用,方法无返回 即void
6、 所以单个文件千万不要太大 否则会严重影响性能。
Andorid 7.0及以上会抛出异常,Sharepreferences不再支持多进程模式。多进程共享文件会出现问题的本质在于,因为不同进程,所以线程同步会失效。要解决这个问题,可尝试跨进程解决方案,如ContentProvider、AIDL、Service。腾讯微信团队的MMKV采用内存映射的方式,解决SharedPreferences的各种问题。
1、找到对应name的文件
2、加载对应文件到内存中SharedPreference
3、一个xml文件对应一个SharedPreferences单例
1、commit()方法和apply()方法的区别:
commit()方法是同步的有返回结果,同步保证使用Countdownlatch,即使同步但不保证往磁盘的写入是发生在当前线程的。
apply()方法是异步的具体发生在QueuedWork中,里面维护了一个单线程去执行磁盘写入操作。
2、commit()和apply()方法其实都是Block主线程。commit()只要在主线程调用就会堵塞主线程;apply()方法磁盘写入操作虽然是异步的,但是当组件(Activity Service BroadCastReceiver)这些系统组件特定状态转换的时候,会把QueuedWork中未完成的那些磁盘写入操作放在主线程执行,且如果比较耗时会产生ANR,手动可怕。
3、跨进程操作,需要借助Android平台常规的IPC手段(如,AIDL ContentProvider等)来完成。
首先,我们看下SharePreferences的简单使用,代码如下:
- mSharedPreferences = context.getSharedPreferences("test", Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = mSharedPreferences.edit();
- editor.putString(key, value);
- editor.apply();
context.getSharedPreferences其实就是简单的调用ContextImpl的getSharedPreferences,具体实现如下
- @Override
- public SharedPreferences getSharedPreferences(String name, int mode) {
- SharedPreferencesImpl sp;
- synchronized (ContextImpl.class) {
- if (sSharedPrefs == null) {
- sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
- }
-
- final String packageName = getPackageName();
- ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
- if (packagePrefs == null) {
- packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
- sSharedPrefs.put(packageName, packagePrefs);
- }
- sp = packagePrefs.get(name);
- if (sp == null) {
- <!--读取文件-->
- File prefsFile = getSharedPrefsFile(name);
- sp = new SharedPreferencesImpl(prefsFile, mode);
- <!--缓存sp对象-->
- packagePrefs.put(name, sp);
- return sp;
- }
- }
- <!--跨进程同步问题-->
- if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
- getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
- sp.startReloadIfChangedUnexpectedly();
- }
- return sp;
- }
这里面数据的加载的地方需要看下,比如,SharePreferences数据的加载是同步还是异步?数据加载是new SharedPreferencesImpl对象时候开始的
- SharedPreferencesImpl(File file, int mode) {
- mFile = file;
- mBackupFile = makeBackupFile(file);
- mMode = mode;
- mLoaded = false;
- mMap = null;
- startLoadFromDisk();
- }
startLoadFromDisk很简单,新开一个子线程,然后去通过 XmlUtils.readMapXml()
方法把指定的 SharedPreferences
文件的所有的键值对都读出来,然后存放到一个 map 中。如果其他线程想要在读取之前就是用的话,就会被阻塞,一直wait等待,直到数据读取完成。
- private void startLoadFromDisk() {
- synchronized (mLock) {
- mLoaded = false;
- }
- new Thread("SharedPreferencesImpl-load") {
- public void run() {
- loadFromDiskLocked();
- }
- }.start();
- }
-
- private void loadFromDiskLocked() {
- ...
- Map map = null;
- StructStat stat = null;
- try {
- stat = Os.stat(mFile.getPath());
- if (mFile.canRead()) {
- BufferedInputStream str = null;
- try {
- <!--读取xml中配置-->
- str = new BufferedInputStream(
- new FileInputStream(mFile), 16*1024);
- map = XmlUtils.readMapXml(str);
- }...
- mLoaded = true;
- ...
- <!--唤起其他等待线程-->
- notifyAll();
- }
可以看到其实就是直接使用xml解析工具XmlUtils,直接在当前线程读取xml文件,所以,如果xml文件稍大,尽量不要在主线程读取,读取完成之后,xml中的配置项都会被加载到内存,再次访问的时候,其实访问的是内存缓存。
每日一问:谈谈 SharedPreferences 的 apply() 和 commit() - 南尘 - 博客园
每日一问:谈谈 SharedPreferences 的 apply() 和 commit()_linshijun33的博客-CSDN博客
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。