第一章:SharedPreferences 源码学习
文章目录
- 第一章:SharedPreferences 源码学习
- Android SharedPreferences的缺陷
- MMKV、Jetpack DataStore、SharedPreferences对比
- SharedPreferences源码阅读
- 问题:
- 1. SP涉及哪些类,类和类之间的关系?
- 2. Content怎么创建SP?
- 3. SP怎么加载文件?
- 4. SP怎么保存数据到文件?
- 5. SP怎么完成数据的序列化?
- 6. SP是怎么读的?
- 7. SP怎么保证线程安全?
- 8. SP怎么监听数据的变化?
- 9. SP为什么大规模写入,效率低,耗时?
- 10. SP为什么会引起数据丢失?
- 11. SP使用注意事项。
- 12. SP怎么会引起ANR?
- 13. SP 名字为空时,会引起异常吗?
- 14. SP版本大于等于24 N时,不支持MODE_WORLD_READABLE、MODE_WORLD_WRITEABLE模式
- 15. ContextImpl 怎么做的缓存?
- 16. SP 怎么支持文件备份的?
- 17. SP怎么解决内存泄漏的?
- SP源码学习总结
Android SharedPreferences的缺陷
- 引起ANR
- 引起卡顿
- 全部读写到内存
- 数据丢失
- 不能跨进程
MMKV、Jetpack DataStore、SharedPreferences对比
功能 | MMKV | Jetpack DataStore | SharedPreferences |
---|---|---|---|
是否阻塞主线程 | 否 | 否 | 是 |
是否线程安全 | 是 | 是 | 是 |
是否支持跨进程 | 是 | 否 | 否 |
是否支持 protocol buffers | 是 | 是 | 否 |
是否类型安全 | 否 | 是 | 否 |
是否能监听数据变化 | 否 | 是 | 是 |
DataStore:稳定性
MMKV:效率
SharedPreferences源码阅读
问题:
1. SP涉及哪些类,类和类之间的关系?
android.content.SharedPreferences
android.content.SharedPreferences.Editor
android.content.SharedPreferences.OnSharedPreferenceChangeListener
android.app.SharedPreferencesImpl
android.content.Context
android.content.ContextWrapper
android.app.ContextImpl
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HoteALe7-1644676177858)(E:\Android开发进阶:80集框架源码解析,深入分析Android底层原理\第一章:K-V数据持久化\SharedPreferences类图.png)]
SharedPreferences 作用
- 读取键值对的值
getAll
getString
getStringSet
getInt
getLong
getFloat
getBoolean
contains
- 获取SharedPreferences.Editor 对象,SharedPreferences.Editor进行键值的写
Editor edit();
- 注册和反注册SP内容改变的监听器
registerOnSharedPreferenceChangeListener
unregisterOnSharedPreferenceChangeListener
SharedPreferences.Editor 作用
- 提供写入、修好、删除取键值对的值的接口
putString
putStringSet
putInt
putLong
putFloat
putBoolean
remove
clear
- 提供将修改的后值写入文件接口
commit 同步写入
apply 异步写入
OnSharedPreferenceChangeListener 作用
- SP内容改变的监听器
onSharedPreferenceChanged
SharedPreferencesImpl
SharedPreferences 的实现类
Context 获取SP
public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);
ContextWrapper Context 的代理类
ContextImpl Context 的实现类
实现getSharedPreferences方法。
@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);
}
2. Content怎么创建SP?
SP的简单使用
SharedPreferences sp = getSharedPreferences("test", MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("test", "test111");
editor.putString("All", "All");
editor.commit();
查看SP的xml文件。
![img](file:///C:\Users\navy\AppData\Roaming\Tencent\Users\897304074\TIM\WinTemp\RichOle\ASLCEXEKaTeX parse error: Expected 'EOF', got '}' at position 7: F41WPZ}̲CIZ~U]X.png)
通过ContextWrapper 获取SharedPreferences对象。
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return mBase.getSharedPreferences(name, mode);
}
Activity、ContextWrapper 、ContextImpl 、Context关联
ContextImpl 创建getSharedPreferences
@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);
}
ContextImpl 创建SharedPreferencesImpl
@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");
}
}
// 创建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;
}
SharedPreferencesImpl初始化
SharedPreferencesImpl(File file, int mode) {
mFile = file;
// 创建回滚文件
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
startLoadFromDisk();
}
3. SP怎么加载文件?
SharedPreferencesImpl 开启线程加载文件
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void startLoadFromDisk() {
synchronized (mLock) {
// 加对象锁,设置未加载
mLoaded = false;
}
// 开启线程加载文件
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
SharedPreferencesImpl 加载文件
private void loadFromDisk() {
//
synchronized (mLock) {
if (mLoaded) {
return;
}
// 文件回滚
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
// Debugging
if (mFile.exists() && !mFile.canRead()) {
Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
}
Map<String, Object> map = null;
StructStat stat = null;
Throwable thrown = null;
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
// 使用BufferedInputStream、FileInputStream 读取文件
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;
}
synchronized (mLock) {
mLoaded = true;
mThrowable = thrown;
// It's important that we always signal waiters, even if we'll make
// them fail with an exception. The try-finally is pretty wide, but
// better safe than sorry.
try {
if (thrown == null) {
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();
}
}
// In case of a thrown exception, we retain the old map. That allows
// any open editors to commit and store updates.
} catch (Throwable t) {
mThrowable = t;
} finally {
mLock.notifyAll();
}
}
}
4. 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();
}
return new EditorImpl();
}
进行put操作
@Override
public Editor putString(String key, @Nullable String value) {
synchronized (mEditorLock) {
// Map<String, Object> mModified 要修改的值
mModified.put(key, value);
return this;
}
}
进行commit操作 同步写入至内存中
@Override
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
// 修改 SharedPreferencesImpl mMap对象
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;
}
写入至内存 commitToMemory
获得 要写入文件的K-V mapToWriteToDisk。
- 使用mMap初始化mapToWriteToDisk
- 将mModified的的K-V,填充到mapToWriteToDisk
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
boolean keysCleared = false;
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
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();
}
keysCleared = true;
mClear = false;
}
// 便利要修改的mModified Map,
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.
// v 设置为null时,移除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, keysCleared, keysModified,
listeners, mapToWriteToDisk);
}
private static class MemoryCommitResult {
final long memoryStateGeneration;
final boolean keysCleared;
@Nullable final List<String> keysModified;
@Nullable final Set<OnSharedPreferenceChangeListener> listeners;
final Map<String, Object> mapToWriteToDisk;
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
@GuardedBy("mWritingToDiskLock")
volatile boolean writeToDiskResult = false;
boolean wasWritten = false;
放入磁盘写入队列 SharedPreferencesImpl.this.enqueueDiskWrite
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
// 是否是同步 commit
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
// 写入文件
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 队列,等待执行
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
QueuedWork
/** {@link #getHandler() Lazily} created handler */
@GuardedBy("sLock")
private static Handler sHandler = null; // HandlerThread的handle
/** Work queued via {@link #queue} */
@GuardedBy("sLock")
private static LinkedList<Runnable> sWork = new LinkedList<>();
// 添加至sWork
public static void queue(Runnable work, boolean shouldDelay) {
Handler handler = getHandler();
synchronized (sLock) {
sWork.add(work);
if (shouldDelay && sCanDelay) {
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
private static class QueuedWorkHandler extends Handler {
static final int MSG_RUN = 1;
QueuedWorkHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
if (msg.what == MSG_RUN) {
// 执行消息
processPendingWork();
}
}
}
写入内容至文件
@GuardedBy("mWritingToDiskLock")
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
long startTime = 0;
long existsTime = 0;
long backupExistsTime = 0;
long outputStreamCreateTime = 0;
long writeTime = 0;
long fsyncTime = 0;
long setPermTime = 0;
long fstatTime = 0;
long deleteTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
boolean fileExists = mFile.exists();
if (DEBUG) {
existsTime = System.currentTimeMillis();
// Might not be set, hence init them to a default value
backupExistsTime = existsTime;
}
// Rename the current file so it may be used as a backup during the next read
if (fileExists) {
boolean needsWrite = false;
// Only need to write if the disk state is older than this commit
if (mDiskStateGeneration < mcr.memoryStateGeneration) {
if (isFromSyncCommit) {
needsWrite = true;
} else {
synchronized (mLock) {
// No need to persist intermediate states. Just wait for the latest state to
// be persisted.
if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
needsWrite = true;
}
}
}
}
if (!needsWrite) {
mcr.setDiskWriteResult(false, true);
return;
}
boolean backupFileExists = mBackupFile.exists();
if (DEBUG) {
backupExistsTime = System.currentTimeMillis();
}
// fileExists 存在,备份文件不存在,将fileExists改名为mBackupFile
if (!backupFileExists) {
if (!mFile.renameTo(mBackupFile)) {
Log.e(TAG, "Couldn't rename file " + mFile
+ " to backup file " + mBackupFile);
mcr.setDiskWriteResult(false, false);
return;
}
} else {
mFile.delete();
}
}
// Attempt to write the file, delete the backup and return true as atomically as
// possible. If any exception occurs, delete the new file; next time we will restore
// from the backup.
try {
// 创建FileOutputStream
FileOutputStream str = createFileOutputStream(mFile);
if (DEBUG) {
outputStreamCreateTime = System.currentTimeMillis();
}
if (str == null) {
mcr.setDiskWriteResult(false, false);
return;
}
// 将mapToWriteToDisk 写入xml文件
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
writeTime = System.currentTimeMillis();
FileUtils.sync(str);
fsyncTime = System.currentTimeMillis();
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
if (DEBUG) {
setPermTime = System.currentTimeMillis();
}
try {
final StructStat stat = Os.stat(mFile.getPath());
synchronized (mLock) {
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
}
} catch (ErrnoException e) {
// Do nothing
}
if (DEBUG) {
fstatTime = System.currentTimeMillis();
}
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
if (DEBUG) {
deleteTime = System.currentTimeMillis();
}
mDiskStateGeneration = mcr.memoryStateGeneration;
// 设置wasWritten 为true,result为true
mcr.setDiskWriteResult(true, true);
if (DEBUG) {
Log.d(TAG, "write: " + (existsTime - startTime) + "/"
+ (backupExistsTime - startTime) + "/"
+ (outputStreamCreateTime - startTime) + "/"
+ (writeTime - startTime) + "/"
+ (fsyncTime - startTime) + "/"
+ (setPermTime - startTime) + "/"
+ (fstatTime - startTime) + "/"
+ (deleteTime - startTime));
}
long fsyncDuration = fsyncTime - writeTime;
mSyncTimes.add((int) fsyncDuration);
mNumSync++;
if (DEBUG || mNumSync % 1024 == 0 || fsyncDuration > MAX_FSYNC_DURATION_MILLIS) {
mSyncTimes.log(TAG, "Time required to fsync " + mFile + ": ");
}
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// Clean up an unsuccessfully written file
if (mFile.exists()) {
if (!mFile.delete()) {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
}
}
mcr.setDiskWriteResult(false, false);
}
补充:apply流程
@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);
}
5. SP怎么完成数据的序列化?
传统IO读写 + XML解析
IO技术
BufferedInputStream、FileInputStream 读
FileOutputStream 写
XML解析
XmlUtils.readMapXml(str)
mlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
6. SP是怎么读的?
SharedPreferencesImpl
@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
7. SP怎么保证线程安全?
private final Object mLock = new Object();
加载文件时
先获取SharedPreferencesImpl的mLock锁,设置未加载
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void startLoadFromDisk() {
synchronized (mLock) {
// 加对象锁,设置未加载
mLoaded = false;
}
// 开启线程加载文件
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
非主线程加载文件
private void loadFromDisk() {
// 先获取SharedPreferencesImpl的mLock锁,将备份文件重命名为 mFile
synchronized (mLock) {
if (mLoaded) {
return;
}
// 文件回滚
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
```
// 文件内容加载完毕
```
// 再获取SharedPreferencesImpl的mLock锁,将mLoaded设置为true,表示加载成功。并且通知其他
synchronized (mLock) {
mLoaded = true;
mThrowable = thrown;
// It's important that we always signal waiters, even if we'll make
// them fail with an exception. The try-finally is pretty wide, but
// better safe than sorry.
try {
if (thrown == null) {
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();
}
}
// In case of a thrown exception, we retain the old map. That allows
// any open editors to commit and store updates.
} catch (Throwable t) {
mThrowable = t;
} finally {
// 唤醒在该对象上等待的所有线程。
mLock.notifyAll();
}
}
}
读内容时
先获取SharedPreferencesImpl的mLock锁,并且等待文件加载完毕才能进行读操作。
@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
@GuardedBy("mLock")
private void awaitLoadedLocked() {
if (!mLoaded) {
// Raise an explicit StrictMode onReadFromDisk for this
// thread, since the real read will be in a different
// thread and otherwise ignored by StrictMode.
BlockGuard.getThreadPolicy().onReadFromDisk();
}
while (!mLoaded) {
try {
// 如果没有加载成功,就让当期线程进入等待状态
mLock.wait();
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
写内容时
获得Editor对象时
先获取SharedPreferencesImpl的mLock锁,并且等待文件加载完毕才能 创建EditorImpl对象
@Override
public Editor edit() {
//
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
EditorImpl 进行put操作时。
不对SharedPreferencesImpl 的mMap进行修改。将要修改的值存放在mModified中,不影响SP的读。使用mEditorLock保证put的线程安全。
public final class EditorImpl implements Editor {
private final Object mEditorLock = new Object();
@Override
public Editor putString(String key, @Nullable String value) {
synchronized (mEditorLock) {
// Map<String, Object> mModified 要修改的值
mModified.put(key, value);
return this;
}
}
commit/apply,写入内存 commitToMemory时。
先获取SharedPreferencesImpl.this.mLock锁,保证不影响get、put操作。得到mMap的值。
获取EditorImpl mEditorLock锁,保证mModified的值正确。
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
boolean keysCleared = false;
List<String> keysModified = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
Map<String, Object> mapToWriteToDisk;
synchronized (SharedPreferencesImpl.this.mLock) {
if (mDiskWritesInFlight > 0) {
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();
}
keysCleared = true;
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, keysCleared, keysModified,
listeners, mapToWriteToDisk);
}
commit时
MemoryCommitResult 使用CountDownLatch 保证setDiskWriteResult 方法只被调用了一次。
private static class MemoryCommitResult {
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
void setDiskWriteResult(boolean wasWritten, boolean result) {
this.wasWritten = wasWritten;
writeToDiskResult = result;
writtenToDiskLatch.countDown();
}
@Override
public boolean commit() {
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;
}
放入磁盘写入队列 SharedPreferencesImpl.this.enqueueDiskWrite时
获取mLock说,修改mDiskWritesInFlight值。
- mDiskWritesInFlight ++ 表示内存的次数
- mDiskWritesInFlight-- 表示写入磁盘
isFromSyncCommi
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) {
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) {
// 只调用一次commit 时,直接执行writeToDiskRunnable
writeToDiskRunnable.run();
return;
}
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
writeToFile 时,调用setDiskWriteResult,修改结果
@GuardedBy("mWritingToDiskLock")
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
总结
- 使用技术synchronized、[notifyAll, wait]、CountDownLatch(countDown、await)分布式相关技术
- 使用mLock保证SP 对象的值安全,如mLoaded、mMap
@GuardedBy("mLock")
private Map<String, Object> mMap;
@GuardedBy("mLock")
private Throwable mThrowable;
@GuardedBy("mLock")
private int mDiskWritesInFlight = 0;
@GuardedBy("mLock")
private boolean mLoaded = false;
@GuardedBy("mLock")
private StructTimespec mStatTimestamp;
@GuardedBy("mLock")
private long mStatSize;
@GuardedBy("mLock")
private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
- 使用mWritingToDiskLock 保证SP 与磁盘相关的值得正确性
/** Latest memory state that was committed to disk */
@GuardedBy("mWritingToDiskLock")
private long mDiskStateGeneration;
/** Time (and number of instances) of file-system sync requests */
@GuardedBy("mWritingToDiskLock")
private final ExponentiallyBucketedHistogram mSyncTimes = new ExponentiallyBucketedHistogram(16);
- 使用mEditorLock 保证EditorImpl的值得正确性
@GuardedBy("mEditorLock")
private final Map<String, Object> mModified = new HashMap<>();
@GuardedBy("mEditorLock")
private boolean mClear = false;
-
mLock.wait() 等待文件加载、mLock.notifyAll() 通知文件加载
-
使用mWritingToDiskLock,保存一次只能进行一次磁盘写入。
-
使用CountDownLatch 保证setDiskWriteResult 方法只被调用了一次。
8. SP怎么监听数据的变化?
注册和添加监听器
@GuardedBy("mLock")
private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
@Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(mLock) {
mListeners.put(listener, CONTENT);
}
}
@Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(mLock) {
mListeners.remove(listener);
}
}
写入内存时。获取listeners集合
// Returns true if any changes were made
private MemoryCommitResult commitToMemory() {
Set<OnSharedPreferenceChangeListener> listeners = null;
synchronized (SharedPreferencesImpl.this.mLock) {
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
keysModified = new ArrayList<String>();
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
}
return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,
listeners, mapToWriteToDisk);
}
commit/apply 时,通知监听器发生变化
@Override
public boolean commit() {
...
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
通知监听器发生变化
private void notifyListeners(final MemoryCommitResult mcr) {
if (mcr.listeners == null || (mcr.keysModified == null && !mcr.keysCleared)) {
return;
}
if (Looper.myLooper() == Looper.getMainLooper()) {
if (mcr.keysCleared && Compatibility.isChangeEnabled(CALLBACK_ON_CLEAR_CHANGE)) {
for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
if (listener != null) {
listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, null);
}
}
}
for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
final String key = mcr.keysModified.get(i);
for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
if (listener != null) {
listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
}
}
}
} else {
// Run this function on the main thread.
ActivityThread.sMainThreadHandler.post(() -> notifyListeners(mcr));
}
}
}
9. SP为什么大规模写入,效率低,耗时?
- SP要加载全部文件内容进入内存,才能进行读写操作。如果文件内容非常多时,就非常耗时。
- SP采用传统的IO方式,进行文件读写。效率低
- SP采用XMl的方式进行序列化,效率低。
10. SP为什么会引起数据丢失?
- SP apply采用异步操作,当异步操作未完成时,程序出现崩溃,就会造成数据丢失。
- SP writeToFile时,如果出现异常,会删除当前文件。
@GuardedBy("mWritingToDiskLock")
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
.....
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// Clean up an unsuccessfully written file
if (mFile.exists()) {
if (!mFile.delete()) {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
}
}
mcr.setDiskWriteResult(false, false);
}
11. SP使用注意事项。
-
获取SP完对象时,文件存在没有加载完毕的可能性。如果马上进行读写操作时,会阻塞主线程。
-
调用edit() 方法时,会创建新的EditorImpl。不要创建过多的EditorImpl对象,可能会出现内存抖动。
-
不要频繁的调用commit、apply进入磁盘操作。commit会阻塞主线程。commit、apply时,会调用enqueueDiskWrite方法,会调用 QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit),将写入磁盘任务放入QueuedWork中。ActivityThread会调用waitToFinish方法。
waitToFinish会调用processPendingWork。processPendingWork对sWork(LinkedList)进行遍历执行。
当writeToDiskRunnable过多时,或者writeToDiskRunnable非常耗时时,会造成主线程卡顿,影响启动速度,甚至引起ANR。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-reFUlmMq-1644676177863)(C:\Users\navy\AppData\Roaming\Typora\typora-user-images\image-20220212201214044.png)]
public static void waitToFinish() { try { processPendingWork(); } finally { StrictMode.setThreadPolicy(oldPolicy); } }
private static void processPendingWork() { long startTime = 0; if (DEBUG) { startTime = System.currentTimeMillis(); } synchronized (sProcessingWork) { LinkedList<Runnable> work; synchronized (sLock) { work = sWork; sWork = new LinkedList<>(); // Remove all msg-s as all work will be processed now getHandler().removeMessages(QueuedWorkHandler.MSG_RUN); } if (work.size() > 0) { for (Runnable w : work) { w.run(); } if (DEBUG) { Log.d(LOG_TAG, "processing " + work.size() + " items took " + +(System.currentTimeMillis() - startTime) + " ms"); } } } }
12. SP怎么会引起ANR?
参考SP使用注意事项。
13. SP 名字为空时,会引起异常吗?
不会。
-
当传达的name时,版本号小于19时,会将name变成 "null"字符串。
@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);
}
-
获取SharedPreferencesPath路径时,及时name为null,name + ".xml"会变成 null.xml
ContextImpl
@Override
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
14. SP版本大于等于24 N时,不支持MODE_WORLD_READABLE、MODE_WORLD_WRITEABLE模式
MODE_WORLD_READABLE 跨应用读
MODE_WORLD_WRITEABLE 跨应用写
private void checkMode(int mode) {
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
if ((mode & MODE_WORLD_READABLE) != 0) {
throw new SecurityException("MODE_WORLD_READABLE no longer supported");
}
if ((mode & MODE_WORLD_WRITEABLE) != 0) {
throw new SecurityException("MODE_WORLD_WRITEABLE no longer supported");
}
}
}
15. ContextImpl 怎么做的缓存?
ContextImpl 获取SP,根据包名、File做了SP的缓存。如果存在就不用创建SP。
class ContextImpl extends Context {
// packageName [file, SharedPreferencesImpl]
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
@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;
}
16. SP 怎么支持文件备份的?
初始化备份文件
@UnsupportedAppUsage
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
static File makeBackupFile(File prefsFile) {
return new File(prefsFile.getPath() + ".bak");
}
加载文件时,如果备份文件存在。删除当前文件,将备份文件改名为当前文件
private void loadFromDisk() {
synchronized (mLock) {
if (mLoaded) {
return;
}
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
写入磁盘时
如果备份文件不存在,将当前文件重命名为备份文件。写入成功时,删除备份文件。
@GuardedBy("mWritingToDiskLock")
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
// Rename the current file so it may be used as a backup during the next read
if (fileExists) {
boolean needsWrite = false;
boolean backupFileExists = mBackupFile.exists();
if (!backupFileExists) {
if (!mFile.renameTo(mBackupFile)) {
Log.e(TAG, "Couldn't rename file " + mFile
+ " to backup file " + mBackupFile);
mcr.setDiskWriteResult(false, false);
return;
}
} else {
mFile.delete();
}
}
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
17. SP怎么解决内存泄漏的?
@GuardedBy("mLock")
private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
SP源码学习总结
SP 优化点
- 使用NIO、mmap代码传统的IO操作
- 使用protocol buffer代替xml
SP 架构总结
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-roxZrZ3l-1644676177864)(E:\Android开发进阶:80集框架源码解析,深入分析Android底层原理\第一章:K-V数据持久化\SharedPreferences类图.png)]
- 单一职责原则 读写分离,内存写、磁盘写分离。
SharedPreferencesImpl 辅助读。Editor负责临时内存写。
- ContextWrapper、ContextImp、 Context 装饰者模式