链接:https://www.jianshu.com/p/5fcef7f68341
前言
本文不是与大家一起探讨关于SharedPreferences的基本使用,而是结合源码的角度分析对 SharedPreferences 使用不当可能引发的“严重后果”以及该如何正确的使用 SharedPreferences。
SharedPreferences 是 Android 平台为应用开发者提供的一个轻量级的存储辅助类,用来保存应用的一些常用配置,它提供了 putString()、putString(Set)、putInt()、putLong()、putFloat()、putBoolean() 六种数据类型。数据最终是以 XML 形式进行存储。在应用中通常做一些简单数据的持久化存储。SharedPreferences 作为一个轻量级存储,所以就限制了它的使用场景,如果对它使用不当可能会引发“严重后果”。那么让我们从源码角度出发(基于 API Level 28)吧。
1、 SharedPreferences 文件保存位置
SharedPreferences config = context.getSharedPreferences("config", Context.MODE_PRIVATE);String value = config.getString("key", "default");
通过 Context 的 getSharedPreferences() 方法得到 SharedPreferences 对象,这里实际调用的是 ContextImpl.getSharedPreferences() 方法。
@Overridepublic SharedPreferences getSharedPreferences(String name, int mode){//mBase实际类型是 ContextImplreturn mBase.getSharedPrefenences(name, mode);}
mBase 的实际类型是 ContextImpl(不熟悉的朋友,可以去看下 Activity 的创建过程,在 ActivityThread 中)。
ContextImpl 中 getSharedPreferences(String name, int mode) 调用过程如下:
@Overridepublic SharedPreferences getSharedPreferences(String name, int mode) {if (mPackageInfo.getApplicationInfo().targetSdkVersion
File file;synchronized (ContextImpl.class) {if (mSharedPrefsPaths == null) {//mSharedPrefsPaths维护文件名name和文件File 的映射关系//这个在较早版本中不存在mSharedPrefsPaths = new ArrayMap<>();}//通过文件名name获取对应的文件Filefile = mSharedPrefsPaths.get(name);if (file == null) {//SharedPreferences文件目录创建过程file = getSharedPreferencesPath(name);mSharedPrefsPaths.put(name, file);}}//根据File创建SharedPreferencesreturn getSharedPreferences(file, mode);}
代码中标注了详细的注释,这里主要维护了 SharedPreferences 文件名 name 和文件 File 的映射关系,既根据文件名 name 得到文件 File,每个 Activity 都会包含一个 ContextImpl 对象,mSharedPrefsPaths 是它的成员变量,既仅在当前对象有效。这里重点跟踪下 SharedPreferences 文件的保存目录,SharedPreferences 文件路径创建过程:
/*** 根据文件名创建File对象*/@Overridepublic File getSharedPreferencesPath(String name){return makeFilename(getPreferencesDir(), name+".xml");}
getPreferencesDir 方法如下:
@Overrideprivate File getPreferencesDir(){synchronized(mSync){if(mPreferencesDir == null){//创建SharedPreferences文件保存目录//getDataDir返回:/data/data/packageName/mPreferencesDir = new File(getDataDir(), "shared_prefs");}//确保应用私有文件目录已经存在return ensurePrivateDirExists(mPreferencesDir);}}
从这里可以看出 SharedPreferences 文件的存储位置是在应用程序包名下 shared_prefs 目录内。
这里需要注意的是文件名 name 不能是路径形式如:“/config”,如下将会抛出异常:
@overrideprivate File makeFilename(File base, String name){if(name.indexOf(File.separatorChar) < 0){//SharedPreferences文件名中如果包含“/”字符将会抛出异常return new File(base, name);}throw new IllegalArgumentException("File " + name + " contains a path separator" );}
跟踪到这里,SharedPreferences 的文件保存路径我们就算是找到了。这一步中主要通过文件名 name 创建对应文件 File 对象。并且会将其缓存在 ContextImpl 的 Map(mSharedPerfsPaths)容器中。接着我们看 SharedPreferences 的创建过程。
2、SharedPreferences 创建过程
@Overridepublic SharedPreferences getSharedPreferences(File file, int mode) {SharedPreferencesImpl sp;synchronized (ContextImpl.class) {//得到用于缓存SharedPreferences的Map容器//该Map容器在ContextImpl单例方式声明final ArrayMap cache = getSharedPreferencesCacheLocked();sp = cache.get(file);if (sp == null) {//Android N之后不在支持MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLEcheckMode(mode);//Android 7.0之后的文件级加密相关if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {if (isCredentialProtectedStorage()&& !getSystemService(UserManager.class).isUserUnlockingOrUnlocked(UserHandle.myUserId())) {throw new IllegalStateException("SharedPreferences in credential encrypted "+ "storage are not available until after user is unlocked");}}//SharedPreferences首次创建,实际类型是SharedPreferencesImpl//SharedPreferences只是一个接口,定义了操作的基本API。//真正实现是在SharedPreferencesImpl中sp = new SharedPreferencesImpl(file, mode);//保存在Map容器中,该Map容器为单例cache.put(file, sp);return sp;}}if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {//MODE_MULTI_PROCESS的加载策略sp.startReloadIfChangedUnexpectedly();}return sp;}
在该方法中首先看下 getSharedPreferencesCacheLocked 方法如下: