SharedPreferencesImpl.java中的相关代码:
private void loadFromDisk() {
// 同步锁,锁对象为mLock
synchronized (mLock) {
// 双重锁,若已经加载过,则返回
if (mLoaded) {
return;
}
// 若备份文件存在,说明不是第一次创建该SharedPreferences
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 {
// 创建缓存输入流,缓存大小为16 * 1024KB
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) {
// 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();
}
}
}
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);
}
}
调用SharedPreferences对象的edit方法获取Editor对象,然后对Editor对象进行操作。
SharedPreferencesImpl.java中的相关代码:
@Override
public Editor edit() {
// 同步锁,锁对象为mLock
synchronized (mLock) {
// 让当前的线程等待从磁盘加载数据的线程加载完数据
awaitLoadedLocked();
}
// 创建EditorImpl对象并返回
return new EditorImpl();
}
===============================================================================
Editor是一个接口,它的具体实现是EditorImpl类。因此最终获取的为EditorImpl对象。
EditorImpl类是SharedPreferencesImpl类的内部类。SharedPreferences中对数据的增、删、改都是通过调用Editor的相关方法来实现的。
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
EditorImpl类中提供很多不同的put方法来添加不同类型的数据。这些方法内部实现的原理是类似的。以putString方法为例。
SharedPreferencesImpl.java中的相关代码:
@Override
public Editor putString(String key, @Nullable String value) {
// 同步锁,锁对象为mEditorLock
synchronized (mEditorLock) {
// 向全局变量mModified添加数据
mModified.put(key, value);
// 返回
return this;
}
}
SharedPreferencesImpl.java中的相关代码:
@Override
public Editor clear() {
// 同步锁,锁对象为mEditorLock
synchronized (mEditorLock) {
// 清除标志位为true
mClear = true;
// 返回
return this;
}
}
SharedPreferencesImpl.java中的相关代码:
@Override
public Editor remove(String key) {
// 同步锁,锁对象为mEditorLock
synchronized (mEditorLock) {
// 将全局变量mModified对应的value改为自身,表示这个键值对需要删除
mModified.put(key, this);
// 返回
return this;
}
}
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;
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**
**深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
**因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/152e1a554a58a00c107f492d043b1d15.png)
![img](https://img-blog.csdnimg.cn/img_convert/248205cfbe2a30534ae44de128ddff50.png)
![img](https://img-blog.csdnimg.cn/img_convert/c2e05e70f1bdb6bd49f9ac5b9cf58af1.png)
![img](https://img-blog.csdnimg.cn/img_convert/574dffb882f93f288072292120e78f49.png)
![img](https://img-blog.csdnimg.cn/img_convert/06990093392f01eb1b130318902f6493.png)
![img](https://img-blog.csdnimg.cn/img_convert/27b0d5709bc92fe652c6e637e368f1c7.png)
![img](https://img-blog.csdnimg.cn/13f2cb2e05a14868a3f0fd6ac81d625c.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!**
**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**
**如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)**
![img](https://img-blog.csdnimg.cn/img_convert/b5d07763fce99a03f9d52966d96849c5.png)
### 结语
看到这篇文章的人不知道有多少是和我一样的Android程序员。
35岁,这是我们这个行业普遍的失业高发阶段,这种情况下如果还不提升自己的技能,进阶发展,我想,很可能就是本行业的职业生涯的终点了。
我们要有危机意识,切莫等到一切都成定局时才开始追悔莫及。只要有规划的,有系统地学习,进阶提升自己并不难,给自己多充一点电,你才能走的更远。
千里之行始于足下。这是上小学时,那种一元钱一个的日记本上每一页下面都印刷有的一句话,当时只觉得这句话很短,后来渐渐长大才慢慢明白这句话的真正的含义。
有了学习的想法就赶快行动起来吧,不要被其他的事情牵绊住了前行的脚步。不要等到裁员时才开始担忧,不要等到面试前一晚才开始紧张,不要等到35岁甚至更晚才开始想起来要学习要进阶。
给大家一份系统的Android学习进阶资料,希望这份资料可以给大家提供帮助。
![](https://img-blog.csdnimg.cn/img_convert/0884789b0c4c1467580f779d00a69c0a.webp?x-oss-process=image/format,png)
**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
![img](https://img-blog.csdnimg.cn/img_convert/0c7aa6c76287b8421f3e53f0f2adcaf7.png)
移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中...(img-kpEWtuoz-1712683519320)]
[外链图片转存中...(img-IsSmptOq-1712683519321)]
[外链图片转存中...(img-dsDx1xgl-1712683519321)]
[外链图片转存中...(img-q4259509-1712683519322)]
[外链图片转存中...(img-03g1P1yi-1712683519322)]
[外链图片转存中...(img-rrxetwtV-1712683519322)]
![img](https://img-blog.csdnimg.cn/13f2cb2e05a14868a3f0fd6ac81d625c.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!**
**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**
**如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)**
[外链图片转存中...(img-3XGexpOi-1712683519323)]
### 结语
看到这篇文章的人不知道有多少是和我一样的Android程序员。
35岁,这是我们这个行业普遍的失业高发阶段,这种情况下如果还不提升自己的技能,进阶发展,我想,很可能就是本行业的职业生涯的终点了。
我们要有危机意识,切莫等到一切都成定局时才开始追悔莫及。只要有规划的,有系统地学习,进阶提升自己并不难,给自己多充一点电,你才能走的更远。
千里之行始于足下。这是上小学时,那种一元钱一个的日记本上每一页下面都印刷有的一句话,当时只觉得这句话很短,后来渐渐长大才慢慢明白这句话的真正的含义。
有了学习的想法就赶快行动起来吧,不要被其他的事情牵绊住了前行的脚步。不要等到裁员时才开始担忧,不要等到面试前一晚才开始紧张,不要等到35岁甚至更晚才开始想起来要学习要进阶。
给大家一份系统的Android学习进阶资料,希望这份资料可以给大家提供帮助。
[外链图片转存中...(img-vJKACdZh-1712683519323)]
**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
[外链图片转存中...(img-eWkL85gX-1712683519324)]