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的相关方法来实现的。
[]( )1.重要的全局变量
---------------------------------------------------------------------------
**SharedPreferencesImpl.java中的相关代码:**
private final Object mEditorLock = new Object();// 锁
@GuardedBy("mEditorLock")
private final Map<String, Object> 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方法为同步方法。
### []( )同步提交数据:commit
**SharedPreferencesImpl.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<String> keysModified = null; // 用于存储发生变化的键
Set<OnSharedPreferenceChangeListener> listeners = null; // 用于存储监听器
Map<String, Object> mapToWriteToDisk; // 用于记录需要写入磁盘的所有的数据
// 同步锁,锁对象为SharedPreferencesImpl.this.mLock
synchronized (SharedPreferencesImpl.this.mLock) {
// 若当前有线程在写入
if (mDiskWritesInFlight > 0) {
// 复制mMap,对新数据进行操作
mMap = new HashMap<String, Object>(mMap);
}
// 获取全局变量
mapToWriteToDisk = mMap;
// 当前进行写入操作的线程数加一
mDiskWritesInFlight++;
// SharedPreferences是否有监听器
boolean hasListeners = mListeners.size() > 0;
// 若有监听器
if (hasListeners) {
// 进行初始化
keysModified = new ArrayList<String>();
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
// 同步锁,锁对象为mEditorLock
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) {
// 若写入磁盘中的数据没有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<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; // 表示是否执行写入磁盘的操作
// 构造方法
private MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified,@Nullable Set<OnSharedPreferenceChangeListener> listeners,
Map<String, Object> 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代码结束。
### []( )异步提交数据:apply
**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);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长。而不成体系的学习效果低效漫长且无助。时间久了,付出巨大的时间成本和努力,没有看到应有的效果,会气馁是再正常不过的。
所以学习一定要找到最适合自己的方式,有一个思路方法,不然不止浪费时间,更可能把未来发展都一起耽误了。
如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
QueuedWork.removeFinisher(awaitCommit);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-mb3KStHW-1712683488753)]
[外链图片转存中…(img-nNQarM3f-1712683488754)]
[外链图片转存中…(img-qv6BGBjM-1712683488754)]
[外链图片转存中…(img-C8U3W2kG-1712683488754)]
[外链图片转存中…(img-sQmqklhz-1712683488755)]
[外链图片转存中…(img-RnEjGQ09-1712683488755)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-g7HVqDEu-1712683488755)]
最后
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长。而不成体系的学习效果低效漫长且无助。时间久了,付出巨大的时间成本和努力,没有看到应有的效果,会气馁是再正常不过的。
所以学习一定要找到最适合自己的方式,有一个思路方法,不然不止浪费时间,更可能把未来发展都一起耽误了。
如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-hPIP67xd-1712683488756)]