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将会很大,内存上存在问题,所以回到题目上来,轻量级存储,不适合大而复杂的数据存储

更多相关文章

  1. android 系统启动
  2. Android文件存储位置简述
  3. Android(安卓)自定义权限 ( )
  4. Android中使用SAX方式解析XML文件
  5. 布局中文件中【控件间距参数详解以及单位选择】
  6. 关于android中使用new Message的内存泄露问题
  7. Android中的文件权限操作
  8. Android(安卓)控件二 Button
  9. NPM 和webpack 的基础使用

随机推荐

  1. android 电池(一):锂电池基本原理篇
  2. Android(安卓)android.text.TextWatcher
  3. android获得手机的电量
  4. 关于Android(安卓)studio的模拟器无法打
  5. Android的权限分类
  6. 在低版本Android上使用Material Design—
  7. Android模拟器无法上网解决方案
  8. android navigation使用
  9. Activity属性
  10. Android(安卓)Log 机制