SharedPreference源码解析

来源:互联网 发布:mac steam 目录 编辑:程序博客网 时间:2024/06/03 17:12

概要:

    SharedPreference属于轻量级的键值存储方式,以xml文件保存。作为Android储数据的

一个重要的方式,值得透彻分析一下。


SharedPreference的获取方式:

 首先SharedPreference的获取方式,有两种ActivitygetSharedPreference(int mode)Context

getSharedPreferenceString name, int mode;

先看ActivitygetSharedPreferenceint mode):

public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {//getLocalClassName 就是Activity的类名    return getSharedPreferences(getLocalClassName(), mode);}
@Overridepublic SharedPreferences getSharedPreferences(String name, int mode) {    return mBase.getSharedPreferences(name, mode);//mBase是Context实例}

所以最后还是调用了Context.getSharedPreference(String name, int mode),但是Context是抽象类,

它的的实现是ContextImp(这涉及到Activity的启动过程,不在这里赘述了)。

 现在我们来看一下ContextImp的getSharedPreference(String name, int mode)(为了方便展示

我会去掉一些多余的代码)

@Overridepublic SharedPreferences getSharedPreferences(String name, int mode) {        File file;        synchronized (ContextImpl.class) {            if (mSharedPrefsPaths == null) {                mSharedPrefsPaths = new ArrayMap<>();            }            file = mSharedPrefsPaths.get(name);            if (file == null) {                file = getSharedPreferencesPath(name);//创建file                mSharedPrefsPaths.put(name, file);            }        }        return getSharedPreferences(file, mode);//获取SharedPreference。    }
这里会先根据name判断相应的File是否存在,如果不存在就利用getSharedPreferencesPath(name)创建一个新

File,然后再回调getSharedPreferences(File file, int mode)。

其中创建File的函数:getSharedPreferencesPath(name):

    @Override    public File getSharedPreferencesPath(String name) {        return makeFilename(getPreferencesDir(), name + ".xml");//getPreferencesDir父目录的确认    }
    private File makeFilename(File base, String name) {        if (name.indexOf(File.separatorChar) < 0) {            return new File(base, name);        }    }
其中getPreferencesDir的确认涉及到了PackageInfo的datadir,和process.myUid();

 我们继续看getSharedPreference(File file,int mode);

    @Override    public SharedPreferences getSharedPreferences(File file, int mode) {        checkMode(mode);        SharedPreferencesImpl sp;        synchronized (ContextImpl.class) {            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;    }

    private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {        if (sSharedPrefsCache == null) {            sSharedPrefsCache = new ArrayMap<>();        }        final String packageName = getPackageName();        ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);        if (packagePrefs == null) {            packagePrefs = new ArrayMap<>();            sSharedPrefsCache.put(packageName, packagePrefs);        }        return packagePrefs;    }

我们先拿到ArrayMap<File, SharedPreferencesImp>类型的map,再从map中检查,没有则重新创建

其中SharedPreferencesImp就是SharedPreference的实现类。

  最后我们查看SharedPreferencesImp类:

   final class SharedPreferencesImpl implements SharedPreferences {       SharedPreferencesImpl(File file, int mode) {           mFile = file;           mBackupFile = makeBackupFile(file);           mMode = mode;           mLoaded = false;           mMap = null;           startLoadFromDisk();//加载数据   }   private void startLoadFromDisk() {        synchronized (mLock) {            mLoaded = false;        }        new Thread("SharedPreferencesImpl-load") {            public void run() {                 loadFromDisk();            }        }.start();   }}
可以看到,SharedPreferencesImp的构造中,开启线程去加载文件xml的数据。

  总结:SharedPreference获取的实例是一个单例,且在创建的时候开线程去解析加载文件数据。


SharedPreference.edit()获取Editor。

    public Editor edit() {        synchronized (mLock) {            awaitLoadedLocked();        }        return new EditorImpl();    }
可以看到返回了一个Editor的实现类EditorImpl,但是前面有个方法awaitLoadedLocked()方法,采用的

是await和notify,处理并发,实现逻辑:loadFromDisk数据加载完成前,awaitLoadedLocked处于阻塞状态。


Editor.putxxxx方法,这里举例putString(String key, String value)

    public Editor putString(String key, @Nullable String value) {        synchronized (mLock) {            mModified.put(key, value);            return this;        }    }

很简单,就是将数据放到内存中。


Editor.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;   }
真正重要的方法就两个commitToMemory将改变数据和原先的数据整理在一起(Map中,待写入数据),

封装到MemoryCommitResult,然后enqueueDiskWrite正式写到磁盘。

     commitToMemory{        long memoryStateGeneration;        List<String> keysModified = null;        Set<OnSharedPreferenceChangeListener> listeners = null;        Map<String, Object> mapToWriteToDisk;        synchronized (SharedPreferencesImpl.this.mLock) {                if (mDiskWritesInFlight > 0) {                    mMap = new HashMap<String, Object>(mMap);                }                mapToWriteToDisk = mMap;                synchronized (mLock) {//整合数据                    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 (!mMap.containsKey(k)) {                                continue;                            }                            mMap.remove(k);                        } else {                            if (mMap.containsKey(k)) {                                Object existingValue = mMap.get(k);                                if (existingValue != null && existingValue.equals(v)) {                                    continue;                                }                            }                            mMap.put(k, v);                        }                    }                    mModified.clear();                 }         }//封装到MemoryCommitResultzhong         return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,                 mapToWriteToDisk);     }
enqueueDiskWrite:
    private void enqueueDiskWrite(final MemoryCommitResult mcr,                                  final Runnable postWriteRunnable) {        final boolean isFromSyncCommit = (postWriteRunnable == null);        final Runnable writeToDiskRunnable = new Runnable() {                public void run() {                    synchronized (mWritingToDiskLock) {                        writeToFile(mcr, isFromSyncCommit);                    }                    synchronized (mLock) {                        mDiskWritesInFlight--;                    }                    if (postWriteRunnable != null) {                        postWriteRunnable.run();                    }                }            };        if (isFromSyncCommit) {            synchronized (mLock) {                wasEmpty = mDiskWritesInFlight == 1;            }            if (wasEmpty) {                writeToDiskRunnable.run();//关键点,说明是同步进行的,在主线调用,在主线执行。                return;            }        }
       QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);//apply(),则是放在任务队列中,异步执行    }
    private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {        try {此方法最重要的函数,调用XmlUtils写入操作。            XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);        } catch (XmlPullParserException e) {            Log.w(TAG, "writeToFile: Got exception:", e);        } catch (IOException e) {            Log.w(TAG, "writeToFile: Got exception:", e);        }    }

Eidtor.apply()

与Commit类似,只是它是异步的。在最后一步将任务放在任务队列中,多个任务线性一次执行。

又因为SharePreferenceImp单例原因,使用apply异步操作也是安全操作。推荐使用。