SharedPreferences解析
一、概述
SharedPreferences(简称SP)是Android中很常用的数据存储方式,SP采用key-value(键值对)形式,主要用于轻量级的数据存储,尤其适合保存应用的配置参数,但不建议使用SP来存储大规模的数据,可能会降低性能。
SP采用xml文件格式来保存数据,改文件所在目录位于/data/data/shared_prefs/。
二、使用
1.得到SharedPreferences对象
private SharedPreferences mSharedPreferences; private final static String PREFRENCE_FILE_KEY = "com.zhangmiao.shared_preferences"; mSharedPreferences = getSharedPreferences(PREFRENCE_FILE_KEY, MODE_PRIVATE);
2.添加数据
final SharedPreferences.Editor editor = mSharedPreferences.edit(); editor.putInt("id",1); editor.putString("name","小熊"); editor.putInt("age",24); editor.commit();
3.获取数据
TextView textView = (TextView)findViewById(R.id.text); String message = "id = " + mSharedPreferences.getInt("id",-1) + ",name = " + mSharedPreferences.getString("name","无") + ",age = " + mSharedPreferences.getInt("age",-1)+"。"; textView.setText(message);
4.查看生成的sharedpreferences.xml文件
我使用的是adb命令查看的文件,命令如下:(系统是window10)
adb shell
run-as com.zhangmiao.myapplication(应用包名)
ls(查看xml文件的名称)
cat com.zhangmiao.shared_preferences.xml(查看xml文件)
三、解析SharedPreferences
1.获取方式
1.1.getSharedPreferences(String name, int mode)
@Override public SharedPreferences getSharedPreferences(String name, int mode) { return mBase.getSharedPreferences(name, mode); }
这里的mBase就是Context类,Context的具体实现类是ContextImpl类,所以直接查看ContextImpl类中的getSharedPreferences方法。
@Override public 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) { if (name == null) { name = "null"; } } File file; synchronized (ContextImpl.class) { if (mSharedPrefsPaths == null) { mSharedPrefsPaths = new ArrayMap<>(); } file = mSharedPrefsPaths.get(name); if (file == null) { file = getSharedPreferencesPath(name);【1.3】 mSharedPrefsPaths.put(name, file); } } return getSharedPreferences(file, mode);【1.2】 }
第一步:判断版本号是否小于17,如果是,判断name是否为null,如果是,则设置name为“null”。
第二步:同步ContextImpl.class,如果mSharedPrefsPaths为null,则初始化mSharedPrefsPath,判断name对应的file是否为null,则调用getSharedPreferencesPath(name)初始化file,并加入mSharedPrefsPaths中。
第三步:返回getSharedPreferences(file, mode)方法。
1.2.public SharedPreferences getSharedPreferences(File file, int mode)
@Override public SharedPreferences getSharedPreferences(File file, int mode) { checkMode(mode);【1.4】 SharedPreferencesImpl sp; synchronized (ContextImpl.class) { final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();【1.5】 sp = cache.get(file); if (sp == null) { sp = new SharedPreferencesImpl(file, mode); cache.put(file, sp); return sp; } } if ((mode & Context.MODE_MULTI_PROCESS) != 0 || getApplicationInfo().targetSdkVersion < 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. sp.startReloadIfChangedUnexpectedly(); } return sp; }
第一步:检查mode的类型,如果没有MODE_WORLD_READABLE与MODE_WORLD_WRITEABLE,则抛出异常。
第二步:使用getSharedPreferencesCacheLocked()方法,得到cache数组映射,得到file对应的sp,如果为空,初始化并家务cache中。
第三步:调用sp的startReloadIfChangedUnexpectedly()方法。
1.3.public File getSharedPreferencesPath(String name)
@Override public File getSharedPreferencesPath(String name) { return makeFilename(getPreferencesDir(), name + ".xml"); }
继续查看makeFileName()方法
private File makeFilename(File base, String name) { if (name.indexOf(File.separatorChar) < 0) { return new File(base, name); } throw new IllegalArgumentException( "File " + name + " contains a path separator"); }
如果name包含文件分隔符则生成xml文件,否则抛出异常。
1.4.private void checkMode(int mode)
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"); } } }
如果sdk的版本大于等于24,则mode包含MODE_WORLD_READABLE与MODE_WORLD_WRITEABLE则抛出异常。意思是从版本24开始,创建的SP文件模式,不允许MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE模块。
当设置MODE_MULTI_PROCESS模式,则每次getSharedPreferences过程,会检查SP文件上次修改时间和文件大小,一旦所有修改则会重新从磁盘加载文件。
1.5.private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked()
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; }
第一步:如果sSharedPrefsCache为null,则初始化它。
第二步:调用getPackageName()方法得到packageName。
第三步:得到sSharedPrefsCache中packageName对应的ArrayMap对象packagePrefs。
第四步:如果packagePrefs为null,则初始化它,并且添加到sSharedPrefsCache中。
总结:获取SharedPreference只要实现在ContextImpl中,mSharedPrefsPaths包含name对应的File,sSharedPrefsCache中包含packageName对应的packagePrefs,packagePrefs中包含File对应的SharedPreferencesImpl。
而获取过程主要时生成File文件,初始化上面的三个ArrayMap,将本应用相关的数据添加进去,以及检测版本与mode,并抛出异常。
2.添加数据
添加数据使用到SharedPreferences.Editor类,SharedPreferences的实现是在ShredPreferencesImpl中,SharedPreferences.Editor的具体实现在ShredPreferencesImpl.EditorImpl中。
2.1.public Editor edit()
public Editor edit() { // TODO: remove the need to call awaitLoadedLocked() when // requesting an editor. will require some work on the // Editor, but then we should be able to do: // // context.getSharedPreferences(..).edit().putString(..).apply() // // ... all without blocking. synchronized (this) { awaitLoadedLocked();【2.2】 } return new EditorImpl(); }
同步this,调用awaitLoadedLocked()方法,返回EditorImpl对象。
2.2.private void awaitLoadedLocked()
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) { } } }
第一步:如果mLoaded为false,则调用BlockGuard.getThreadPolicy().onReadFromDisk()方法,忽略无用的线程。
第二步:如果mLoaded为false,没有加载完成,则等待。
2.3.put***(String key, *** value)系列方法
put***()方法基本相同,这里以putInt(String key, int value)为例介绍:
public Editor putInt(String key, int value) { synchronized (this) { mModified.put(key, value); return this; } }
同步this,将数据添加到mModified中,this是Editor对象。
2.4.public Editor remove(String key)
public Editor remove(String key) { synchronized (this) { mModified.put(key, this); return this; } }
同步this,添加到mModified的数据为key与this。
2.5.public Editor clear()
public Editor clear() { synchronized (this) { mClear = true; return this; } }
设置mClear为true。
2.6.public boolean commit()
public boolean commit() { MemoryCommitResult mcr = commitToMemory();【2.7】 SharedPreferencesImpl.this.enqueueDiskWrite(【2.9】 mcr, null /* sync write on this thread okay */); try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException e) { return false; } notifyListeners(mcr);【2.8】 return mcr.writeToDiskResult; }
第一步:调用commitToMemory()方法,得到mcr对象。
第二步:调用SharedPreferencesImpl的enqueueDiskWrite()方法。
第三步:调用mcr,writtenToDiskLatch.await()方法。
第四步:调用notifyListeners(mcr)方法。
2.7.private MemoryCommitResult commitToMemory()
private MemoryCommitResult 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 mMap = new HashMap<String, Object>(mMap); } mcr.mapToWriteToDisk = mMap; 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; }
第一步:同步SharedPreferencesImpl.this。
第二步:如果mDiskWritesInFlinght大于0,则初始化mMap。
第三步:设置mcr.mapToWriteToDisk为mMap,mDiskWritesInFlight加1。
第四步:如果存在listener,初始化mcr的keysModified和listeners。
第五步:同步this,如果mClear为true,调用mMap.clear();遍历mModified,如果value为this,则调用mMap.remove(k)方法,移除关键字;如果不是this,先判断mMap是否包含k与value,不包含,则调用mMap.put(k,v),在mcr.keysModified中添加k。
第六步:调用mModified.clear()方法。
2.8.private void notifyListeners(final MemoryCommitResult 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); } }); } }
第一步:如果mcr.listeners为null或者mcr.keysModified为null或者mcr.keysModified.size()等于0,则直接返回。
第二步:如果当前looper是mainLooper,遍历mcr.keysModified,得到mcr.keysModified.get(i)为key,遍历scr.Listeners,调用listener.onSharedPreferenceChanged()方法。
第三步:如果不是mainLooper,则在主线程中调用notifyListeners(mcr)方法。
2.9.private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable)
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) { final Runnable writeToDiskRunnable = new Runnable() { public void run() { synchronized (mWritingToDiskLock) { writeToFile(mcr);【2.10】 } synchronized (SharedPreferencesImpl.this) { mDiskWritesInFlight--; } if (postWriteRunnable != null) { postWriteRunnable.run(); } } }; final boolean isFromSyncCommit = (postWriteRunnable == null); // Typical #commit() path with fewer allocations, doing a write on // the current thread. if (isFromSyncCommit) { boolean wasEmpty = false; synchronized (SharedPreferencesImpl.this) { wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { writeToDiskRunnable.run(); return; } } QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable); }
第一步:创建writeToDiskRunnable线程,同步mWritingToDiskLock,调用writeFile(mcr)执行文件写入操作,同步SharedPreferencesImpl.this,将mDiskWritesInFlight减一,如果postWriteRunnable不为null,则运行postWriteRunnable。
第二步:如果isFromSyncCommit为true(commit方法会进入),同步ShreadPreferencesImpl.this,设置wasEmpty的值(因为commitToMemory过程会加1,则wasEmpty为true);如果wasEmpty为true,运行wirteToDiskRunnable线程,然后返回。
第三步:调用QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable)方法。
2.10.private void writeToFile(MemoryCommitResult mcr)
// Note: must hold mWritingToDiskLock private void writeToFile(MemoryCommitResult mcr) { // Rename the current file so it may be used as a backup during the next read if (mFile.exists()) { if (!mcr.changesMade) { // If the file already exists, but no changes were // made to the underlying map, it's wasteful to // re-write the file. Return as if we wrote it // out. mcr.setDiskWriteResult(true); return; } if (!mBackupFile.exists()) { if (!mFile.renameTo(mBackupFile)) { Log.e(TAG, "Couldn't rename file " + mFile + " to backup file " + mBackupFile); mcr.setDiskWriteResult(false); return; } } else { mFile.delete(); } } // Attempt to write the file, delete the backup and return true as atomically as // possible. If any exception occurs, delete the new file; next time we will restore // from the backup. try { FileOutputStream str = createFileOutputStream(mFile); if (str == null) { mcr.setDiskWriteResult(false); return; } XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str); FileUtils.sync(str); str.close(); ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0); try { final StructStat stat = Os.stat(mFile.getPath()); synchronized (this) { mStatTimestamp = stat.st_mtime; mStatSize = stat.st_size; } } catch (ErrnoException e) { // Do nothing } // Writing was successful, delete the backup file if there is one. mBackupFile.delete(); mcr.setDiskWriteResult(true); return; } catch (XmlPullParserException e) { Log.w(TAG, "writeToFile: Got exception:", e); } catch (IOException e) { Log.w(TAG, "writeToFile: Got exception:", e); } // Clean up an unsuccessfully written file if (mFile.exists()) { if (!mFile.delete()) { Log.e(TAG, "Couldn't clean up partially-written file " + mFile); } } mcr.setDiskWriteResult(false); }
第一步:如果mFile存在,如果mcr.changesMade为false(没有key发生改变,则直接饭后),设置mcr的DiskWriteResult为true,返回。如果mBackupFile不存在,则将mFile重命名为mBackupFile,设置mcr的DiskWriteResult为false,如果mBackupFile存在,则直接删除mFile。
第二步:将mMap全部信息写去文件中,调用Context.setFilePermissionsFromMode()方法。
第三步:得到Os.stat(mFile.getPath())的stat,设置mStatTimestamp与mStatSize。
第四步:写入成功后,删除mBackupFile备份文件。
第五步:设置mcr的DiskWriteResult为true,返回写入成功,唤醒等待线程。
第六步:如果文件mFile存在,则删除mFile,如果写入文件的操作失败,则删除未成功写入的文件。
第七步:设置mcr的DiskWriteResult为true,返回写入失败,唤醒等待线程。
2.11.public void apply()
public void apply() { final MemoryCommitResult mcr = commitToMemory();【2.7】 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);【2.8】 }
第一步:调用commitToMemory()方法。
第二步:创建线程awaitCommit调用mcr.writtenToDiskLatch.await()方法。
第三步:在QueuedWork添加awaitCommit线程。
第四步:创建线程postWriteRunnable调用awaitCommit.run()方法,从QueuedWork中移除awaitCommit。
第五步:调用SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable)方法。
第六步:调用notifyListeners(mcr)方法。
总结:所有的put、clear、remove都加了同步锁,如果有多次调用putString,建议使用putStringSet方法,减少锁的消耗。
edit()每次都是创建新的EditorImpl对象,getSharedPreferences()是从ContextImpl.sSharedPrefsCache唯一的SPI对象。
commit与apply的区别:
(1)apply没有返回值;commit有返回值能知道修改是否提交成功。
(2)apply是将修改提交到内存中,再异步提交到磁盘文件;commit是同步地提交到磁盘文件。
(3)多并发的提交commit是,需等待正在处理的commit数据更新到磁盘文件后才会继续往下执行,从而降低效率;而apply只是原子更新到内存,后调用apply函数会直接覆盖前面内存数据,从一定程度上提高很多效率。
3.获取数据
3.1.public Map<String, ?> getAll()
public Map<String, ?> getAll() { synchronized (this) { awaitLoadedLocked(); //noinspection unchecked return new HashMap<String, Object>(mMap); } }
第一步:调用awaitLoadedLocked()方法。
第二步:返回mMap的HashMap。
3.2.public String get***(String key, @Nullable *** defValue)
get***方法的实现基本相同,这里以getString()方法为例介绍。
@Nullable public String getString(String key, @Nullable String defValue) { synchronized (this) { awaitLoadedLocked(); String v = (String)mMap.get(key); return v != null ? v : defValue; } }
第一步:同步this。
第二步:调用awaitLoadedLocked()方法。
第三步,从mMap中获取key对应的value值,如果为空,则返回defValue,不为空。则返回从mMap中得到的值。
总结:所有的get都加了同步锁,
四、优化建议
1.强烈建议不要在sp里面存储特别大的key/value,有助于减少卡顿。
2.请不要高频地使用apply,尽可能地批量提交,commit直接在主线程操作,也需要主要不要高频的使用。
3.不要使用MODE_MULTI_PROCESS。
4.高频写操作的key与高频读操作的key可以适当地拆分文件,用于减少同步锁竞争。
5.不要一上来就执行getSharedPreferences().edit(),应当分成量大步骤来做,中间可以执行其他代码。
6.不要连续多次edit(),应该一次获取edit(),然后多次执行putXXX(),减少内存波动。
7.每次commit时会把所有的数据更新到文件,所以整个文件是不应该过大的,影响整体性能。
参考文章:http://gityuan.com/2017/06/18/SharedPreferences/