先来看下SharedPreferences的使用方法
SharedPreferences存数据:
//获得SharedPreferences的实例 sp_name是文件名SharedPreferences sp = getSharedPreferences("sp_name", Context.MODE_PRIVATE);//获得Editor 实例SharedPreferences.Editor editor = sp.edit();//以key-value形式保存数据editor.putString("data_key", "data");//apply()是异步写入数据editor.apply(); //commit()是同步写入数据 //editor.commit();
SharedPreferences取数据:
//获得SharedPreferences的实例SharedPreferences sp = getSharedPreferences("sp_key", Context.MODE_PRIVATE);//通过key值获取到相应的data,如果没取到,则返回后面的默认值String data = sp.getString("data_key", "defaultValue");
数据以xml形式存储在/data/data/项目包名/shared_prefs/sp_name.xml里,如图:
xml.png
sp_name.png
以上是SharedPreferences的简单用法,下面从源码角度来看下整个过程:
先把结论贴出来:
1. SharedPreferences读取xml文件时,会以DOM方式解析(把整个xml文件直接加载到内存中解析),在调用getXXX()方法时取到的是内存中的数据,方法执行时会有个锁来阻塞,目的是等待文件加载完毕,没加载完成之前会wait()。
2. SharedPreferences写文件时,如果调用的commit(),会将数据同步写入内存中,内存数据更新,再同步写入磁盘中;如果调用的apply(),会将数据同步写入内存中,内存数据更新,然后异步写人磁盘,也就是说可能写磁盘操作还没有完成就直接返回了。在主线程中建议使用apply(),因为同步写磁盘,当文件较大时,commit()会等到写磁盘完成再返回,可能会有ANR问题。
3. SP第一次初始化到读取到数据存在一定延迟,因为需要到文件中读取数据,因此可能会对UI线程流畅度造成一定影响。
我们通过context.getSharedPreferences方法获取SharedPreferences实例
//name是存储的文件名,mode是创建文件时的模式public abstract SharedPreferences getSharedPreferences(String name, int mode);
Context里面的getSharedPreferences方法是抽象方法,接着就找到了Context的实现类是ContextImpl:
//Map from preference name to generated path. //mSharedPrefsPaths为保存文件地址的Mapprivate ArrayMap<String, File> mSharedPrefsPaths;@Overridepublic SharedPreferences getSharedPreferences(String name, int mode) { // At least one application in the world actually passes in a null // name. This happened to work because when we generated the file name // we would stringify it to "null.xml". Nice. if(mPackageInfo.getApplicationInfo().targetSdkVersion<Build.VERSION_CODES.KITKAT) { //如果参数name为null,则直接创建一个null.xml的文件 if (name == null) { name = "null"; } } File file; synchronized (ContextImpl.class) { //第一次进的时候初始化 if (mSharedPrefsPaths == null) { mSharedPrefsPaths = new ArrayMap<>(); } file = mSharedPrefsPaths.get(name); if (file == null) { //通过getPreferencesDir()来获取shared_prefs目录,然后根据文件名加上xml后缀 file = getSharedPreferencesPath(name); mSharedPrefsPaths.put(name, file); } } return getSharedPreferences(file, mode);}@Overridepublic SharedPreferences getSharedPreferences(File file, int mode) {//Android N以后不支持Context.MODE_WORLD_WRITEABLE和Context.MODE_WORLD_READABLE模式 checkMode(mode); SharedPreferencesImpl sp; synchronized (ContextImpl.class) { //通过getSharedPreferencesCacheLocked()根据包名来获得缓存preferences的Map final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); //如果静态内存缓存中有,直接取出来 sp = cache.get(file); if (sp == null) { //缓存中没有,new一个sp出来(SharedPreferences是一个接口,SharedPreferencesImpl是其实现类) sp = new SharedPreferencesImpl(file, mode); cache.put(file, sp); return sp; } } if ((mode & Context.MODE_MULTI_PROCESS) != 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { // If somebody else (some other process) changed the prefs // file behind our back, we reload it. This has been the // historical (if undocumented) behavior. //MODE_MULTI_PROCESS多进程模式或者SDK<11时,会重新从磁盘加载文件,不过多进程模式 //已经被deprecated了,官方建议使用ContentProvider来处理多进程访问. sp.startReloadIfChangedUnexpectedly(); } return sp;}
//通过getPreferencesDir()来获取shared_prefs目录,然后根据文件名加上xml后缀@Overridepublic File getSharedPreferencesPath(String name) { return makeFilename(getPreferencesDir(), name + ".xml"); }
//Android N以后不支持Context.MODE_WORLD_WRITEABLE和Context.MODE_WORLD_READABLE模式private void checkMode(int mode) { if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) { if ((mode & MODE_WORLD_READABLE) != 0) { throw new SecurityException("MODE_WORLD_READABLE no longer supported"); } if ((mode & MODE_WORLD_WRITEABLE) != 0) { throw new SecurityException("MODE_WORLD_WRITEABLE no longer supported"); } }}
/** * Map from package name, to preference name, to cached preferences. *///sSharedPrefsCache根据包名来缓存preferences的Mapprivate static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() { if (sSharedPrefsCache == null) { sSharedPrefsCache = new ArrayMap<>(); } final String packageName = getPackageName(); //首先在静态缓存sSharedPrefsCache中查找preferences的Map,如果有,直接取出来返回 ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName); //如果静态缓存中没有,直接new一个Map并且加到静态缓存sSharedPrefsCache中 if (packagePrefs == null) { packagePrefs = new ArrayMap<>(); sSharedPrefsCache.put(packageName, packagePrefs); } return packagePrefs;}
下面再来分析一下SharedPreferencesImpl实例化过程,也是从磁盘读取文件到内存中的过程:
SharedPreferencesImpl(File file, int mode) { mFile = file; //产生一个.bak结尾的临时File mBackupFile = makeBackupFile(file); //加载模式 mMode = mode; //标志位,表示从磁盘加载到内存中是否完成 mLoaded = false; //保存在内存中sp对象的Map mMap = null; //从磁盘中加载文件到内存中 startLoadFromDisk(); }static File makeBackupFile(File prefsFile) { return new File(prefsFile.getPath() + ".bak");}private void startLoadFromDisk() { synchronized (this) { mLoaded = false; } //新起一个Thread开始加载 new Thread("SharedPreferencesImpl-load") { public void run() { loadFromDisk(); } }.start();}private void loadFromDisk() { synchronized (SharedPreferencesImpl.this) { if (mLoaded) { return; } if (mBackupFile.exists()) { mFile.delete(); mBackupFile.renameTo(mFile); } } // Debugging if (mFile.exists() && !mFile.canRead()) { //忘了写权限 Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission"); } Map map = null; StructStat stat = null; try { stat = Os.stat(mFile.getPath()); if (mFile.canRead()) { BufferedInputStream str = null; try { //SharedPreferences文件以流形式读出来 str = new BufferedInputStream( new FileInputStream(mFile), 16*1024); //读取xml中的内容,构造一个Map赋值给下面的mMap map = XmlUtils.readMapXml(str); } catch (XmlPullParserException | IOException e) { Log.w(TAG, "getSharedPreferences", e); } finally { IoUtils.closeQuietly(str); } } } catch (ErrnoException e) { /* ignore */ } synchronized (SharedPreferencesImpl.this) { //标记位,数据已经加载完成 mLoaded = true; if (map != null) { //赋值给内存中的mMap mMap = map; //设置时间戳和文件的大小 mStatTimestamp = stat.st_mtime; mStatSize = stat.st_size; } else { mMap = new HashMap<>(); } // 通知所有线程被SharedPreferencesImpl.this对象锁住的数据已经加载完成了,数据可以使用了。 notifyAll(); }}
最后来看下读文件和写文件,首先是读文件(文件从磁盘加载到内存中),这里看的SharedPreferencesImpl类中getString(String key, String defValue)地源码,其他getXXX()也是一样的。
@Nullablepublic String getString(String key, @Nullable String defValue) { synchronized (this) { //同步等待文件从磁盘加载到内存完成为止,否自wait() awaitLoadedLocked(); //从内存中的mMap直接取值 String v = (String)mMap.get(key); return v != null ? v : defValue; }}private void awaitLoadedLocked() { if (!mLoaded) { // Raise an explicit StrictMode onReadFromDisk for this // thread, since the real read will be in a different // thread and otherwise ignored by StrictMode. BlockGuard.getThreadPolicy().onReadFromDisk(); } while (!mLoaded) { try { wait(); } catch (InterruptedException unused) { } }}
写文件(先把要修改的数据写到内存中,再写入磁盘中):
public final class EditorImpl implements Editor { // 保存putXXX()方法时提供的所有要提交修改的数据 private final Map<String, Object> mModified = Maps.newHashMap(); private boolean mClear = false; public Editor putString(String key, @Nullable String value) { synchronized (this) { mModified.put(key, value); return this; } }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; } notifyListeners(mcr); return mcr.writeToDiskResult;} }public void apply() { //同步写入内存中 final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { public void run() { try { //阻塞等待写操作完成 mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } } }; QueuedWork.add(awaitCommit); Runnable postWriteRunnable = new Runnable() { public void run() { awaitCommit.run(); QueuedWork.remove(awaitCommit); } }; //异步写入磁盘 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); // Okay to notify the listeners before it's hit disk // because the listeners should always get the same // SharedPreferences instance back, which has the // changes reflected in memory. notifyListeners(mcr); }private void notifyListeners(final MemoryCommitResult mcr) { if (mcr.listeners == null || mcr.keysModified == null || mcr.keysModified.size() == 0) { return; } if (Looper.myLooper() == Looper.getMainLooper()) { for (int i = mcr.keysModified.size() - 1; i >= 0; i--) { final String key = mcr.keysModified.get(i); for (OnSharedPreferenceChangeListener listener : mcr.listeners) { if (listener != null) { listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key); } } } } else { // Run this function on the main thread. ActivityThread.sMainThreadHandler.post(new Runnable() { public void run() { notifyListeners(mcr); } }); }}// Returns true if any changes were madeprivate MemoryCommitResult //commit()和apply()两个方法都调用了commitToMemory。该方法主要根据mModified和是否被//clear修改内存中mMap的值,然后返回写磁盘需要的一些相关值commitToMemory() { MemoryCommitResult mcr = new MemoryCommitResult(); synchronized (SharedPreferencesImpl.this) { // We optimistically don't make a deep copy until // a memory commit comes in when we're already // writing to disk. if (mDiskWritesInFlight > 0) { // We can't modify our mMap as a currently // in-flight write owns it. Clone it before // modifying it. // noinspection unchecked // 当有多个写操作时,clone一份 mMap = new HashMap<String, Object>(mMap); } mcr.mapToWriteToDisk = mMap; //未完成的写操作数+1 mDiskWritesInFlight++; boolean hasListeners = mListeners.size() > 0; if (hasListeners) { mcr.keysModified = new ArrayList<String>(); mcr.listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); } synchronized (this) { if (mClear) { if (!mMap.isEmpty()) { mcr.changesMade = true; mMap.clear(); } mClear = false; } 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); } mcr.changesMade = true; if (hasListeners) { mcr.keysModified.add(k); } } mModified.clear(); } } return mcr;}// Return value from EditorImpl#commitToMemory()//写磁盘需要的相关值private static class MemoryCommitResult { public boolean changesMade; // any keys different? public List<String> keysModified; // may be null public Set<OnSharedPreferenceChangeListener> listeners; // may be null public Map<?, ?> mapToWriteToDisk; public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1); public volatile boolean writeToDiskResult = false; public void setDiskWriteResult(boolean result) { writeToDiskResult = result; writtenToDiskLatch.countDown(); }}
commit()和apply()这两个方法都是首先修改内存中缓存的mMap的值,然后将数据写到磁盘中。它们的主要区别是commit会等待写入磁盘后再返回,而apply则在调用写磁盘操作后就直接返回了,但是这时候可能磁盘中数据还没有被修改。
作者:_小马快跑_
链接:https://www.jianshu.com/p/0648bdc23885