Android中的SharedPreference源码整理总结
SharedPreference是Android中的轻量级的存储方式,将键值对写入xml文件中,并保存在/data/data/package_name/shared_prefs路径下。
1、SharedPreferences.java
它是一个interface,如下:
public interface SharedPreferences { public interface OnSharedPreferenceChangeListener { void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key); } public interface Editor { /** * 写入键值对 */ Editor putString(String key, String value); Editor putStringSet(String key, Set values); Editor putInt(String key, int value); Editor putBoolean(String key, boolean value); /** * 清除键值为key的preference */ Editor remove(String key); /** * 清除preference所有内容 */ Editor clear(); /** *commit将其preference同步写入持久存储; *写入成功后返回true *如果不关心返回值,并在应用主线程中使用,请考虑使用apply()以替换commit() */ boolean commit(); /** *apply启动异步将更改提交到磁盘;没有返回值,如果失败,将不会收到任何通知。 */ void apply(); } /** * Retrieve all values from the preferences. */ Map getAll(); /** * 获取数据 */ String getString(String key, String defValue); Set getStringSet(String key, Set defValues); int getInt(String key, int defValue); boolean getBoolean(String key, boolean defValue); void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener); void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);}
2、SharedPreference的获取
通过 Context#getSharedPreferences 方式获取。
在Context.java中,getSharedPreferences()是一个抽象方法,
public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);
具体实现在ContextImpl.java中。
这里先说一下这个方法中的两个参数:
name:preference文件名;
mode:有MODE_PRIVATE,MODE_WORLD_READABLE,MODE_WORLD_WRITEABLE,MODE_MULTI_PROCESS。其中MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE在AndroidN以后版本不能使用,若使用会抛SecurityException异常;MODE_MULTI_PROCESS在某些Android版本上支持不好,未来可能会Deprecated,设置了此mode,每次getSharedPreferences时都会重新检查并加载磁盘文件。(如需精确的跨进程数据管理,应使用ContentProvider.)
ContextImpl.getSharedPreferences():
@Override public SharedPreferences getSharedPreferences(String name, int mode) { // 判空处理 "null.xml". 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) { //创建name.xml文件 file = getSharedPreferencesPath(name); mSharedPrefsPaths.put(name, file); } } return getSharedPreferences(file, mode); } @Override public SharedPreferences getSharedPreferences(File file, int mode) { SharedPreferencesImpl sp; synchronized (ContextImpl.class) { final ArrayMap cache = getSharedPreferencesCacheLocked(); sp = cache.get(file); if (sp == null) { //检查mode checkMode(mode); if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) { if (isCredentialProtectedStorage() && !getSystemService(UserManager.class) .isUserUnlockingOrUnlocked(UserHandle.myUserId())) { //AndroidO及以后版本,sp是ce加密,在用户第一次解锁之前无法使用 throw new IllegalStateException("SharedPreferences in credential encrypted " + "storage are not available until after user is unlocked"); } } //创建sp,保存到缓存中并返回 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) { //多进程模式需要重新读取文件 sp.startReloadIfChangedUnexpectedly(); } return sp; }
Sharedpreferences的实现类是SharedPreferencesImpl类,构造方法如下:
SharedPreferencesImpl(File file, int mode) { mFile = file; //磁盘上的xml文件 mBackupFile = makeBackupFile(file); //xml.bak备份文件,用于写入失败时的恢复 mMode = mode; mLoaded = false; mMap = null; //用于内存中缓存数据,getXxx从这里获取数据 mThrowable = null; startLoadFromDisk(); }
构造方法中涉及到startLoadFromDisk(),用于从磁盘读取文件:
private void startLoadFromDisk() { synchronized (mLock) { mLoaded = false; } //启动了一个名为"SharedPreferencesImpl-load"从磁盘加载数据 new Thread("SharedPreferencesImpl-load") { public void run() { loadFromDisk(); } }.start(); } private void loadFromDisk() { synchronized (mLock) { //若已经读取过,直接返回,不从磁盘读取 if (mLoaded) { return; } if (mBackupFile.exists()) { mFile.delete(); mBackupFile.renameTo(mFile); } } Map map = null; StructStat stat = null; Throwable thrown = null; try { stat = Os.stat(mFile.getPath()); if (mFile.canRead()) { BufferedInputStream str = null; try { str = new BufferedInputStream( new FileInputStream(mFile), 16 * 1024); //将xml文件转换为map map = (Map) XmlUtils.readMapXml(str); } catch (Exception e) { Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e); } finally { IoUtils.closeQuietly(str); } } } catch (ErrnoException e) { } catch (Throwable t) { thrown = t; } synchronized (mLock) { //设置mLoaded为true,表示已经读取过,下次getSharedPreference时不再从磁盘读取 mLoaded = true; mThrowable = thrown; try { if (thrown == null) { if (map != null) { //将map赋给全局变量mMap mMap = map; mStatTimestamp = stat.st_mtim; mStatSize = stat.st_size; } else { mMap = new HashMap<>(); } } } catch (Throwable t) { mThrowable = t; } finally { //已经加载完毕,释放mLock锁,通知其他等待线程。 mLock.notifyAll(); } } }
3、SharedPreference获取数据方法
以getString(String, String)为例,代码如下:
public String getString(String key, @Nullable String defValue) { synchronized (mLock) { //阻塞等待sp从磁盘读取xml到内存之后再get数据 awaitLoadedLocked(); String v = (String)mMap.get(key); //v为null,则返回默认值 return v != null ? v : defValue; } } private void awaitLoadedLocked() { ...; while (!mLoaded) { try { mLock.wait(); } catch (InterruptedException unused) { } } if (mThrowable != null) { throw new IllegalStateException(mThrowable); } }
awaitLoadedLocked
是等待xml文件加载完毕;如果首次执行getSharedPreferences后立即调用getXxx ,若加载还未完成(mLoaded为false
), getXxx 会卡在awaitLoadedLocked
,一旦加载完毕,则加载线程会通过notifyAll
通知所有在 awaitLoadedLocked
中等待的线程,getXxx 就能够返回了。sp最好不要加载过大的文件,否则阻塞时间过长。
4、SharedPreference写入及移除数据
4.1、写入数据需要Editor对象,先来看一下SharedPreferencesImpl.java中Editor对象的获取:
public Editor edit() { synchronized (mLock) { awaitLoadedLocked(); } //每次调用edit()都会创建一个EditorImpl对象 return new EditorImpl(); }
EditorImpl类中有两个成员变量mModified和mClear,其中mModified表示每次putXxx后的改变的配置项,mClear表示清空配置项,但是只清了SharedPreferenceImpl的mMap。
现在来看EditorImpl中的put,remove方法:
public Editor putString(String key, @Nullable String value) { synchronized (mEditorLock) { //将键值对存入mModified mModified.put(key, value); return this; } } public Editor remove(String key) { synchronized (mEditorLock) { //remove中存入value是this mModified.put(key, this); return this; } }
可见,修改值只会将其暂存到mModified
中,在Editor中所做的所有更改直到调用了commit()
或apply()
才会设置到mMap(内存)
和xml文件(磁盘)中去。
4.2、commit及apply实现
commit()方法:
public boolean commit() { //完成内存写入,并返回一个MemoryCommitResult对象 MemoryCommitResult mcr = commitToMemory(); //放入磁盘写入队列 SharedPreferencesImpl.this.enqueueDiskWrite( mcr, null /* sync write on this thread okay */); try { //等待其他线程执行完毕 writtenToDiskLatch.countdown减为0后从await方法返回 mcr.writtenToDiskLatch.await(); } catch (InterruptedException e) { return false; } finally { } //通知监听者 notifyListeners(mcr); //返回提交结果 return mcr.writeToDiskResult; }
内存写入:
private MemoryCommitResult commitToMemory() { long memoryStateGeneration; List keysModified = null; Set listeners = null; Map mapToWriteToDisk; synchronized (SharedPreferencesImpl.this.mLock) { if (mDiskWritesInFlight > 0) { //此时正在写入,无法修改 mMap = new HashMap(mMap); } mapToWriteToDisk = mMap; mDiskWritesInFlight++; //设置监听处理 boolean hasListeners = mListeners.size() > 0; if (hasListeners) { keysModified = new ArrayList(); listeners = new HashSet(mListeners.keySet()); } synchronized (mEditorLock) { //标记是否发生改变 boolean changesMade = false; //如果调用了clear方法,会先执行clear操作 if (mClear) { if (!mapToWriteToDisk.isEmpty()) { changesMade = true; mapToWriteToDisk.clear(); } mClear = false; } for (Map.Entry e : mModified.entrySet()) { String k = e.getKey(); Object v = e.getValue(); //remove方法里面存放的value是this,如果是this就去执行remove操作 if (v == this || v == null) { if (!mapToWriteToDisk.containsKey(k)) { continue; } mapToWriteToDisk.remove(k); } else { if (mapToWriteToDisk.containsKey(k)) { Object existingValue = mapToWriteToDisk.get(k); //未发生修改,则跳过 if (existingValue != null && existingValue.equals(v)) { continue; } } //发生了修改,则存入map中 mapToWriteToDisk.put(k, v); } //标记发生了修改 changesMade = true; if (hasListeners) { keysModified.add(k); } } //将修改的map:mModified置为null mModified.clear(); if (changesMade) { //提交到内存的次数 mCurrentMemoryStateGeneration++; } memoryStateGeneration = mCurrentMemoryStateGeneration; } } //将提交到内存次数,发生修改的集合,监听者及map封装成MemoryCommitResult并返回 return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners, mapToWriteToDisk); }
内存修改完成后,接着加入磁盘写入队列:
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) { //commit为true,apply为false final boolean isFromSyncCommit = (postWriteRunnable == null); final Runnable writeToDiskRunnable = new Runnable() { @Override public void run() { synchronized (mWritingToDiskLock) { //写入文件 writeToFile(mcr, isFromSyncCommit); } synchronized (mLock) { mDiskWritesInFlight--; } //apply操作时此runnable不为null,apply操作时该runnable执行 if (postWriteRunnable != null) { postWriteRunnable.run(); } } }; //commit操作时才执行 if (isFromSyncCommit) { boolean wasEmpty = false; synchronized (mLock) { wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { //writeToDiskRunnable执行,执行写入文件 writeToDiskRunnable.run(); return; } } //apply操作时才会执行 QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); }
由上述代码段看出commit()是同步的,通过enqueueDiskWrite()
调用 writeToFile()方法
将数据写到磁盘。
apply()方法:
apply方法利用QueuedWork实现异步操作.
public void apply() { final long startTime = System.currentTimeMillis(); final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { @Override public void run() { try { //线程等待 mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } } }; //将awaitCommit加入QueuedWork QueuedWork.addFinisher(awaitCommit); Runnable postWriteRunnable = new Runnable() { @Override public void run() { awaitCommit.run(); QueuedWork.removeFinisher(awaitCommit); } }; //放入写队列 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); //通知监听者 notifyListeners(mcr); }
通过阅读源码,整理总结如下:
1、sharedpreference是一个使用二级缓存(磁盘用xml文件,内存使用hashmap)的key-value存储方式
2、sp写入文件不要太大,否则会导致卡顿等
3、xml解析速度较慢,空间占用大,因此对于高频变化的存储场景不适用
4、不要多次调用edit(),每次调用都会创建一个editor对象
5、不要多次调用apply()或commit(),多次写入的话应该批量修改,最后一次执行调用
6、尽可能早地调用getSharedPreference,这样可以确保在调用getXxx或putXxx时数据已经完成写入内存
7、sharedpreference的跨进程模式只是在获取sharedpreference时重新从磁盘读取文件,使用此模式时每次都要通过getSharedPreferences获取sharedpreference变量,重新load数据,这样会引起其他线程等待耗时,降低内存读写性能。跨进程使用它是不可靠的。
更多相关文章
- android 科大讯飞语音唤醒demo
- Android(安卓)线程中更新UI
- Android应用程序全屏显示
- Android(安卓)ApiDemos示例解析(23):App->Intents
- android中退出整个app应用程序
- Android全屏(包含3种隐藏顶部状态栏及标题栏和一种隐藏Android(安
- android 2.2 apidemos 赏析笔记 6
- android之helloworld详解
- Android双击事件拦截方法