1.实现类

SharedPreferences 只是一个接口,其实现类是SharedPreferencesImpl。

工作流程分析:
创建sp 的时候,会去查看是否有bak文件,如果有的话,把bak文件,重命名成file的真正文件名,读取到内存。

    SharedPreferencesImpl(File file, int mode) {mFile = file;mBackupFile = makeBackupFile(file);mMode = mode;mLoaded = false;mMap = null;mThrowable = null;startLoadFromDisk();}private void startLoadFromDisk() {synchronized (mLock) {mLoaded = false;}new Thread("SharedPreferencesImpl-load") {public void run() {loadFromDisk();}}.start();}

如果在读取的过程中,你调用了getString,那么该方法会等到io 读取到map 完成,返回结果。

2.getString 会直接从磁盘里面直接读取吗?

不会,会从内存里面读取。

    @Override@Nullablepublic String getString(String key, @Nullable String defValue) {synchronized (mLock) {awaitLoadedLocked();String v = (String)mMap.get(key);return v != null ? v : defValue;}}

3.apply 和 commit 的 区别。

apply 不会立马写在磁盘里面,commit 会的。
关键位置是SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);

我们看下这个方法:

    /*** Enqueue an already-committed-to-memory result to be written* to disk.** They will be written to disk one-at-a-time in the order* that they're enqueued.** @param postWriteRunnable if non-null, we're being called*   from apply() and this is the runnable to run after*   the write proceeds.  if null (from a regular commit()),*   then we're allowed to do this disk write on the main*   thread (which in addition to reducing allocations and*   creating a background thread, this has the advantage that*   we catch them in userdebug StrictMode reports to convert*   them where possible to apply() ...)*/private void enqueueDiskWrite(final MemoryCommitResult mcr,final Runnable postWriteRunnable) {final boolean isFromSyncCommit = (postWriteRunnable == null);final Runnable writeToDiskRunnable = new Runnable() {@Overridepublic void run() {synchronized (mWritingToDiskLock) {writeToFile(mcr, isFromSyncCommit);}synchronized (mLock) {mDiskWritesInFlight--;}if (postWriteRunnable != null) {postWriteRunnable.run();}}};// Typical #commit() path with fewer allocations, doing a write on// the current thread.if (isFromSyncCommit) {boolean wasEmpty = false;synchronized (mLock) {wasEmpty = mDiskWritesInFlight == 1;}if (wasEmpty) {writeToDiskRunnable.run();return;}}QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);}

如果是commit 的话,writeToDiskRunnable.run(); 写文件的这个操作直接进行。如果不是的话,会放到一个队列里面去执行。QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);

疑问:

如果我apply 是异步进行的,那么为什么我putString (“aaa”,“111”)之后调用apply ,立马就getString(“aaa”,"")能够返回正确的结果呢?

因为apply 和commit 一样,都会先把改动保存到内存,然后写到文件里面。

@Overridepublic void apply() {final long startTime = System.currentTimeMillis();final MemoryCommitResult mcr = commitToMemory();
        // Returns true if any changes were madeprivate MemoryCommitResult commitToMemory() {long memoryStateGeneration;List<String> keysModified = null;Set<OnSharedPreferenceChangeListener> listeners = null;Map<String, Object> mapToWriteToDisk;synchronized (SharedPreferencesImpl.this.mLock) {// We optimistically don't make a deep copy until// a memory commit comes in when we're already// writing to disk.if (mDiskWritesInFlight > 0) {// We can't modify our mMap as a currently// in-flight write owns it.  Clone it before// modifying it.// noinspection uncheckedmMap = 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;if (mClear) {if (!mapToWriteToDisk.isEmpty()) {changesMade = true;mapToWriteToDisk.clear();}mClear = false;}for (Map.Entry<String, Object> e : mModified.entrySet()) {String k = e.getKey();Object v = e.getValue();// "this" is the magic value for a removal mutation. In addition,// setting a value to "null" for a given key is specified to be// equivalent to calling remove on that key.if (v == this || v == null) {if (!mapToWriteToDisk.containsKey(k)) {continue;}mapToWriteToDisk.remove(k);} else {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;}}return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,mapToWriteToDisk);}

mapToWriteToDisk = mMap; 这一句话就把mMap 赋值给mapToWriteToDisk 。而所有的改动都会在这个mapToWriteToDisk 上去修改。其实最终修改的就是mMap。所以不需要等到写到文件里面,你就可以拿到正确的结果。

4.apply 的实现方法

比如当我们调用sp.setString().apply 的时候,首先会把你设置的String 提交到内存里面,也就是map 里面。

然后会调用QueuedWork.addFinisher(awaitCommit); 把这个等待的runnable 添加到QueueWork 的finish 队列里。
等待的Runnable 代码如下:

            final Runnable awaitCommit = new Runnable() {@Overridepublic void run() {try {mcr.writtenToDiskLatch.await();} catch (InterruptedException ignored) {}}};

然后把写文件的runnable 放入QueueWork 的队列里面。QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);

在QueueWork 里面会发送一个延迟100ms 的消息,在消息里面会去处理写文件的Runnable。

写文件成功之后,会把等待的Runnable 从QueueWork 的 finisher 里面移除。QueuedWork.removeFinisher(awaitCommit);

在ActivityThread hanlderStopActivity 的时候,会调用QueueWork 的waitTofinish() 方法,等待所有的apply 的写文件完成。

commit 的实现

commit 的实现比较简单,当我们commit 的时候,会把写文件的runnable 发送到QueueWork 的 队列里,所以就算是commit 写文件也不是在主线程写的。但是commit 方法会调用mcr.writtenToDiskLatch.await(); 去等待QueueWork 写文件完成。

5.设计优缺点

6.SP设计里面的备份文件

SP会涉及两个文件,一个真正的文件,一个备份文件。
如果sp 改变了,是要写文件的话,如果当前的sp 文件file存在,首先把文件真正的file重命名为file.bak.
如果重命名不成功的话,整个操作以失败告终。

接着创建fileoutputstream.把map 写进去,改文件权限。
如果整个操作成功,那么把备份文件删除掉。如果失败,把file 文件删除掉。

 // Note: must hold mWritingToDiskLockprivate void writeToFile(MemoryCommitResult mcr) {// Rename the current file so it may be used as a backup during the next readif (mFile.exists()) {if (!mcr.changesMade) {// If the file already exists, but no changes were// made to the underlying map, it's wasteful to// re-write the file.  Return as if we wrote it// out.mcr.setDiskWriteResult(true);return;}if (!mBackupFile.exists()) {if (!mFile.renameTo(mBackupFile)) {Log.e(TAG, "Couldn't rename file " + mFile+ " to backup file " + mBackupFile);mcr.setDiskWriteResult(false);return;}} else {mFile.delete();}}// Attempt to write the file, delete the backup and return true as atomically as// possible.  If any exception occurs, delete the new file; next time we will restore// from the backup.try {FileOutputStream str = createFileOutputStream(mFile);if (str == null) {mcr.setDiskWriteResult(false);return;}XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);FileUtils.sync(str);str.close();ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);try {final StructStat stat = Os.stat(mFile.getPath());synchronized (this) {mStatTimestamp = stat.st_mtime;mStatSize = stat.st_size;}} catch (ErrnoException e) {// Do nothing}// Writing was successful, delete the backup file if there is one.mBackupFile.delete();mcr.setDiskWriteResult(true);return;} catch (XmlPullParserException e) {Log.w(TAG, "writeToFile: Got exception:", e);} catch (IOException e) {Log.w(TAG, "writeToFile: Got exception:", e);}// Clean up an unsuccessfully written fileif (mFile.exists()) {if (!mFile.delete()) {Log.e(TAG, "Couldn't clean up partially-written file " + mFile);}}mcr.setDiskWriteResult(false);}

下次进来去读文件的时候,如果back 文件存在,直接把file 文件删除掉,并且把back 文件,重新命名成file.

private void loadFromDiskLocked() {if (mLoaded) {return;}if (mBackupFile.exists()) {mFile.delete();mBackupFile.renameTo(mFile);}

总之,所有正确的都以bak 文件为准。

7.wait notify 的使用

8.SharedPreferences 支持多进程吗?

不知道,如果是多进程,可能在一个进程里面写的值,被另外一个进程都给冲掉了。

9.apply 是完全异步的吗?会不会导致ANR?

"main@10722" prio=5 tid=0x2 nid=NA waitingjava.lang.Thread.State: WAITINGat sun.misc.Unsafe.park(Unsafe.java:-1)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:190)at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:868)at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1023)at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1334)at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:232)at android.app.SharedPreferencesImpl$EditorImpl$1.run(SharedPreferencesImpl.java:466)at android.app.QueuedWork.waitToFinish(QueuedWork.java:194)at android.app.ActivityThread.handleStopActivity(ActivityThread.java:4318)at android.app.servertransaction.StopActivityItem.execute(StopActivityItem.java:41)at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:145)at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1872)at android.os.Handler.dispatchMessage(Handler.java:106)at android.os.Looper.loop(Looper.java:193)at android.app.ActivityThread.main(ActivityThread.java:6743)at java.lang.reflect.Method.invoke(Method.java:-1)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:486)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:882)

我们看这段堆栈,发现当activity onStop 的时候,会执行QueuedWork.waitToFinish()方法。这个QueuedWork 类,是供SP apply 异步写文件的一个类,里面会有HandlerThread去负责写文件。
waitToFinish 方法会把没有执行的所有runnable,放到主线程执行。所以handleStopActivity 会等待所有的apply 没有完成的runnable 去执行完成。所以,apply 并不是说完全异步的。也有可能导致ANR。但是,apply 这种只会在调用waitToFinish() 的场景才会触发ANR. 如果一个点击事件,如果里面处理的很多的业务逻辑,最后调用了commit 方法,那么有可能因为commit 产生ANR,但是不会因为apply 产生ANR.

我们看下QueueWork 所有waitToFinish() 方法调用的地方:

我们发现基本上都在ActivityThread 这个类里面。

模拟apply 产生ANR:

Class<?> aClass = null;try {aClass = Class.forName("android.app.QueuedWork");Method addFinisher = aClass.getMethod("addFinisher", Runnable.class);if (addFinisher != null) {addFinisher.invoke(null, new Runnable() {@Overridepublic void run() {try {Thread.sleep(30000);} catch (InterruptedException e) {e.printStackTrace();}}});}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}

我们可以通过以上代码,在QueueWork 里面添加一个finisher,然后点击手机back键,会发现会卡在onStop 方法那里。

总结:

1.面试不会面试业务代码,他们根本不熟悉,只会面android 源码 相关的问题。而sp 从来没有看过。所以很多问题不知道。

Android SharedPreferences 详解 源码解析相关推荐

  1. beaninfo详解源码解析 java_【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  2. beaninfo详解源码解析 java_Java后端精选技术:源码解析Spring Cloud Zuul

    Zuul 架构图 在zuul中, 整个请求的过程是这样的,首先将请求给zuulservlet处理,zuulservlet中有一个zuulRunner对象,该对象中初始化了RequestContext: ...

  3. Java开源生鲜电商平台-Java分布式以及负载均衡架构与设计详解(源码可下载)

    Java开源生鲜电商平台-Java分布式以及负载均衡架构与设计详解(源码可下载) 说明:主要是针对一些中大型的项目需要进行分布式以及负载均衡的架构提一些思路与建议. 面对大量用户访问.高并发请求,海量 ...

  4. android handler2--消息队列源码解析

    android handler2–消息队列源码解析 1.Looper 对于Looper主要是prepare()和loop()两个方法. 首先看prepare()方法 public static fin ...

  5. Android Hawk数据库的源码解析,Github开源项目,基于SharedPreferences的的存储框架

    今天看了朋友一个项目用到了Hawk,然后写了这边文章 一.了解一下概念 Android Hawk数据库github开源项目 Hawk是一个非常便捷的数据库.操作数据库只需一行代码,能存任何数据类型. ...

  6. android网络框架retrofit源码解析二

    注:源码解析文章参考了该博客:http://www.2cto.com/kf/201405/305248.html 前一篇文章讲解了retrofit的annotation,既然定义了,那么就应该有解析的 ...

  7. Android手游 “2048” 源码解析

    转载请写明出处:http://blog.csdn.net/big_heart_c 下面所解析的源码是来自极客学院"Android 2048 "中的源码,读者可以从 https:// ...

  8. Android 常用开源框架源码解析 系列 (九)dagger2 呆哥兔 依赖注入库

    一.前言 依赖注入定义 目标类中所依赖的其他的类的初始化过程,不是通过手动编码的方式创建的. 是将其他的类已经初始化好的实例自动注入的目标类中. "依赖注入"也是面向对象编程的 设 ...

  9. Android之EventBus框架源码解析下(源码解析)

    转载请标明出处:[顾林海的博客] 个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持! 前言 EventBus是典型的发布订阅模式,多个订阅者可以订阅某个事件,发布者通过 ...

最新文章

  1. 关于jspsmartupload中的各种问题
  2. python reader循环_python – 多次循环遍历csv.DictReader行
  3. javascript function
  4. 用 Flask 来写个轻博客 (14) — M(V)C_实现项目首页的模板
  5. Win32汇编基本编程框架
  6. 推荐系统笔记(深度学习)
  7. react学习(3)----不能在该位置用setstate
  8. 分享30个新鲜PSD网站模板免费下载
  9. [Leedcode][JAVA][第355题][设计推特][面向对象][哈希表][链表][优先队列]
  10. 自然语言处理中的语言模型与预训练技术的总结
  11. M1 Mac 是否入手,先了解这些常用软件兼容性!!
  12. 股票交易数据下载 | 下载股票历史交易数据到本地Excel
  13. mysql实现axure协同工作_AxureUX CRM及协同办公APP高保真原型模板(带移动端实用元件库)...
  14. MAC+appium自动化测试
  15. 用 JustTrustMe 干翻 SSL Pinning: 爬尤美 app 付费视频(app.youmei.com)
  16. 智慧社区解决方案上线!给社区装上智能大脑
  17. springboot智慧餐厅点餐餐桌预订系统设计与实现
  18. matlab中霍夫线检测函数,matlab 霍夫检测
  19. 纸壳CMS列表Grid的配置
  20. oracle 的导入导出,Oracle 导入导出详细介绍

热门文章

  1. adb安装apk是出现INSTALL_FAILED_INSUFFICIENT_STORAGE
  2. XXL-JOB v2.0.1 发布,分布式任务调度平台
  3. Browser Security-同源策略、伪URL的域
  4. opwnert挂载摄像头
  5. WINDOWS SERVER 2003 组策略应用
  6. sqlserver安装和简单的使用
  7. android用户界面之TabHost教程实例汇总
  8. 【0729作业】随机生成20个手机号码
  9. [19/04/02-星期二] IO技术_字符流分类总结(含字符转换流InputStreamReader/ OutputStreamWriter,实现字节转字符)...
  10. Ubuntu侧边任务栏自动隐藏