Android轻量级存储源码分析
Android提供了轻量级的数据存储方式,那就是SharePreference数据存储。其实质也就是文件存储,只不过是符合XML标准的文件存储而已,是Android中比较常用的简易型数据存储解决方案。下面简单分析下源码实现,源码都是一些独立的东西,实现也比较符合大家的编码习惯,相比其他模块源码比较简单一些
留个小问题:
1,这样存储基本类型数据有问题吗?
SharedPreferences sp = getSharedPreferences("sp_demo", Context.MODE_PRIVATE);sp.edit().putString("name", "小张");sp.edit().putInt("age", 11);sp.edit().commit();
2,这样存储基本基本类型数据有什么不好的地方?
SharedPreferences sp = getSharedPreferences("sp_demo", Context.MODE_PRIVATE);SharedPreferences.Editor editor = sp.editor();editor.putString("name", "小张");editor.commit();//SharedPreferences.Editor editor = sp.editor();editor.putInt("age", 11);editor.commit();
先从获取SharedPreference得入口getSharedPreferences(String name, int mode)开始
@Override public SharedPreferences getSharedPreferences(String name, int mode) {(mPackageInfo.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) { if (name == null) { name = "null"; } } File file; synchronized (ContextImpl.class) { if (mSharedPrefsPaths == null) { mSharedPrefsPaths = new ArrayMap<>(); } //字段存储在xml文件中,而xml文件是放在ArrayMap中 file = mSharedPrefsPaths.get(name); if (file == null) { file = getSharedPreferencesPath(name); mSharedPrefsPaths.put(name, file); } } return getSharedPreferences(file, mode); }
原来我们的sp对象放在一个ArrayMap< String, File> mSharedPrefsPaths,看来name--File进行了关联,看下这个getSharedPreferencesPath(String name)到底干了啥
public File getSharedPreferencesPath(String name) { return makeFilename(getPreferencesDir(), name + ".xml"); }
原来这货就是创建xml文件,结合getSharedPreferences(String name, int mode),那么可以理解在第一次存储数据放在某个xml时候,如果没有xml文件,去创建这个名称的xml文件,那么我们继续往下看如何获取SharedPreference对象
@Override public SharedPreferences getSharedPreferences(File file, int mode) { checkMode(mode); SharedPreferencesImpl sp; synchronized (ContextImpl.class) { final ArrayMap cache = getSharedPreferencesCacheLocked(); sp = cache.get(file); if (sp == null) { sp = new SharedPreferencesImpl(file, mode); cache.put(file, sp); return sp; } } return sp; }
要看sp对象尽然还要进一层,为啥这么艰难,nm
private ArrayMap getSharedPreferencesCacheLocked() { if (sSharedPrefsCache == null) { sSharedPrefsCache = new ArrayMap<>(); } final String packageName = getPackageName(); ArrayMap packagePrefs = sSharedPrefsCache.get(packageName); if (packagePrefs == null) { packagePrefs = new ArrayMap<>(); sSharedPrefsCache.put(packageName, packagePrefs); } return packagePrefs; }
原来,这家伙是把File对象和SharedPreferencesImpl关联到一块了,存在一个ArrayMap中,那么我们小结一下,我们命名一个fileName的xml文件,那么会生成一个File,所以有了fileName-File对象关联在一块,紧接着,File--SharedPreferencesImpl关联在一块,那么我猜测最终所有的存储逻辑是在SharedPreferencesImpl这个实现类中实现,请继续看
public Editor edit() { synchronized (mLock) { awaitLoadedLocked(); } return new EditorImpl(); }
这个是Editor是SharedPreferencesImpl的内部类,是一个接口,0.001s看下暴露的方法
//Editor编辑接口提供的方法概览 public interface Editor { Editor putString(String key, String value); Editor putStringSet(String key, Set values); Editor putInt(String key, int value); Editor putLong(String key, long value); Editor putFloat(String key, float value); Editor putBoolean(String key, boolean value); Editor remove(String key); Editor clear(); boolean commit(); void apply(); }
public final class EditorImpl implements Editor { //mModified对的是一个String--Object的Map,其实就是存储所有put过来的key--value,放在内存中,内存中,内存中(重要事情说三遍) //突然间我发现getXXX时候可以从mModified里面取,不用读取文件了,那么每一回存储文件都会调用sp.editor(),进而每次都会new EditorImpl(),所以有很多个 mModified这样的map,有这么一瞬间我觉得很坑爹,这里问题留在这 private final Map mModified = Maps.newHashMap(); private boolean mClear = false; //举例分析存储boolean类型,其他的意淫一下 public Editor putBoolean(String key, boolean value) { synchronized (mLock) { //来一个我就存一个,来俩我存一对,存的越多,赚的越多 mModified.put(key, value); return this; } } public Editor remove(String key) { synchronized (mLock) { mModified.put(key, this); return this; } } public Editor clear() { synchronized (mLock) { mClear = true; return this; } } //你们要的commit在这 public boolean commit() { //往内存里存储,并且还造了个数据结构,为啥还要往内存里写,存个文件写了这么多东西到内存里,当内存不要钱??? MemoryCommitResult mcr = commitToMemory(); //往存储空间里写数据,这个是同步的 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null /* sync write on this thread okay */); try { //什么时候写完,什么时候继续后面的事情 mcr.writtenToDiskLatch.await(); } catch (InterruptedException e) { return false; } finally { } notifyListeners(mcr); return mcr.writeToDiskResult; } //写内存的这货在这,看他干啥了 private MemoryCommitResult commitToMemory() { //从这货名字可以猜处它最后应该是要写到内存里 Map mapToWriteToDisk; //这又出来个mMap,好乱,猜一下m开头,成员变量,没错,这个就是你以后每次getXXX(String key)时候要从内存中操作的map,你app所有存储的东西都在这个成员变量里 mapToWriteToDisk = mMap;//结果赋给这个马甲map,这个马甲map待会肯定要去变成xml文件内容存到手机里去 synchronized (mLock) { boolean changesMade = false; //mClear好眼熟,成员变量,还记得clear()方法里对mClear进行赋值吗?.....原来clear的操作在这终结 if (mClear) { if (!mMap.isEmpty()) { changesMade = true; mMap.clear(); } mClear = false; } for (Map.Entry e : mModified.entrySet()) { String k = e.getKey(); Object v = e.getValue(); //map.remove应该是你调用了editor.remove()方法以后要进行最终结果map的remove,这里为啥是value == this就给remove了呢? if (v == this || v == null) { if (!mMap.containsKey(k)) { continue; } mMap.remove(k); } else { if (mMap.containsKey(k)) { Object existingValue = mMap.get(k); if (existingValue != null && existingValue.equals(v)) { continue; } } //大胸弟我终于看到我曾经跨过山河大海put的值,你现在终于放进这个mMap里了 mMap.put(k, v); } if (hasListeners) { keysModified.add(k); } } //本次commit临时存储用的map可以皈依佛门了 mModified.clear(); memoryStateGeneration = mCurrentMemoryStateGeneration; } } //我想要的东西都很贵,我想要去的地方都很远,不造一个包装数据结构的类怎么把他们打包带走 return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners, mapToWriteToDisk); } //小姐姐长啥样,一睹芳容 private static class MemoryCommitResult { final long memoryStateGeneration; @Nullable final List keysModified; @Nullable final Set listeners; final Map mapToWriteToDisk; final CountDownLatch writtenToDiskLatch = new CountDownLatch(1); @GuardedBy("mWritingToDiskLock") volatile boolean writeToDiskResult = false; boolean wasWritten = false; private MemoryCommitResult(long memoryStateGeneration, @Nullable List keysModified, @Nullable Set listeners, Map 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.countDown(); } } //原来就放了几个成员变量,盛放下打包的物品,太丑陋了,这不是去小姐姐家的路 //存完内存中的map以后,俺们看看如何存到disk里 private void enqueueDiskWrite(final MemoryCommitResult mcr,final Runnable postWriteRunnable) { //原来这货根据runnabl == null就是comit,不是就是apply模式 final boolean isFromSyncCommit=(postWriteRunnable == null); final Runnable writeToDiskRunnable = new Runnable() { public void run() { synchronized (mWritingToDiskLock) { //最终写文件到disk的操作在这 writeToFile(mcr,isFromSyncCommit); } synchronized (mLock) { mDiskWritesInFlight--; } if (postWriteRunnable != null) { postWriteRunnable.run(); } } }; //确认过眼神,这是commit模式 if (isFromSyncCommit) { boolean wasEmpty = false; synchronized (mLock) { wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { //commit模式下,当前只有一个写入任务,直接在当前线程write writeToDiskRunnable.run(); return; } } //apply模式 || 当前是commit模式,但是有很多个写入任务,原来这么坑爹commit在任务多的时候也会apply,为啥嘞?要是胡写乱调用commit,阻塞了UI线程那不就挂了(这是我猜测) QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); } public void apply() { //和commit一样,三七二十一先给它在内存里的mMap操作完再说,再处理打包回来的马甲map final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { public void run() { try { //这个包装的数据结构的writtenToDiskLatch到底是啥勒,他是一个标志位,表示当前mac这个文件是否写入到disk完成,如果完成在继续下一个mc数据结构写入,那么这货到底是在什么时候赋值的呢? mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } QueuedWork.addFinisher(awaitCommit); Runnable postWriteRunnable = new Runnable() { public void run() { awaitCommit.run(); QueuedWork.removeFinisher(awaitCommit); } }; //这个上面commit已经分析过,runnable !=null,新线程写文件 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); notifyListeners(mcr); } 到这里我们知道了为啥apply和commit一个异步一个同步了,以及我们getXX是从mMap这个成员变量中获取的了,那么万水千山还有一步,说了半天到底怎么从把mMap的马甲Map这个mWriteToDiskMap,所在的包装类MemoryCommitResult,这个破玩意如何变成xml,再把xml写入到disk里呢?最后一个机关来了 //我靠好长啊 private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) { //mFile是个成员变量,是当初传过来的File文件,判空是因为有地方会有mFile.delete()操作 boolean fileExists = mFile.exists(); if (fileExists) { boolean needsWrite = false; //mDiskStateGeneration是一个标志位,在构造数据包装结构MemoryCommitResult的时候赋值过 if (mDiskStateGeneration < mcr.memoryStateGeneration) { if (isFromSyncCommit) { needsWrite = true; } else { synchronized (mLock) { if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) { needsWrite = true; } } } } if (!needsWrite) { mcr.setDiskWriteResult(false, true); return; } boolean backupFileExists = mBackupFile.exists(); if (!backupFileExists) { mcr.setDiskWriteResult(false, false); return; } } else { mFile.delete(); } } try { FileOutputStream str = createFileOutputStream(mFile); if (str == null) { mcr.setDiskWriteResult(false, false); return; } //把mMap的马甲Map结果集写入到mFile文件,mFile是啥,waht?,当初你在ContextImpl里getSharedPreference(String name,Mode mode)传入一个name,生成一个mFile,然后new SharedPreferenceImpl(File file,Mode mode),这个mFile就来了,如今将结果map写入到mFile里,木毛病 XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str); //将xml文件最终写入到disk里 FileUtils.sync(str); str.close(); ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0); try { final StructStat stat = Os.stat(mFile.getPath()); synchronized (mLock) { mStatTimestamp = stat.st_mtime; mStatSize = stat.st_size; } } catch (ErrnoException e) { // Do nothing } mBackupFile.delete(); mDiskStateGeneration = mcr.memoryStateGeneration; mcr.setDiskWriteResult(true, true); mSyncTimes.add(Long.valueOf(fsyncDuration).intValue()); mNumSync++; return; } catch (XmlPullParserException e) { } catch (IOException e) { } if (mFile.exists()) { if (!mFile.delete()) { } } mcr.setDiskWriteResult(false, false); }
小结
1,SharedPreference保存数据的形式是xml文件,并且创建时不同的name对应不同的xml文件,本质是文件读写。
2,在SharedPreferences的Editor中如果用commit()方法提交数据,其过程是先把数据更新到内存,然后在当前线程中写文件操作,如果用的是apply()方法提交数据,首先也是写到内存,接着在一个新线程中异步写文件,注意:操作commit时有锁操作,所以效率很低一些,如果当我们一次有多个修改写操作时等都批量put完了再一次提交确认,这样可以提高效率
3,SharedPreferences在实例化时首先会从disk异步读文件,然后缓存在内存中,接下来的读操作都是内存缓存操作而不是文件操作。
4,由于键值对在内存中保存了一份,放在mMap中,用内存换取getXXX(String key)时候速度上的提高,所以如果存储大量数据在里面,那么这个map将会很大,内存上存在问题,所以回到题目上来,轻量级存储,不适合大而复杂的数据存储
更多相关文章
- android 系统启动
- Android文件存储位置简述
- Android(安卓)自定义权限 ( )
- Android中使用SAX方式解析XML文件
- 布局中文件中【控件间距参数详解以及单位选择】
- 关于android中使用new Message的内存泄露问题
- Android中的文件权限操作
- Android(安卓)控件二 Button
- NPM 和webpack 的基础使用