内容简介:复习了下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 中子进程与父进程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
GUI设计禁忌2.0
Jeff Johnson / 盛海艳 等 / 机械工业出版社 / 2008 / 49.00元
本书描述软件开发人员在设计图形用户界面(GUI)时经常犯的“禁忌”,并提出避免这些错误的基本原则和理论依据。本书将GUI禁忌分为7种类型:GUI控件禁忌、导航禁忌、文字禁忌、图形设计和布局禁忌、交互禁忌、响应性禁忌以及管理禁忌,并分别进行详述。 本书编排独特,条理清晰,针对性极强,是不可多得的GUI设计优秀资源。本书适合软件开发人员、web站点设计人员、开发经理、用户界面设计人员等阅读。一起来看看 《GUI设计禁忌2.0》 这本书的介绍吧!
JSON 在线解析
在线 JSON 格式化工具
RGB转16进制工具
RGB HEX 互转工具