【Android】SharedPreferences源码分析(1),2024年最新你头秃都没想到还能这样吧

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移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长。而不成体系的学习效果低效漫长且无助。时间久了,付出巨大的时间成本和努力,没有看到应有的效果,会气馁是再正常不过的。

所以学习一定要找到最适合自己的方式,有一个思路方法,不然不止浪费时间,更可能把未来发展都一起耽误了。

如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img
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)]
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-g7HVqDEu-1712683488755)]

最后

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长。而不成体系的学习效果低效漫长且无助。时间久了,付出巨大的时间成本和努力,没有看到应有的效果,会气馁是再正常不过的。

所以学习一定要找到最适合自己的方式,有一个思路方法,不然不止浪费时间,更可能把未来发展都一起耽误了。

如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-hPIP67xd-1712683488756)]

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值