SharedPreferences源码分析一.SharedPreferences的创建:1.获取SharedPreferences对象1)get
使用SharedPreferences需要调用getSharedPreferences方法获取SharedPreferences对象。
该方法是ContextWrapper对象的方法。
ContextWrapper.java中的相关代码:
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return mBase.getSharedPreferences(name, mode);
}
从代码中可以看出,getSharedPreferences方法内部调用Context对象的getSharedPreferences方法。而Context.java是抽象类,它的实现类为ContextImpl.java。因此该Context对象为ContextImpl对象。
ContextImpl.java中的相关代码:
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
// 当目标Android版本小于Android4.4时,若传入的名字为空,则默认它的名字为“null”
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
// 同步锁,锁对象为ContextImpl.class
synchronized (ContextImpl.class) {
// 若第一次调用getSharedPreferences方法,则创建mSharedPrefsPaths对象
if (mSharedPrefsPaths == null) {
// 该对象的key用于存储SharedPreferences的名字,value用来存储保存了SharedPreferences对应路径的File对象
// 该对象用作缓存,实现从名字到生成路径的映射
mSharedPrefsPaths = new ArrayMap();
}
// 根据名字获取File对象
file = mSharedPrefsPaths.get(name);
// 若返回对象为空
if (file == null) {
// getSharedPreferencesPath方法会根据名字创建包含对应路径的File对象
file = getSharedPreferencesPath(name);
// 保存SharedPreferences的名字和对应的文件
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
该方法最后又调用了一个重载的getSharedPreferences方法,并返回了SharedPreferences对象。
ContextImpl.java中的相关代码:
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
//同步锁,锁对象为ContextImpl.class
synchronized (ContextImpl.class) {
// 获取用于缓存的cache对象
// 详解在1)处
final ArrayMap cache = getSharedPreferencesCacheLocked();
// 根据file,从缓存中获取SharedPreferencesImpl对象
sp = cache.get(file);
// 若缓存中没有
if (sp == null) {
// checkMode方法用于对SharedPreferences的模式进行检查。
// 详解在2)处
checkMode(mode);
//若目标Android版本大于等于Android8.0
// 详解在3)处
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;
}
}
// 若目标Android版本在Android3.0之下,且参mode为MODE_MULTI_PROCESS模式
// 详解在4)处
if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
// 该方法会将硬盘中的数据再次加载一次
sp.startReloadIfChangedUnexpectedly();
}
return sp;
}
1)getSharedPreferencesCacheLocked方法
ContextImpl.java中的相关代码:
// 该注解表示只有当线程持有ContextImpl.class对象时,才能使用该变量。
@GuardedBy("ContextImpl.class")
private static ArrayMap<String, ArrayMap> ssharedPrefsCache;
// 该注解表示只有当线程持有ContextImpl.class对象时,才能调用该方法。
@GuardedBy("ContextImpl.class")
private ArrayMap getSharedPreferencesCacheLocked() {
// 若第一次调用该方法,则对全局变量sSharedPrefsCache进行初始化
if (sSharedPrefsCache == null) {
// 该对象用于缓存,实现从包名到SharedPreferences名再到缓存的SharedPreferences的映射
sSharedPrefsCache = new ArrayMap();
}
// 获取包名
final String packageName = getPackageName();
// 根据包名获取packagePrefs对象
ArrayMap packagePrefs = sSharedPrefsCache.get(packageName);
// 若缓存中没有
if (packagePrefs == null) {
// 则进行创建
packagePrefs = new ArrayMap();
// 添加到缓存中
sSharedPrefsCache.put(packageName, packagePrefs);
}
// 返回
return packagePrefs;
}
2)checkMode方法
ContextImpl.java中的相关代码:
private void checkMode(int mode) {
// 若目标Android版本大于等于Android7.0
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
// 若mode为MODE_WORLD_READABLE模式
if ((mode & MODE_WORLD_READABLE) != 0) {
throw new SecurityException("MODE_WORLD_READABLE no longer supported");
}
// 若mode为MODE_WORLD_WRITEABLE模式
if ((mode & MODE_WORLD_WRITEABLE) != 0) {
throw new SecurityException("MODE_WORLD_WRITEABLE no longer supported");
}
}
}
Android7.0以上系统不支持这两个模式,Android7.0之后统一替换为MODE_PRIVATE模式。
3)Android7.0引入了DirectBoot模式,即用户开机但未解锁前,应用只能访问安全区内的数据。 Android7.0将数据分成凭据保护存储区和设备保护存储区两部分。
凭据保护存储区(credential-protected):所有应用默认存储位置,仅在用户解锁设备后可用。
设备保护存储区(device-protected):一个新的存储位置,设备启动后随时都可以访问。
调用startReloadIfChangedUnexpectedly方法仅会再次从硬盘加载一次数据,因此跨进程时SharedPreferences中的数据可能会出错。
二.SharedPreferencesImpl类SharedPreferences是一个接口,它的具体实现是SharedPreferencesImpl类。因此最终获取的为SharedPreferencesImpl对象。
1.重要的全局变量SharedPreferencesImpl.java中的相关代码:
private final File mFile; // 用于保存file
private final File mBackupFile; // 备份文件
private final int mMode; //用于保存mode
private final Object mLock = new Object(); // 用于锁
private final Object mWritingToDiskLock = new Object(); // 写入时的锁
@GuardedBy("mLock")
private Map 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 mListeners =
new WeakHashMap(); //用于保存SharedPreferences的监听器
@GuardedBy("this")
private long mCurrentMemoryStateGeneration; // 记录内存变化的次数
@GuardedBy("mWritingToDiskLock")
private long mDiskStateGeneration;// 记录内存数据写入磁盘的次数
2. SharedPreferencesImpl的构造方法
SharedPreferencesImpl.java中的相关代码:
SharedPreferencesImpl(File file, int mode) {
// 保存file
mFile = file;
// 获取备份文件,详解在1)处
mBackupFile = makeBackupFile(file);
// 保存mode
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
// 从硬盘中加载数据,详解在2)处
startLoadFromDisk();
}
1)makeBackupFile方法
SharedPreferencesImpl.java中的相关代码:
static File makeBackupFile(File prefsFile) {
// 通过传入文件的路径,创建文件
return new File(prefsFile.getPath() + ".bak");
}
2)startLoadFromDisk方法
SharedPreferencesImpl.java中的相关代码:
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
// 创建并启动了一个名为“SharedPreferencesImpl-load”的线程
new Thread("SharedPreferencesImpl-load") {
public void run() {
// 从硬盘中加载数据,详解在3)处
loadFromDisk();
}
}.start();
}
3)loadFromDisk方法
SharedPreferencesImpl.java中的相关代码:
private void loadFromDisk() {
// 同步锁,锁对象为mLock
synchronized (mLock) {
// 双重锁,若已经加载过,则返回
if (mLoaded) {
return;
}
// 若备份文件存在,说明不是第一次创建该SharedPreferences
if (mBackupFile.exists()) {
// 删除原文件
mFile.delete();
// 将备份文件的内容复制到原文件,若操作成功,会删除备份文件
// 即使用备份文件作为原文件,但名字为原文件的名字
mBackupFile.renameTo(mFile);
}
}
// 用于临时保存磁盘中加载解析后的键值对数据
Map map = null;
// 用于保存文件的信息
StructStat stat = null;
// 用于保存加载数据中产生的异常
Throwable thrown = null;
try {
// 获取文件的信息
stat = Os.stat(mFile.getPath());
// 若文件可读
if (mFile.canRead())
BufferedInputStream str = null;
try {
// 创建缓存输入流,缓存大小为16 * 1024KB
str = new BufferedInputStream(
new FileInputStream(mFile), 16 * 1024);
// 将文件中的数据解析成键值对,保存在map中
map = (Map) 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;
}
// 同步锁,锁对象为mLock
synchronized (mLock) {
// 设置状态为加载完成
mLoaded = true;
mThrowable = thrown;
try {
// 若加载解析数据中没有发生异常
if (thrown == null) {
// 解析的数据不为空
if (map != null) {
// 将结果保存在全局变量中
mMap = map;
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
} else { // 若解析的数据为空,即文件中没有数据
// 对全局变量进行初始化
mMap = new HashMap();
}
}
} catch (Throwable t) {
mThrowable = t;
} finally {
// 释放锁,随机唤醒一个等待mLock锁的线程
mLock.notifyAll();
}
}
}
3.从SharedPreferences中获取数据
SharedPreferencesImpl类中提供很多不同的get方法,用来获取不同类型的数据。这些方法内部实现的原理是类似的。以getString方法为例。
SharedPreferencesImpl.java中的相关代码:
@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
// 同步锁 锁对象为mLock
synchronized (mLock) {
// 让当前的线程等待从磁盘加载数据的线程加载完数据。
// 详解在1)处
awaitLoadedLocked();
// 从全局变量mMap中获取数据,并进行强制转换
String v = (String)mMap.get(key);
// 若获取数据不存在,则返回默认值,否则返回获取的数据
return v != null ? v : defValue;
}
}
1)awaitLoadedLocked方法
SharedPreferencesImpl.java中的相关代码:
@GuardedBy("mLock")
private void awaitLoadedLocked() {
// 若从硬盘加载数据没有完成
if (!mLoaded) {
// 调用线程策略处理
BlockGuard.getThreadPolicy().onReadFromDisk();
}
// 若从硬盘加载数据没有完成,则循环执行
while (!mLoaded) {
try {
// 释放锁,等待
mLock.wait();
} catch (InterruptedException unused) {
}
}
// 若等待期间发生异常,则抛出异常
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
4.获取Editor对象
调用SharedPreferences对象的edit方法获取Editor对象,然后对Editor对象进行操作。
SharedPreferencesImpl.java中的相关代码:
@Override
public Editor edit() {
// 同步锁,锁对象为mLock
synchronized (mLock) {
// 让当前的线程等待从磁盘加载数据的线程加载完数据
awaitLoadedLocked();
}
// 创建EditorImpl对象并返回
return new EditorImpl();
}
三.EditorImpl类
Editor是一个接口,它的具体实现是EditorImpl类。因此最终获取的为EditorImpl对象。
EditorImpl类是SharedPreferencesImpl类的内部类。SharedPreferences中对数据的增、删、改都是通过调用Editor的相关方法来实现的。
SharedPreferencesImpl.java中的相关代码:
private final Object mEditorLock = new Object();// 锁
@GuardedBy("mEditorLock")
private final Map mModified = new HashMap();// 用于临时存储需要写入磁盘的数据或需要移除的数据,之后统一处理
@GuardedBy("mEditorLock")
private boolean mClear = false;// 用于表示是否清空SharedPreferences
2.添加数据
EditorImpl类中提供很多不同的put方法来添加不同类型的数据。这些方法内部实现的原理是类似的。以putString方法为例。
SharedPreferencesImpl.java中的相关代码:
@Override
public Editor putString(String key, @Nullable String value) {
// 同步锁,锁对象为mEditorLock
synchronized (mEditorLock) {
// 向全局变量mModified添加数据
mModified.put(key, value);
// 返回
return this;
}
}
3.清空数据
SharedPreferencesImpl.java中的相关代码:
@Override
public Editor clear() {
// 同步锁,锁对象为mEditorLock
synchronized (mEditorLock) {
// 清除标志位为true
mClear = true;
// 返回
return this;
}
}
4.删除数据
SharedPreferencesImpl.java中的相关代码:
@Override
public Editor remove(String key) {
// 同步锁,锁对象为mEditorLock
synchronized (mEditorLock) {
// 将全局变量mModified对应的value改为自身,表示这个键值对需要删除
mModified.put(key, this);
// 返回
return this;
}
}
5.提交数据到磁盘
EditorImpl中有apply和commit两种方法来实现将数据写入磁盘。apply为异步方法,commit方法为同步方法。
同步提交数据:commitSharedPreferencesImpl.java中的相关代码:
@Override
public boolean commit() {
// 对EditorImpl对象的操作(put、remove、clear等)进行整合处理
// 详解在1)处
MemoryCommitResult mcr = commitToMemory();
// 将数据写入磁盘
// 详解在3)处
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null);
try {
// 阻塞等待写入过程完成
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
} finally {
}
}
// 对注册当前SharedPreferences对象的监听器进行回调
notifyListeners(mcr);
// 返回
return mcr.writeToDiskResult;
}
1)commitToMemory方法
SharedPreferencesImpl.java中的相关代码:
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration; // 用于记录
List keysModified = null; // 用于存储发生变化的键
Set listeners = null; // 用于存储监听器
Map mapToWriteToDisk; // 用于记录需要写入磁盘的所有的数据
// 同步锁,锁对象为SharedPreferencesImpl.this.mLock
synchronized (SharedPreferencesImpl.this.mLock) {
// 若当前有线程在写入
if (mDiskWritesInFlight > 0) {
// 复制mMap,对新数据进行操作
mMap = new HashMap(mMap);
}
// 获取全局变量
mapToWriteToDisk = mMap;
// 当前进行写入操作的线程数加一
mDiskWritesInFlight++;
// SharedPreferences是否有监听器
boolean hasListeners = mListeners.size() > 0;
// 若有监听器
if (hasListeners) {
// 进行初始化
keysModified = new ArrayList();
listeners = new HashSet(mListeners.keySet());
}
// 同步锁,锁对象为mEditorLock
synchronized (mEditorLock) {
// 表示内存数据是否发生变化
boolean changesMade = false;
// 若用户调用clear方法清空数据
if (mClear) {
// 若写入的数据不为空,即有需要清除的数据
if (!mapToWriteToDisk.isEmpty()) {
// 表示内存数据发生变化
changesMade = true;
// 清空内存中的数据
mapToWriteToDisk.clear();
}
// 重置清除标志
mClear = false;
}
// 对提交到Editor中的数据进行遍历
for (Map.Entry e : mModified.entrySet()) {
// 获取键
String k = e.geTKEy();
// 获取值
Object v = e.getValue();
// 若值为空或自身,表示该键值对需要删除
if (v == this || v == null) {
// 若写入磁盘中的数据没有k这个键
// 说明之前没有添加k和其对应的v数据到磁盘中
// 本次删除是无效的
if (!mapToWriteToDisk.containsKey(k)) {
// 跳过本次操作
continue;
}
// 从打算写入磁盘的数据中移除
mapToWriteToDisk.remove(k);
} else { //若值不为空,也不为自身
//说明添加了新键值对或修改了键值对的值
// 若打算写入磁盘的数据包含k这个键,说明对值进行了修改
if (mapToWriteToDisk.containsKey(k)) {
// 获取k键之前的值
Object existingValue = mapToWriteToDisk.get(k);
// 若之前的值不为空,同时和现在的值相同
// 说明实际没有修改
if (existingValue != null && existingValue.equals(v)) {
// 跳过本次操作
continue;
}
}
// 将新添加的键值对添加到打算写入磁盘的数据中
mapToWriteToDisk.put(k, v);
}
// 表示内存数据发生变化
changesMade = true;
// 若有监听器
if (hasListeners) {
// 则对变换的键进行保存
keysModified.add(k);
}
}
// 内存整理结束,所有需要写入磁盘的数据保存在mapToWriteToDisk中
// 清空EditorImpl中临时存储的数据
mModified.clear();
// 若内存数据发生变换
if (changesMade) {
// 内存变换次数加一
mCurrentMemoryStateGeneration++;
}
// 赋值到方法内的局部变量
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
// 创建MemoryCommitResult对象并返回
// 详解在2)处
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
2)MemoryCommitResult类
MemoryCommitResult类是SharedPreferencesImpl类的静态内部类。用来保存对内存整理的结果。
SharedPreferencesImpl.java中MemoryCommitResult类的全部代码:
private static class MemoryCommitResult {
final long memoryStateGeneration; // 用于保存当前为第几次内存变换
@Nullable final List keysModified; // 用于保存发生变化的键
@Nullable final Set listeners; // 用于保存监听器
final Map mapToWriteToDisk; // 用于保存整理后的需要写入磁盘的全部数据
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1); // 用于同步阻塞
@GuardedBy("mWritingToDiskLock")
volatile boolean writeToDiskResult = false; // 表示写入磁盘是否成功
boolean wasWritten = false; // 表示是否执行写入磁盘的操作
// 构造方法
private MemoryCommitResult(long memoryStateGeneration, @Nullable List keysModified,@Nullable Set listeners,
Map mapToWriteToDisk) {
// 保存传入的参数
this.memoryStateGeneration = memoryStateGeneration;
this.keysModified = keysModified;
this.listeners = listeners;
this.mapToWriteToDisk = mapToWriteToDisk;
}
// 该方法用于设置硬盘写入的结果
void setDiskWriteResult(boolean wasWritten, boolean result) {
// 保存参数
this.wasWritten = wasWritten;
writeToDiskResult = result;
// writtenToDiskLatch减一,由于减一后为0,因此解除阻塞状态
writtenToDiskLatch.countDown();
}
}
3)enqueueDiskWrite方法
SharedPreferencesImpl.java中MemoryCommitResult类的全部代码:
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
// 通过判断参数postWriteRunnable是否为空
// 表示需要同步写入磁盘还是异步写入磁盘
final boolean isFromSyncCommit = (postWriteRunnable == null);
// 创建writeToDiskRunnable对象,封装写入磁盘的核心操作
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
// 同步锁,锁对象为mWritingToDiskLock
synchronized (mWritingToDiskLock) {
// 写入磁盘
writeToFile(mcr, isFromSyncCommit);
}
// 同步锁,锁对象为mLock
synchronized (mLock) {
// 当前写入磁盘的线程数量减一
mDiskWritesInFlight--;
}
// 若postWriteRunnable不为空
if (postWriteRunnable != null) {
// 调用run方法
postWriteRunnable.run();
}
}
};
// 若为同步写入磁盘
if (isFromSyncCommit) {
// 表示是否有其它线程正在写入
boolean wasEmpty = false;
// 同步锁,锁对象为mLock
synchronized (mLock) {
// 若当前写入磁盘的线程数量为1
// 说明除了本线程,没有其它线程写入,wasEmpty为true
wasEmpty = mDiskWritesInFlight == 1;
}
// 若没有其它线程正在写入数据
if (wasEmpty) {
// 调用上面writeToDiskRunnable对象封装的run方法
// 同步写入数据
writeToDiskRunnable.run();
// 返回
return;
}
}
// 若为异步写入,或同步写入时有其它线程正在写入,则调用本方法异步写入
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
从enqueueDiskWrite方法的代码可以知道,若为同步方法,会调用writeToFile方法。
4)writeToFile方法SharedPreferencesImpl.java中的相关代码:
@GuardedBy("mWritingToDiskLock")
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
// 判断要写入的文件是否存在
boolean fileExists = mFile.exists();
// 若文件存在
if (fileExists) {
// 表示是否要写入
boolean needsWrite = false;
// 若写入磁盘的次数小于内存变化的次数
// 因为当写入磁盘次数大于等于磁盘变换次数,说明写入操作是重复的。
// 即写入的数据和磁盘的数据相同
if (mDiskStateGeneration < mcr.memoryStateGeneration) {
// 若为同步写入
if (isFromSyncCommit) {
// 表示需要写入
needsWrite = true;
} else {// 若为异步写入
// 同步锁, 锁对象为mLock
synchronized (mLock) {
// 若内存变化的次数和当前内存整合的次数相同
// 说明当前要写入的数据为最新的数据
// 防止中间数据也写入,造成IO资源浪费
if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
// 表示需要写入
needsWrite = true;
}
}
}
}
若不需要写入
if (!needsWrite) {
// 设置结果
// 写入结果为true,因为当前数据不是最终要写入的数据,之后会写入
mcr.setDiskWriteResult(false, true);
// 返回
return;
}
// 表示备份文件是否存在
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();
}
}
try {
// 创建文件输出流
// 详解在5)处
FileOutputStream str = createFileOutputStream(mFile);
// 若文件输出流创建失败
if (str == null) {
// 设置结果,写入失败
mcr.setDiskWriteResult(false, false);
// 返回
return;
}
// 核心方法,将内存中的数据按Xml文件格式写入到文件
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
// 等待磁盘写入文件完成
FileUtils.sync(str);
// 关闭文件输出流
str.close();
// 设置文件的权限
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
// 获取文件信息
final StructStat stat = Os.stat(mFile.getPath());
// 同步锁,锁对象为mLock
synchronized (mLock) {
// 保存文件修改时间
mStatTimestamp = stat.st_mtim;
// 保存文件大小
mStatSize = stat.st_size;
}
} catch (ErrnoException e) {
// Do nothing
}
// 写入成功后,删除备份文件
mBackupFile.delete();
// 更新硬盘写入次数
mDiskStateGeneration = mcr.memoryStateGeneration;
// 设置结果,写入成功
mcr.setDiskWriteResult(true, true);
// 返回
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// 若程序在写入过程发生错误,会执行这里,对写入错误的文件进行处理
// 若写入的文件存在
if (mFile.exists()) {
// 删除文件,若删除失败
if (!mFile.delete()) {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
}
}
// 设置结果,写入失败
mcr.setDiskWriteResult(false, false);
}
5)createFileOutputStream方法
SharedPreferencesImpl.java中的相关代码:
private static FileOutputStream createFileOutputStream(File file) {
FileOutputStream str = null;
try {
// 创建文件输出流
str = new FileOutputStream(file);
} catch (FileNotFoundException e) {//若文件找不到
// 获取文件外面一层的文件夹
File parent = file.getParentFile();
// 创建外层文件夹
// 若创建失败
if (!parent.mkdir()) {
Log.e(TAG, "Couldn't create directory for SharedPreferences file " + file);
// 返回空
return null;
}
// 设置文件权限
FileUtils.setPermissions(
parent.getPath(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-1, -1);
try {
// 创建文件输出流
str = new FileOutputStream(file);
} catch (FileNotFoundException e2) {
Log.e(TAG, "Couldn't create SharedPreferences file " + file, e2);
}
}
// 返回
return str;
}
在java中,File既可以表示文件也可以表示文件夹,当我们要查找一个文件时,必须要保证外层的文件夹存在。
至此,同步方法commit代码结束。
SharedPreferencesImpl.java中的相关代码:
@Override
public void apply() {
// 对EditorImpl对象的操作(put、remove、clear等)进行整合处理
// 详解在commit方法的1)处
final MemoryCommitResult mcr = commitToMemory();
// 将阻塞方法封装成Runnable对象
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
// 阻塞等待数据写入磁盘完成
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
};
// 将awaitCommit对象保存到QueuedWork
// 当QueuedWork在执行任务时需要阻塞时会调用
QueuedWork.addFinisher(awaitCommit);
// 进一步封装
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
// 将awaitCommit对象从QueuedWork中移除
QueuedWork.removeFinisher(awaitCommit);
}
};
// 将数据写入磁盘
// 详解在commit方法的3)处
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
// 对注册当前SharedPreferences对象的监听器进行回调
notifyListeners(mcr);
}
根据之前对enqueueDiskWrite方法的分析,可以知道异步提交时,最后会调用QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit)方法。
三. QueuedWork类该类用来异步执行任务,该类中的方法全部为静态方法。
1.重要的全局变量QueuedWork.java中的相关代码:
private static final Object sLock = new Object(); // 锁
@GuardedBy("sLock")
private static final LinkedList sFinishers = new LinkedList(); // 用于保存封装阻塞方法的Runnable对象
@GuardedBy("sLock")
private static Handler sHandler = null; // 用于发送消息到线程中
@GuardedBy("sLock")
private static final LinkedList sWork = new LinkedList(); // 用于保存待执行的任务
@GuardedBy("sLock")
private static boolean sCanDelay = true; // 表示是否能延时发送消息
2. queue方法
QueuedWork.java中的相关代码:
public static void queue(Runnable work, boolean shouldDelay) {
// 获取Handler对象
// 详解在1)处
Handler handler = getHandler();
// 同步锁,锁对象为sLock
synchronized (sLock) {
// 添加待执行的任务
sWork.add(work);
// 若要求延时发送消息且能够延时发送
if (shouldDelay && sCanDelay) {
// 延时发送消息
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
// 不延时发送消息
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
1)getHandler方法
QueuedWork.java中的相关代码:
private static Handler getHandler() {
// 同步锁,锁对象为sLock
synchronized (sLock) {
// 若sHandler为空,即第一次调用该方法
if (sHandler == null) {
// 创建名为"queued-work-looper"的HandlerThread对象
HandlerThread handlerThread = new HandlerThread("queued-work-looper",Process.THREAD_PRIORITY_FOREGROUND);
// 启动线程
handlerThread.start();
// 根据HandlerThread线程的Looper创建QueuedWorkHandler对象
sHandler = new QueuedWorkHandler(handlerThread.getLooper());
}
// 返回
return sHandler;
}
}
通过getHandler方法,可以知道sHandler为QueuedWorkHandler对象。QueuedWorkHandler类继承handler类,是QueuedWork类的静态内部类。
当调用sHandler发送消息,消息会在QueuedWorkHandler类的handlerMessage方法进行处理。
QueuedWork.java中的相关代码:
public void handleMessage(Message msg) {
if (msg.what == MSG_RUN) {
processPendingWork();
}
}
收到消息后,调用processPendingWork方法进行处理。
2)processPendingWork方法QueuedWork.java中的相关代码:
private static void processPendingWork() {
// 同步锁,锁对象为sProcessingWork
synchronized (sProcessingWork) {
// 保存待执行的任务
LinkedList work;
// 同步锁,锁对象为sLock
synchronized (sLock) {
// 复制待执行任务队列sWork的数据到work中
work = (LinkedList) sWork.clone();
// 清空全局变量
sWork.clear();
// 移除消息队列中的消息
getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
}
// 若待执行的任务数量大于0
if (work.size() > 0) {
// 遍历
for (Runnable w : work) {
// 执行任务
w.run();
}
}
}
}
至此,异步方法apply代码结束。
四.总结 1.缓存查找 ArrayMap<String, ArrayMap>缓存根据进程的包名,找到对应进程的全部SharedPreferences对象。
ArrayMap缓存根据文件名,找到对应的SharedPreferences对象。
SharedPreferences通过使用synchronized同步锁、CountDownLatch实现线程间的同步。
通过规定锁的获取顺序来防止产生死锁。
获取EditorImpl 的mEditorLock前需要先获取SharedPreferencesImpl的mLock或mWritingToDiskLock。
正常情况下,除了第一次创建SharedPreferences之外,一个SharedPreferences中最终只应该存在一个文件。
当打开文件读取数据时,若存在备份文件,则读取备份文件,删除原文件,将备份文件的名字改成原文件的名,当作原文件使用。
当打开原文件写入数据成功时,会删除备份文件。当打开原文件写入数据错误或失败时,会删除写入不完整的原文件,继续使用备份文件作为原文件使用。
在SharedPreferences从磁盘上读取数据到内存后,内存上的数据和磁盘中的数据是两个完全相同的数据。之后的每次获取数据会从内存中获取。如果需要写入删除或修改数据,会先对这部分数据进行临时的存储,在提交数据时,会对内存中的数据和临时存储的数据进行整合。整合完毕后,内存的数据是最新的数据,会写入到磁盘中。为了防止对IO资源的过多占用,SharedPreferences使用计数器保证在高并发时只有整合了最终结果的内存数据才能写入磁盘,中间过程的整合数据不能写入磁盘。
LeeDuo. 原创文章 23获赞 8访问量 1万+ 关注 私信 展开阅读全文
--结束END--
本文标题: 【Android】SharedPreferences源码分析,全网最全!
本文链接: https://lsjlt.com/news/29732.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-01-21
2023-10-28
2023-10-28
2023-10-27
2023-10-27
2023-10-27
2023-10-27
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0