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)