0. 前言
SharedPreferences可以说是Android中最常用的一种存数据到文件的方式。他的数据是以键值对的方式存储在 ~/data/data/包名/shared_prefs
这个文件夹中的。
这个存储框架是非常轻量级的,如果我们需要存一些小数据或者是一个小型的可序列化的Bean实体类的,使用SharedPreferences是最明智的选择。
1. 使用方法
1.1 获取SharedPreferences
在使用SharedPreferences前,我们得先获取到它。
由于SharedPreferences是Android内置的一个框架,所以我们想要获取到它非常的简单,不需要导入任何依赖,直接写代码就行。下面我们就来介绍下获取对象的三个方式:
1.1.1 Context # getSharedPreferences()
首先就是可以说是最常用的方法,通过Context的getSharedPreferences()
方法去获取到SharedPreferences对象。由于是通过Context获取的,所以基本上Android的所有场景我们都可以通过这个方法获取到。
public abstract SharedPreferences getSharedPreferences (String name,
int mode)
这个方法接收两个参数,分别是name
和mode
:
name
:name就是我们要存储的SharedPreferences本地文件的名字,这个可以自定义。但是如果使用同样的name的话,永远只能获取到同一个SharedPreferences的对象。mode
:mode就是我们要获取的这个SharedPreferences的访问模式,Android给我们提供了挺多的模式的,但是由于其余的模式或多或少存在着安全隐患(因为其他应用也可以直接获取到),所以就全部都弃用了,现在就只有一个MODE_PRIVATE
模式。
此外,这个方法是线程安全的。
Mode
的可选参数:
MODE_PRIVATE
:私有模式,该SharedPreferences只会被调用他的APP去使用,其他的APP无法获取到这个SharedPreferences。:API17被弃用。使用这个模式,所有的APP都可以对这个SharedPreferences进行读操作。所以这个模式被Android官方严厉警告禁止使用(It is strongly discouraged),并推荐使用MODE_WORLD_READABLE
ContentProvider
、BroadcastReceiver
和Service
。:API17被弃用。和上面类似,这个是可以被所有APP进行写操作。同样也是被严厉警告禁止使用。MODE_WORLD_WRITEABLE
:API23被弃用。使用了这个模式,允许多个进程对同一个SharedPreferences进行操作,但是后来也被启用了,原因是因为在某些Android版本下,这个模式不能可靠的运行,官方建议如果多进程建议使用MODE_MULTI_PROCESS
ContentProvider
去操作。在后面我们会说为啥多进程下不可靠。
1.1.2 Activity # getPreferences()
这个方法只能在Activity中或者通过Activity对象去使用。
public SharedPreferences getPreferences (int mode)
这个方法需要传入一个mode
参数,这个参数和上面的context#getSharedPreferences()
的mode
参数是一样的。其实这个方法和上面Context的那个方法是一样的,他两都是调用的SharedPreferences getSharedPreferences(String name, int mode)
。只不过Context的需要你去指定文件名,而这个方法你不需要手动去指定,而是会自动将当前Activity的类名作为了文件名。
1.1.3 PreferencesManager # getDefaultSharedPreferences()
这个一般用在Android的设置页面上,或者说,我们也只有在构建设置页面的时候才会去使用这个。
public static SharedPreferences getDefaultSharedPreferences (Context context)
他承接一个context参数,并自动将当前应用的报名作为前缀来命名文件。
1.2 存数据
如果需要往SharedPreferences中存储数据的话,我们并不能直接对SharedPreferences对象进行操作,因为SharedPreferences没有提供存储或者修改数据的接口。
如果想要对SharedPreferences存储的数据进行修改,需要通过SharedPreferences.edit()
方法去获取到SharedPreferences.Editor对象来进行操作。
获取到Editor对象后,我们就可以调用他的putXXX()
方法进行存储了,存储之后一定记得通过apply()
和commit()
方法去将数据提交。
至于commit
和apply
的区别我们后面会说。
//步骤1:创建一个SharedPreferences对象
SharedPreferences sharedPreferences= getSharedPreferences("data",Context.MODE_PRIVATE);
//步骤2: 实例化SharedPreferences.Editor对象
SharedPreferences.Editor editor = sharedPreferences.edit();
//步骤3:将获取过来的值放入文件
editor.putString("name", “Tom”);
editor.putInt("age", 28);
editor.putBoolean("marrid",false);
//步骤4:提交
editor.commit();
// 删除指定数据
editor.remove("name");
editor.commit();
// 清空数据
editor.clear();
editor.commit();
1.3 取数据
取值就很简单了,构建出SharedPreferences的对象后,就直接调用SharedPreferences的getXXX()
方法就行。
SharedPreferences sharedPreferences = getSharedPreferences("data", Context .MODE_PRIVATE);
String userId = sharedPreferences.getString("name", "");
2. 源码分析
2.1 获取SharedPreferences实例
我们上面说到,获取SharedPreferences实例最常用的方法就是Context#getSharedPreferences()
。那我们就从这个方法入手,看到底是怎么获取到SharedPreferences实例的。
我们先看下这个方法的实现:
public class ContextWrapper extends Context {
@UnsupportedAppUsage
Context mBase;
// ...
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return mBase.getSharedPreferences(name, mode);
}
}
可以看到他又调用了Context的getSharedPreferences()
方法:
public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);
然后我们就会惊喜的发现,这是一个抽象方法。我开始还想去找一个ContextWrapper
的构造的地方,看看mBase
传入的是啥,后来找了一圈没找到,直接上网搜索,立马得到答案:ContextImpl
,这个可以说是Context
在Android中的唯一实现类,所有的操作又得经过这个类。那么我们就来看下这个类中的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.
// ps:这个nice很精髓😂
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
// 加了一个类锁,保证同步
synchronized (ContextImpl.class) {
// mSharedPrefsPaths是一个保存了name和file对应关系的ArrayMap
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
// 根据name从里面找有没有缓存的file
file = mSharedPrefsPaths.get(name);
// 如果没有,那就调用getSharedPreferencesPath去找
if (file == null) {
// ->>> 重点1. getSharedPreferencesPath(name)
file = getSharedPreferencesPath(name);
// 并保存到mSharedPrefsPaths
mSharedPrefsPaths.put(name, file);
}
}
// 获取到file后,再调用getSharedPreferences
return getSharedPreferences(file, mode);
}
/**
* 重点1. ContextImpl # getSharedPreferencesPath(String name)
* 根据PreferencesDir和name.xml去创建了这个文件
*/
@Override
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
那我们在看下getSharedPreferences(File file, int mode)
的实现:
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
// SharedPreferences唯一实现类SharedPreferencesImpl的实例
SharedPreferencesImpl sp;
// 同样的加类锁
synchronized (ContextImpl.class) {
// 构造了一个File-SharedPreferencesImpl对应关系的ArrayMap
// 调用getSharedPreferencesCacheLocked方法区获取cahce
// ->>> 重点1
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
// 从file-SharedPreferencesImpl键值对中根据当前file去过去SharedPreferencesImpl实例
sp = cache.get(file);
// 如果没有,那就需要新建一个
if (sp == null) {
// 检查mode,如果是MODE_WORLD_WRITEABLE或者MODE_MULTI_PROCESS则直接抛异常
checkMode(mode);
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");
}
}
// 调用构造方法去构造SharedPreferencesImpl对象
sp = new SharedPreferencesImpl(file, mode);
// 将对象和file的键值对存入cache中
cache.put(file, sp);
return sp;
<