最近在复习,发现了关于多线程 多进程的问题,面试中常问的涉及到了SharedPreference的知识,决定去看看源码,到底是如何实现的。这里就不介绍它的用法和一些基础知识了,直接开始源码分析。
源码解析
ContextImpl#getSharedPreferences
这里主要是看context.getSharedPreferences()方法,context的实现类是ContextImpl。
public SharedPreferences getSharedPreferences(String name, int mode) {
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);
文件名为null,创建文件,并存入mSharedPrefsPaths集合中
if (file == null) {
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
//调用下面的方法
return getSharedPreferences(file, mode);
}
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
// 获取用于缓存的cache对象
cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
//检查mode类型
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是SharedPreferences的实现类
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) {
//如果是多进程文件需要重新读取文件
sp.startReloadIfChangedUnexpectedly();
}
return sp;
}
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
//若第一次调用该方法,则对全局变量sSharedPrefsCache进行初始化
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}
//获取包名
final String packageName = getPackageName();
//根据包名获取packagePrefs
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
//创建packagePrefs,并放入缓存中
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
return packagePrefs;
}
SharedPreferencesImpl
构造方法
SharedPreferencesImpl(File file, int mode) {
mFile = file;
//file的备份文件
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
//从硬盘中加载数据
startLoadFromDisk();
}
startLoadFromDisk()
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
//创建并启动了一个名为“SharedPreferencesImpl-load”的线程
new Thread("SharedPreferencesImpl-load") {
public void run() {
//从硬盘中加载数据
loadFromDisk();
}
}.start();
}
loadFromDisk()
private void loadFromDisk() {
synchronized (mLock) {
//前面出现过,用于判断是否从硬盘中加载过,是则返回
if (mLoaded) {
return;
}
//判断备份文件是否存在
if (mBackupFile.exists()) {
//删除源文件
mFile.delete();
//将备份文件中的内容复制到源文件,作了替换
mBackupFile.renameTo(mFile);
}
}
...省略部分代码
//用于临时保存磁盘文件中加载解析后的键值对数据
Map<String, Object> map = null;
//用于保存文件的信息
StructStat stat = null;
//用于保存加载数据中产生的异常
Throwable thrown = null;
try {
//获取文件的信息
stat = Os.stat(mFile.getPath());
//文件可读,则创建缓存输入流
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16 * 1024);
//将文件中的数据解析成键值对,保存在map中
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) {
} catch (Throwable t) {
thrown = t;
}
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();
}
}
}
从SharedPreferences中获取数据
以getString为例
getString
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
// 让当前的线程等待从磁盘加载数据的线程加载完数据
awaitLoadedLocked();
// 从全局变量mMap中获取数据
String v = (String)mMap.get(key);
// 如果获取到数据,就返回数据,否则返回方法参数中给定的默认值
return v != null ? v : defValue;
}
}
awaitLoadedLocked
private void awaitLoadedLocked() {
// 如果从硬盘加载数据没有完成
if (!mLoaded) {
//调用线程处理策略
BlockGuard.getThreadPolicy().onReadFromDisk();
}
// 如果从硬盘加载数据没有完成,则循环执行
while (!mLoaded) {
try {
//释放锁等待
mLock.wait();
} catch (InterruptedException unused) {
}
}
//若等待期间发生异常,则抛出异常
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
写数据
SharedPreferences.Editor
@Override
public Editor edit() {
// 同步锁,锁对象为mLock
synchronized (mLock) {
// 让当前的线程等待从磁盘加载数据的线程加载完数据
awaitLoadedLocked();
}
// 创建EditorImpl对象并返回
return new EditorImpl();
}
EditorImpl类
Editor是一个接口,它的具体实现类是EditorImpl类。EditorImpl类是SharedPreferencesImpl类的内部类,SharedPreferences中对数据的增 删 改都是通过调用Editor的相关方法实现的。
putString
private final Map mModified = new HashMap();// 用于临时存储需要写入磁盘的数据或需要移除的数据,之后统一处理
@Override
public Editor putString(String key, @Nullable String value) {
// 同步锁,锁对象为mEditorLock
synchronized (mEditorLock) {
// 向全局变量mModified添加数据
mModified.put(key, value);
// 返回
return this;
}
}
提交数据到磁盘
commit()
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
//对EditorImpl对象的操作(put remove clear等)进行整合处理
MemoryCommitResult mcr = commitToMemory();
//将数据写入磁盘
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
//阻塞等待写入过程完成
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
} finally {
}
//对注册当前SharedPreferences对象的监听器进行回调
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
commitToMemory
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
List<String> keysModified = null;//用于存储发生变化的键
Set<OnSharedPreferenceChangeListener> listeners = null;//用于存储监听器
Map<String, Object> mapToWriteToDisk;//用于记录需要写入磁盘的所有的数据
synchronized (SharedPreferencesImpl.this.mLock) {
//若当前有线程在写入
if (mDiskWritesInFlight > 0) {
//复制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;
//若用户调用clear方法清空数据
if (mClear) {
//若写入的数据不为空,即有需要清除的数据
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
mClear = false;
}
//对提交到Editor中的数据进行遍历
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
if (v == this || v == null) {
if (!mapToWriteToDisk.containsKey(k)) {
continue;
}
mapToWriteToDisk.remove(k);
} else {
//若值不为空,也不为自身,说明添加了新键值对或修改了键值对的值
//若打算写入磁盘的数据包含k这个键,说明对值进行了修改
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;
}
}
//创建MemoryCommitResult对象并返回
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
enqueueDiskWrite()
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
//通过判断参数postWriteRunnable是否为空,表示需要同步写入磁盘还是异步写入磁盘
final boolean isFromSyncCommit = (postWriteRunnable == null);
//创建writeToRunnable对象,封装写入磁盘的核心操作
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
//写入磁盘
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
//当前写入磁盘的线程数量减一
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// 若为同步写入磁盘
if (isFromSyncCommit) {
//表示是否有其他线程正在写入
boolean wasEmpty = false;
synchronized (mLock) {
//若当前写入磁盘的线程数量为1 说明除了本线程没有其他线程写入,wasEmpty为true
wasEmpty = mDiskWritesInFlight == 1;
}
//若没有其他线程正在写入
if (wasEmpty) {
//执行线程
writeToDiskRunnable.run();
return;
}
}
//若为异步写入或者同步写入时有其他线程正在写入,则调用本方法异步写入
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
apply()
public void apply() {
final long startTime = System.currentTimeMillis();
//对EditorImpl对象的操作进行整合处理
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");
}
}
};
//将awaitCommit对象保存到QueuedWork 当QueuedWork在执行任务时需要阻塞时会调用
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
//将awaitCommit对象从QueuedWork中移除 QueuedWork.removeFinisher(awaitCommit);
}
};
//将数据写入磁盘
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
notifyListeners(mcr);
}
从此可以看出,这两个方法都是首先修改内存中缓存的mMap的值,然后将数据写到磁盘中。它们的主要区别是commit会等待写入磁盘后再返回,而apply则在调用写磁盘操作后就直接返回了,但是这时候可能磁盘中数据还没有被修改。
网友评论