SharedPreference源码解析

概要:

    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);
}
@Override
public 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)(为了方便展示

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

@Override
public 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异步操作也是安全操作。推荐使用。


    原文作者:小松鼠_莲鹿
    原文地址: https://blog.csdn.net/wangsongbin893603021/article/details/78296770
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞