先从如何简单使用开始java
val sp = context.getSharedPreferences("123", Context.MODE_PRIVATE) //经过SharedPreferences读值 val myValue = sp.getInt("myKey",-1) //经过SharedPreferences.Editor写值 sp.edit().putInt("myKey",1).apply()
SharedPreferences只是一个有各类get方法的接口,结构是这样的android
//SharedPreferences.java public interface SharedPreferences { int getInt(String key, int defValue); Map<String, ?> getAll(); public interface Editor { Editor putString(String key, @Nullable String value); Editor putInt(String key, int value); } }
那么它从哪里来,咱们获得context具体实现类ContextImpl里去找,如下代码都会省略没必要要的部分缓存
//ContextImpl.java @Override public SharedPreferences getSharedPreferences(File file, int mode) { //能够看到返回的SharedPreferences其实就是一个SharedPreferencesImpl实例 SharedPreferencesImpl sp; synchronized (ContextImpl.class) { //每一个File都对应着一个SharedPreferencesImpl实例 final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); sp = cache.get(file); if (sp == null) { sp = new SharedPreferencesImpl(file, mode); cache.put(file, sp); return sp; } } return sp; }
从上面能够看出安全
内部存储了一个Map用于把数据缓存到内存app
//SharedPreferencesImpl.java @GuardedBy("mLock")//操做时经过mLock对象锁保证线程安全 Map<String, Object> mMap
对于同一个SharedParences.Editor来讲,每一个Editor也包含了一个map用来保存本次改变的数据异步
//SharedPreferencesImpl.java @GuardedBy("mEditorLock")//操做时经过mEditorLock对象锁保证线程安全 Map<String, Object> mModified
//SharedPreferencesImpl.java public String getString(String key, @Nullable String defValue) { synchronized (mLock) { //若是正在从xml文件中同步map到内存,则会阻塞等待同步完成 awaitLoadedLocked(); //直接从内存mMap中拿数据 String v = (String)mMap.get(key); return v != null ? v : defValue; } }
从上面代码能够看出,SharedPreferences会优先从内存中拿数据ide
public Editor putInt(String key, int value) { synchronized (mEditorLock) { mModified.put(key, value); return this; } }
putInt只是存入了mModified中,并无进行其它操做post
//SharedPreferencesImpl.java public void apply() { //1. 遍历mModified //2. 合并修改到mMap中 //3. 当前memory的代数 mCurrentMemoryStateGeneration++ //以此完成内存的实现。返回的MemoryCommitResult用于以后的xml文件写入 final MemoryCommitResult mcr = commitToMemory(); //这里是一个纯粹等待xml写入用的任务 //writtenToDiskLatch只在本次Editor的修改彻底写入到文件后释放 final Runnable awaitCommit = new Runnable() { @Override public void run() { try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } } }; //把上面的任务加入到QueuedWork的finisher列表中 //ActivityThread在调用Activity的onPause、onStop,或者Service的onStop以前都会调用QueuedWork的waitToFinish //waitToFinish方法则会轮流遍历运行它们的run方法,即在主线程触发await QueuedWork.addFinisher(awaitCommit); //在上一个等待任务外面再封装一层等待任务,用于在写入文件完成后从QueuedWork里移除finish Runnable postWriteRunnable = new Runnable() { @Override public void run() { awaitCommit.run(); //若成功完成,则从QueuedWork里移除该finisher QueuedWork.removeFinisher(awaitCommit); } }; //把写入磁盘的任务提交去执行,commit就不会带第二个参数,后面会说这里 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); //写入内存就直接触发回调监听 notifyListeners(mcr); }
//SharedPreferencesImpl.java @Override public boolean commit() { //与apply相同,直接写入内存 MemoryCommitResult mcr = commitToMemory(); //直接提交disk任务给线程进行处理,第二个参数为空,表示本身是同步的 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null); //注意这里是与apply的不一样,直接本身触发await,再也不放到Runnable里 try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException e) { return false; } notifyListeners(mcr); return mcr.writeToDiskResult; }
执行写入磁盘的任务优化
//SharedPreferencesImpl.java private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) { //经过第二个参数来判断是apply仍是commit,便是否是同步提交 final boolean isFromSyncCommit = (postWriteRunnable == null); //这个runnable就是写入磁盘的任务 final Runnable writeToDiskRunnable = new Runnable() { @Override public void run() { synchronized (mWritingToDiskLock) { //关键方法:写入磁盘 writeToFile(mcr, isFromSyncCommit); } synchronized (mLock) { //这个值在写入一次内存后+1,写入一次磁盘后-1,表示当前正在等待写入磁盘的任务个数 mDiskWritesInFlight--; } if (postWriteRunnable != null) { //与QueuedWork的waitToFinish不一样,这里是在子线程等待写入磁盘任务的完成 postWriteRunnable.run(); } } }; // 下面的条件我判断只有当前是最后一次commit任务才会到当前线程执行 // 而commit正常状况下是同步进行的,所以只要以前的apply任务未执行完成,也会改成异步执行 if (isFromSyncCommit) { boolean wasEmpty = false; synchronized (mLock) { //这次同步任务为当前全部任务的最后一次 wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { //直接在当前线程执行写入xml操做 writeToDiskRunnable.run(); return; } } //这里是把写入xml文件的任务放到QueuedWork的子线程去执行。 QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); //8.0以前则是直接用单线程池去执行 //QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable); }
QueuedWork更像是一个等待任务的集合,其内部含有两个列表ui
//写入磁盘任务会存入这个列表中,在8.0以前没有这个列表,只有一个SingleThreadExecutor线程池用来执行xml写入任务 private static final LinkedList<Runnable> sWork = new LinkedList<>(); //等待任务会存入这个列表中 private static final LinkedList<Runnable> sFinishers = new LinkedList<>();
//插入一个磁盘写入的任务,会放到QueuedWork里的一个HandlerThread去执行 public static void queue(Runnable work, boolean shouldDelay) { Handler handler = getHandler(); synchronized (sLock) { sWork.add(work); //若是是apply则100ms后再触发去遍历执行等待任务,commit则不延迟 if (shouldDelay && sCanDelay) { //这里只须要知道是触发执行sWork里的全部任务,即写入磁盘任务 handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY); } else { handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN); } } }
而等待任务列表sFinishers会在waitToFinish方法中使用到,做用是直接去执行全部磁盘任务,执行完成以后再轮流执行全部等待任务
//SharedPreferencesImpl.java public static void waitToFinish() { Handler handler = getHandler(); synchronized (sLock) { if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) { // 因为咱们会手动执行全部的磁盘任务,因此再也不须要这些触发执行任务的消息 handler.removeMessages(QueuedWorkHandler.MSG_RUN); } // 执行此方法的过程当中若插入了其它任务,都不须要再延迟了,直接去触发执行 sCanDelay = false; } //遍历执行当前的全部等待硬盘任务的run方法 processPendingWork(); try { while (true) { Runnable finisher; synchronized (sLock) { finisher = sFinishers.poll(); } if (finisher == null) { break; } finisher.run(); } } finally { //全部任务执行完成以后,道路通畅了,此次waitToFinish执行经过,能够继续延迟100ms sCanDelay = true; } }
如下是Android8.0以前的waitToFinish,只是遍历执行全部等待任务,也不会去主动写入xml,从而致使ANR出现
public static void waitToFinish() { Runnable toFinish; //只是去轮流执行全部等待任务 while ((toFinish = sPendingWorkFinishers.poll()) != null) { toFinish.run(); } }
咱们会经过context获取SharedPreferences对象时传入mode
context.getSharedPreferences("123", Context.MODE_PRIVATE)
该mode会在生成SharedPreferencesImpl实例时传入
//SharedPreferencesImpl.java SharedPreferencesImpl(File file, int mode) { //... mMode = mode; }
在xml文件写入完成后调用
//SharedPreferencesImpl.java private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) { //写入文件 FileOutputStream str = createFileOutputStream(mFile); XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str); str.close(); //给文件加权限 ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0); }
加权限的过程就终至关于咱们在串口使用chmod给权限
//ConetxtImpl.java static void setFilePermissionsFromMode(String name, int mode, int extraPermissions) { //默认给了同一用户与同一群组的读写权限 int perms = FileUtils.S_IRUSR|FileUtils.S_IWUSR |FileUtils.S_IRGRP|FileUtils.S_IWGRP |extraPermissions; if ((mode&MODE_WORLD_READABLE) != 0) { //其它用户读权限 perms |= FileUtils.S_IROTH; } if ((mode&MODE_WORLD_WRITEABLE) != 0) { //其它用户写权限 perms |= FileUtils.S_IWOTH; } FileUtils.setPermissions(name, perms, -1, -1); } //FileUtils.java public static int setPermissions(String path, int mode, int uid, int gid) { Os.chmod(path, mode); return 0; }
SharedPreference有一个内存缓存mMap,以及一个硬盘缓存xml文件。每次经过apply或者commit提交一次editor修改,都会先合入mMap即内存中,以后再缓存到硬盘。注意提交会触发整个文件的修改,所以多个修改最好放在同一个Editor对象中。
SharedPreferences主要经过对象锁来保证线程安全,Editor修改时用的是另外一个对象锁
,写入disk时也用的是另外一个对象锁。
mode相似于给经过chmod给xml文件不一样的权限,从而实现其余应用也能够访问的效果,默认MODE_PRIVATE给的是全部者和群组的读写权限,而MODE_WORLD_READABLE与MODE_WORLD_WRITEABLE分别给了其它用户的读写权限
commit与apply的不一样主要在于:commit直接在本身的线程等待写入硬盘任务的执行,且commit一次就写一次。而apply不会等待写入硬盘,且8.0以后会根据当前最新的内存代数来过滤掉以前的全部内存修改,只保存最后一次内存修改。
apply提交时会生成一个等待任务放到QueuedWork的一个等待列表里,在Activity的pause、Stop,或者Service的stop执行时,会依次调用这个等待列表的任务,保证每一个等待列表所等待的任务均可以执行。若未执行完毕则会致使ANR
8.0的优化方案为