内容简介:复习了下SharePreference的使用,以及了解下SharePreference的源码实现,解决多进程情况下的SharePreference问题,做下笔记。参考文章:源码分析:
复习了下SharePreference的使用,以及了解下SharePreference的源码实现,解决多进程情况下的SharePreference问题,做下笔记。
参考文章:
源码分析:
SharePreference的多进程解决方案:
SharePreference
Android平台中一个轻量级的存储库,用来保存应用程序的各种配置信息。本质是一个以“key-value”键值对的方式保存数据的xml文件。
文件保存地址:在/data/data/package name/shared_prefs目录下就可以查看到这个文件了
简单使用例子
//获取得到SharePreference,第一个参数是文件名称,第二个参数是操作模式 //一般是MODE_PRIVATE模式,指定该SharedPreferences数据只能被本应用程序读、写 SharedPreferences sharedPreferences = getSharedPreferences("test", MODE_PRIVATE); //创建Editor对象 SharedPreferences.Editor editor=sharedPreferences.edit(); //保存数据 editor.putString("name","donggua"); //editor.commit(); editor.apply(); //读取数据 String result=sharedPreferences.getString("name","默认值"); 复制代码
commit和apply的区别
当使用commit去提交数据的时候,发现IDE提示让我们使用apply方法。
- commit:同步提交,commit将同步的把数据写入磁盘和内存缓存,并且有返回值。
- apply:异步提交,会把数据同步写入内存缓存,然后异步保存到磁盘,可能会失败,失败不会收到错误回调。
两者的区别:
- commit的效率会比apply慢一点。在一个进程中,如果在不关心提交结果是否成功的情况下,优先考虑apply方法。
- 都是原子性操作,但是原子的操作不同。commit的从数据提交到保存到内存后再保存到磁盘中,中间不可打断。而apply方法是将数据保存到内存后就可以返回了,异步执行保存到磁盘的操作,
源码分析
获取SharePreference对象
利用Context获取到SharePreference实例,ContextImpl是Context的实现类,实现了getSharedPreferences方法。
- 因为SharedPreferences是支持自定义文件名的,所以这里利用了ArrayMap<File, SharedPreferencesImpl>来缓存不同文件对应的SharedPreferencesImpl对象。一个File文件对应一个SharePreference对象。
- getSharedPreferencesCacheLocked(),获取缓存的ArrayMap<File, SharedPreferencesImpl>对象,没有则创建一个。
@Override public SharedPreferences getSharedPreferences(File file, int mode) { SharedPreferencesImpl sp; synchronized (ContextImpl.class) { //获取缓存的map final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); //拿到对应的文件的SharePreference sp = cache.get(file); if (sp == null) { checkMode(mode); if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) { if (isCredentialProtectedStorage() && !getSystemService(UserManager.class) .isUserUnlockingOrUnlocked(UserHandle.myUserId())) { throw new IllegalStateException("SharedPreferences in credential encrypted " + "storage are not available until after user is unlocked"); } } //没有缓存,创建SharedPreferencesImpl对象,并保存到缓存中 sp = new SharedPreferencesImpl(file, mode); cache.put(file, sp); return sp; } } if ((mode & Context.MODE_MULTI_PROCESS) != 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { // If somebody else (some other process) changed the prefs // file behind our back, we reload it. This has been the // historical (if undocumented) behavior. sp.startReloadIfChangedUnexpectedly(); } return sp; } @GuardedBy("ContextImpl.class") private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() { if (sSharedPrefsCache == null) { sSharedPrefsCache = new ArrayMap<>(); } final String packageName = getPackageName(); ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName); if (packagePrefs == null) { packagePrefs = new ArrayMap<>(); sSharedPrefsCache.put(packageName, packagePrefs); } return packagePrefs; } 复制代码
SharedPreferencesImpl是SharedPreferences接口的实现类,实现了commit和apply方法。先看看SharedPreferencesImpl的构造方法。会异步调用一个startLoadFromDisk的方法,作用是从磁盘中把SharePreference文件里面保存的xml信息读取到内存中,并保存到Map里面。
SharedPreferencesImpl(File file, int mode) { mFile = file; mBackupFile = makeBackupFile(file); mMode = mode; mLoaded = false; mMap = null; mThrowable = null; startLoadFromDisk(); } private void startLoadFromDisk() { synchronized (mLock) { mLoaded = false; } new Thread("SharedPreferencesImpl-load") { public void run() { loadFromDisk(); } }.start(); } private void loadFromDisk(){ //省略部分代码 try { stat = Os.stat(mFile.getPath()); if (mFile.canRead()) { BufferedInputStream str = null; try { str = new BufferedInputStream( new FileInputStream(mFile), 16 * 1024); //进行xml解析 map = (Map<String, Object>) XmlUtils.readMapXml(str); } catch (Exception e) { Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e); } finally { IoUtils.closeQuietly(str); } } } catch (ErrnoException e) { // An errno exception means the stat failed. Treat as empty/non-existing by // ignoring. } catch (Throwable t) { thrown = t; } } //省略部分代码。 //将解析结果保存的map进行赋值 if (map != null) { mMap = map; mStatTimestamp = stat.st_mtim; mStatSize = stat.st_size; } 复制代码
读取数据
例如SharedPreferencesImpl的实现getString()方法,是直接从内存中的mMap直接就把数据读取出来,并没有涉及到磁盘操作。(恍然大悟,以前以为读取数据也要去读取file文件)
@Override @Nullable public String getString(String key, @Nullable String defValue) { synchronized (mLock) { awaitLoadedLocked(); String v = (String)mMap.get(key); return v != null ? v : defValue; } } 复制代码
保存数据
EditorImpl类实现了Editor接口。apply和commit都会调用commitToMemory方法,将数据保存到内存中,后面调用enqueueDiskWrite将数据保存到磁盘中。
//临时缓存多个key的数据,后面提交数据的时候,就遍历这个map就行 private final Map<String, Object> mModified = new HashMap<>(); //CountDownLatch,等待直到保存到磁盘的操作完成。 final CountDownLatch writtenToDiskLatch = new CountDownLatch(1); //在当前线程直接写文件,调用await,同步等待,最后返回操作的结果result @Override public boolean commit() { long startTime = 0; if (DEBUG) { startTime = System.currentTimeMillis(); } //保存到内存中 MemoryCommitResult mcr = commitToMemory(); //保存到磁盘 SharedPreferencesImpl.this.enqueueDiskWrite( mcr, null /* sync write on this thread okay */); try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException e) { return false; } finally { if (DEBUG) { Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration + " committed after " + (System.currentTimeMillis() - startTime) + " ms"); } } notifyListeners(mcr); return mcr.writeToDiskResult; } //异步等待保存操作,无法获取操作的结果 @Override public void apply() { final long startTime = System.currentTimeMillis(); //保存到内存中 final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { @Override public void run() { try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } if (DEBUG && mcr.wasWritten) { Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration + " applied after " + (System.currentTimeMillis() - startTime) + " ms"); } } }; QueuedWork.addFinisher(awaitCommit); Runnable postWriteRunnable = new Runnable() { @Override public void run() { awaitCommit.run(); QueuedWork.removeFinisher(awaitCommit); } }; SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); // Okay to notify the listeners before it's hit disk // because the listeners should always get the same // SharedPreferences instance back, which has the // changes reflected in memory. notifyListeners(mcr); } 复制代码
多进程中的SharePreference
上面讲的默认是单进程中的SharePreference,读取操作是直接从内存中的Map读取的,不涉及IO操作。如果是在多进程中的话,不同进程之间的内存并不是共享的,这个时候读写同一个SharePreference就会出现问题了。比如多个进程对同一个sharedpreference进行修改,总会有一个进程获取到的结果不是实时修改后的结果。
解决方法:推荐使用ContentProvider来处理多进程间的文件共享。
ContentProvider的特点:
- ContentProvider内部的同步机制会防止多个进程同时访问,避免数据冲突。
- ContentProvider的数据源,并不是只能选择数据库,其实核心操作就在update()和query()这两个操作,里面操作存取的数据源其实可以根据我们需要,替换成文件,也可以换成SharedPreferences。
所以我们可以使用ContentProvider做了一下中间媒介,让它帮我们实现多进程同步机制,里面操作的数据改成SharedPreferences来实现。这样的话就可以实现了跨进程访问SharePreference。
下面简单地写一个demo,读取的时候只需要传进相应的uri就行了。比如下面的代码,path字段的第二个是fileName,第三个是key值。
public class MultiProcessSharePreference extends ContentProvider{ @Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { //获取xml的文件名,默认取path字段的第一个 Log.d(TAG, "query: uri:" + uri); String tableName = uri.getPathSegments().get(0); String name = uri.getPathSegments().get(1); String key = uri.getPathSegments().get(2); Log.d(TAG, "query: tableName:" + tableName); Log.d(TAG, "query: fileName:" + name); Log.d(TAG, "query: key:" + key); //创建sharedPreferences对象 SharedPreferences sharedPreferences = getContext().getSharedPreferences(name, Context.MODE_PRIVATE); //创建一个cursor对象 MatrixCursor cursor = null; switch (uriMatcher.match(uri)) { case CODE_PREFERENCE_STRING: String value = sharedPreferences.getString(key, "默认值"); cursor = new MatrixCursor(PREFERENCE_COLUMNS, 1); MatrixCursor.RowBuilder rowBuilder = cursor.newRow(); rowBuilder.add(value); break; default: Log.d(TAG, "query: Uri No Match"); } return cursor; } } 复制代码
- MatrixCursor: 如果需要一个cursor而没有一个现成的cursor的话,那么可以使用MatrixCursor实现一个虚拟的表。MatrixCursor.RowBuilder是用来添加Row数据的,通过rowBuilder的add方法,就可以把数值添加到行里面了。使用场景:比如ContentProvider的query方法是返回一个cursor类型的数据,而数据源用的是SharePreference,这个时候就可以利用MatrixCursor。MartixCursor本质上是用一个一位数据来模拟一个二维数据,根据行值和列值就可以找到对应的数据了。
MatrixCursor的源码解析: blog.csdn.net/zhang_jun_l…
//定义每一列的字段名字 public static final String COLUMN_VALUE = "value"; //创建一个字符数组,字符数组的值对应着表的字段 private static String[] PREFERENCE_COLUMNS = {COLUMN_VALUE}; //构造一个MatrixCursor对象 MatrixCursor cursor = new MatrixCursor(PREFERENCE_COLUMNS, 1); //通过matrixCursor的addRow方法添加一行值 MatrixCursor.RowBuilder rowBuilder = cursor.newRow(); rowBuilder.add(value); 复制代码
-
优化一下的思路:
- 在ContentProvider里面加一个HashMap<String,SharePreference>进行一下缓存,key值是文件名,value是对应的SharePreference对象,这样的话,就不用每次都去加载SharePreference对象了。
- 在ContentProvider里面实现回调listener,在key值有变化的时候,进行通知订阅者。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Nginx源码阅读笔记-Master Woker进程模型
- JVM 源码分析之一个 Java 进程究竟能创建多少线程
- 优秀框架源码分析系列(一)让解耦更轻松!多进程组件化框架-ModularizationArchitecture
- 进程:进程生命周期
- Python 知识巩固:通过主进程带起多个子进程实现多进程执行逻辑
- Python 中子进程与父进程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Web Data Mining
Bing Liu / Springer / 2011-6-26 / CAD 61.50
Web mining aims to discover useful information and knowledge from Web hyperlinks, page contents, and usage data. Although Web mining uses many conventional data mining techniques, it is not purely an ......一起来看看 《Web Data Mining》 这本书的介绍吧!