在android中,对于一些少量的数据,会使用SharedPreferences进行key–value的Sp存储,ok那么对于Sp我们的一些源码层的了解必不可少:
首先,直接抛出Api来进行逐步的源码理解:
//存储数据
/** * 获取默认的Sp * name: context.getPackageName() + "_preferences" * mode: Context.MODE_PRIVATE */
SharedPreferences res = PreferenceManager.getDefaultSharedPreferences(context);
//or context.SharedPreferences.geSharedPreferences(name,mode)
Editor currentEditor = res.edit();
currentEditor.putInt(key, val);
currentEditor.commit();
//or currentEditor.apply();
取数据:
/** * 获取默认的Sp * name: context.getPackageName() + "_preferences" * mode: Context.MODE_PRIVATE */
SharedPreferences res = PreferenceManager.getDefaultSharedPreferences(context);
//or context.SharedPreferences.geSharedPreferences(name,mode)
res.putInt(key, val);
直接来看源码:
1.SharedPreferences.geSharedPreferences(name,mode)
Sp的获取在Context子类ContextImpl中实现:
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
// At least one application in the world actually passes in a null
// name. This happened to work because when we generated the file name
// we would stringify it to "null.xml". Nice.
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
if (file == null) {
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
在上面的代码中 file = getSharedPreferencesPath(name);通过传入的name来创建file,然后在mSharedPrefsPaths中以key–value来存储文件,接着看下一步的实现getSharedPreferences(File file, int mode)
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
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");
}
}
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;
}
在这里实现了Sp实例化对象的return,其中Sp的对象获取进行非空的判断,首先从缓存中获取Sp的对象:cache.get(file);如果获取为空,则new SharedPreferencesImpl(file, mode);
所以:
在getSharedPreferences中并不是每次实例化Sp的对象,获取的均为一个对象而已;
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;
}
而以上代码 就是一些信息的存储之类;
2.Editor currentEditor = res.edit();
获取Sp的编辑器Editor:
@Override
public Editor edit() {
// TODO: remove the need to call awaitLoadedLocked() when
// requesting an editor. will require some work on the
// Editor, but then we should be able to do:
//
// context.getSharedPreferences(..).edit().putString(..).apply()
//
// ... all without blocking.
synchronized (mLock) {
awaitLoadedLocked();
}
//hrx: get the editor of Sp
return new EditorImpl();
}
在这里用到程序锁,所以可以看到Sp的存储是线性安全的,有耗时性的,所以Sp是耗时的线程安全的,所以
Sp需要在子线程中进行,不然会造成ANR,而且读取文件不能太大,不然效率不高
3. currentEditor.putInt(key, val);
现在是data写入文件的操作咯:
public final class EditorImpl implements Editor {
@GuardedBy("mEditorLock")
private final Map<String, Object> mModified = new HashMap<>();
@Override
public Editor putInt(String key, int value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
同样有程序锁为了保证线性安全,用mModified<就是一个Map吧>来存储data;这没有什么大的意义;
下面看commit,这是Sp提交的过程,在Sp的源码中比较核心:
@Override
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
//hrx: commit step 1:
MemoryCommitResult mcr = commitToMemory();
//hrx: commit step 2:
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;
}
可以看到Sp的commit过程右两个步骤,就是
//hrx: commit step 1:
MemoryCommitResult mcr = commitToMemory();
//hrx: commit step 2:
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null);
最后刷新 返回提交的boolean来获取存储成功与失败;
首先来看MemoryCommitResult mcr = commitToMemory():
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
List<String> keysModified = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
Map<String, Object> mapToWriteToDisk;
synchronized (SharedPreferencesImpl.this.mLock) {
// We optimistically don't make a deep copy until
// a memory commit comes in when we're already
// writing to disk.
if (mDiskWritesInFlight > 0) {
// We can't modify our mMap as a currently
// in-flight write owns it. Clone it before
// modifying it.
// noinspection unchecked
mMap = new HashMap<String, Object>(mMap);
}
mapToWriteToDisk = mMap;
mDiskWritesInFlight++;
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
keysModified = new ArrayList<String>();
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (mEditorLock) {
boolean changesMade = false;
if (mClear) {
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
mClear = false;
}
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// "this" is the magic value for a removal mutation. In addition,
// setting a value to "null" for a given key is specified to be
// equivalent to calling remove on that key.
if (v == this || v == null) {
if (!mapToWriteToDisk.containsKey(k)) {
continue;
}
mapToWriteToDisk.remove(k);
} else {
if (mapToWriteToDisk.containsKey(k)) {
Object existingValue = mapToWriteToDisk.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mapToWriteToDisk.put(k, v);
}
changesMade = true;
if (hasListeners) {
keysModified.add(k);
}
}
mModified.clear();
if (changesMade) {
mCurrentMemoryStateGeneration++;
}
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
在上面代码中初始化一部分数据结构,然后遍历之前put data的集合Modified,然后将其放入新初始化的map中,但是需要注意:
if (mClear) {
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
mClear = false;
}
在实现之前会有map的clear操作,所以
在SP的存储过程中是清空之后的存储,就是缩每次储存的都是新data,不是增量的存储;
继续看 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null):
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
//hrx: 写入
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
首先看到的是一个runnable,然后开子线程进行写入操作,写入分为写数据和写锁,写操作需要先获得mWritingToDiskLock,也就是写锁。然后执行mDiskWritesInFlight–,表示正在等待写的操作减少1。最后判断postWriteRunnable是否为null,调用commit时它为null,而调用apply时它不为null。
@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);
}
但是提交操作commit和apply同样右缩区别;所以
commit的写操作是在调用线程中执行的,而apply内部是用一个单线程的线程池实现的,因此写操作是在子线程中执行的。