为什么要封装?
系统提供SharedPreferences很不好用,参数多余,也不友好,用起来很繁琐,基本上都需要封装一下才能用。
创建方法1
// 创建函数式context的一个实例方法
public abstract SharedPreferences getSharedPreferences(String var1, int var2);
// 使用的一个例子
SharedPreferences sp=context.getSharedPreferences("名称", Context.MODE_PRIVATE);
context
,在安卓中很常见。不过从含义上来说,这和工具,垃圾箱等等一样,是不明确的,不不是好用的一个参数。从大多数使用场景来看,context
可以代表一个Activity
,也可以代表一个Application
。但不论怎么说,这是一个实例,有个体的含义在。getSharedPreferences
,从方法的名字看应该是静态类型的,但是却出现先在一个对象的成员方法中,怎么看都感觉别扭。"名称"参数,代表的是一个文件名,这说明了
SharedPreferences
本质上是文件操作,这个函数的真正作用,其实是打开或者新建一个文件。Context.MODE_PRIVATE
,这个参数看起来很乞丐,不过了解了SharedPreferences
的本质是读写xml
文件之后,也可以理解,这个参数是为了再多进程之间通过xml
配置文件进行信息共享。不得不说,当初的设计者的脑洞真大,野心也真大。这个方法作为
xml
文件的底层读写方法是合适的,但是作为key-value形式的缓存信息的读写,可以说是很不好用。
创建方法2
// 方法定义
public static SharedPreferences getDefaultSharedPreferences(Context context) {
throw new RuntimeException("Stub!");
}
// 使用例子
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
这个方法就简单多了,省去了文件名,多文件共享等参数,看起来更像缓存读写类函数。
静态方法,并且代表的是整个
Application
的缓存。context
参数是一个大败笔,就是“坏了那锅粥的老鼠屎”。前面已经分析过context
名字含有不明确,既可以是一个Activity
,也可以是一个Application
。既然这个静态方法是代表整个Application
,放这么一个不伦不类的参数在这里干什么呢?只能说明设计者的脑子真二。
创建方法3
// 方法定义
public SharedPreferences getPreferences(int mode) {
throw new RuntimeException("Stub!");
}
// 使用实例
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
这是某个
Activity
特有的缓存读写,所以是Activity
对象的成员方法,有一定的使用场景。Context.MODE_PRIVATE
,又是一个脑子被门缝夹的参数,真不知道设计者脑子能蠢成什么样子。既然是某个Activity
特有的,加这么个用来共享的参数真是不伦不类。
写入操作
//可以创建一个新的SharedPreference来对储存的文件进行操作
SharedPreferences sp=context.getSharedPreferences("名称", Context.MODE_PRIVATE);
//像SharedPreference中写入数据需要使用Editor
SharedPreference.Editor editor = sp.edit();
//类似键值对
editor.putString("name", "string");
editor.putInt("age", 0);
editor.putBoolean("read", true);
//editor.apply();
editor.commit();
既然只是简单的put,知觉中只要一条语句就可以了,为什么要这么多?
edit(),commit()
这些都是什么鬼?跟put
有关系吗?这些事写文件的常见操作,但不是写缓存应该有的操作。
apply(),commit()
这两个又是什么作用,有什么区别?方法的意义在于让使用者用起来简单舒服,而不是带来困扰。
小结:使用者的期望是简单信息的缓存读写,不过看到的是不伦不类的文件读写。广大Android开发者的眼睛是雪亮的,几乎所有人都觉得要二次封装一下,不然用起来就觉得恶心。能设计出人人喊打的API,设计者的脑子该有多二啊
Android SharedPreference的使用
SharedPreference使用
iOS的缓存读写
作为对比,可以参考一下iOS的缓存读写,就知道简单信息的缓存API应该是什么样子的。
//将NSString 对象存储到 NSUserDefaults 中
NSString *passWord = @"1234567";
NSUserDefaults *user = [NSUserDefaults standardUserDefaults];
[user setObject:passWord forKey:@"userPassWord"];
NSUserDefaults 简介,使用 NSUserDefaults 存储自定义对象
第1层封装
封装对象
// 创建函数式context的一个实例方法
public abstract SharedPreferences getSharedPreferences(String var1, int var2);
// 使用的一个例子
SharedPreferences sp=context.getSharedPreferences("名称", Context.MODE_PRIVATE);
Context.MODE_PRIVATE
这个参数多余,应该隐藏掉。"名称",文件名这个参数反而是需要的。由于本质是
xml
文件的学些。一方面出于性能考虑,多分几个文件,可以减小文件大小。另外一方面,从逻辑上,配置信息也是分块的,多几个文件也是现实需求。
Android之不要滥用SharedPreferencescontext
这个参数是Android
的习惯,不想要,但是作为通用工具的话又隐藏不了。要等到具体的APP
之后,可以用Application
的context
来统一替代,一般在二次封装的时候再隐藏。
实例还是静态方法
- 通过静态方法封装,让很多步骤的
put
方法简单好用。这是很普遍的做法,也是很好的一种做法,比如下面这样的例子
/**
* 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法
*
* @param context 上下文
* @param key 关键字
* @param object 数据
* @param fileName 文件名
*/
public static void put(Context context, String key, Object object, String fileName)
作为通用工具,这种封装方法是很灵活的,也很独立,没有依赖。
这篇文章就是一篇很好的封装参考,强烈推荐:
SharedPreferences封装类SPUtilsContext context; String fileName
这两个参数是拖油瓶,在具体的put
操作中用处不大,感觉有点多余。所以,这里考虑进行封装成实例对象的尝试,目的是将
Context context; String fileName
这两个拖油瓶参数在对象初始化的时候统一确定,减少put
等缓存实际操作方法的参数。静态方法封装,名字可以叫
SPUtil
; 而现在的实例化封装,那么名字就叫SPCache
构造函数
统一确定Context context; String fileName
这两个参数是拖油瓶,并且作为内部静态变量,对外隐藏。
public final class SPCache {
// 隐藏两个参数
private Context context;
private SharedPreferences sp;
// 禁止无参默认构造函数
private SPCache() {}
// 统一确定两个参数,一个fileName,对应于内部一个sp
public SPCache(@NonNull Context context, @NonNull String fileName) {
this.context = context.getApplicationContext();
this.sp = this.context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
}
}
put操作封装
关于文件操作的内容,比如
edit,commit
之类的,需要隐藏,对外应该是key-value
风格的。putXXX
有一类方法,有点杂,可以统一为Object
的put
操作。
/**
* 存入缓存,类型可以是int,long,float,Boolean,String;自定义对象会通过toString()转换为字符串
* @param key 键
* @param object 值
*/
public void put(@NonNull String key, Object object) {
SharedPreferences.Editor editor = sp.edit();
// instanceof 用来 指出对象是否是特定类的一个实例
if (object instanceof String) {
editor.putString(key, (String) object);
}
else if (object instanceof Integer) {
editor.putInt(key, (Integer) object);
}
else if (object instanceof Boolean) {
editor.putBoolean(key, (Boolean) object);
}
else if (object instanceof Float) {
editor.putFloat(key, (Float) object);
} else if (object instanceof Long) {
editor.putLong(key, (Long) object);
} else {
editor.putString(key, object.toString());
}
editor.commit();
}
get操作封装
和put
类似,将一堆getXXX
操作统一为get Object
/**
* 根据默认值类型,获取相应的数据。类型可以是int,long,float,Boolean,String;
* @param key 键
* @param defaultObject 默认值
* @return 数据
*/
public Object get(@NonNull String key, @NonNull Object defaultObject) {
if (defaultObject instanceof String) {
return sp.getString(key, (String) defaultObject);
}
else if (defaultObject instanceof Integer) {
return sp.getInt(key, (Integer) defaultObject);
}
else if (defaultObject instanceof Boolean) {
return sp.getBoolean(key, (Boolean) defaultObject);
}
else if (defaultObject instanceof Float) {
return sp.getFloat(key, (Float) defaultObject);
}
else if (defaultObject instanceof Long) {
return sp.getLong(key, (Long) defaultObject);
}
return null;
}
删除和清空
/**
* 移除键对应的数据
* @param key 键
*/
public void remove(@NonNull String key) {
SharedPreferences.Editor editor = sp.edit();
editor.remove(key);
editor.commit();
}
/**
* 清空缓存
*/
public void clear() {
SharedPreferences.Editor editor = sp.edit();
editor.clear();
editor.commit();
}
讨论:SharedPreferencesCompat
SharedPreferences
适合读写少量的配置信息,正常使用,没什么问题。但是如果滥用的话,比如,信息量大的时候,毕竟是xml文件读写,所以性能比较低。
Android之不要滥用SharedPreferences为了提升性能,引入了
editor.apply();
,将文件读写操作移到工作线程中。但是,这样又引入了偶尔会崩溃的新问题。为了解决崩溃问题,引入了SharedPreferencesCompat,核心代码就是默认用
editor.apply();
,提高性能;如果崩溃了,就用editor.commit();
public void apply(@NonNull SharedPreferences.Editor editor) {
try {
editor.apply();
} catch (AbstractMethodError unused) {
// The app injected its own pre-Gingerbread
// SharedPreferences.Editor implementation without
// an apply method.
editor.commit();
}
}
- 不知道什么原因,
Android
又把SharedPreferencesCompat
的废弃了。
小结:既然被废弃,就不用了,直接使用editor.commit();保证安全。如果担心性能问题,应该考虑信息拆分,正确使用,符合SharedPreferences少量配置信息读写的定位
第2层封装: 静态化
到了具体的
APP,context
可以用Application
对应的那个全局化的。根据逻辑模块划分,相应的
fileName
也就可以指定了。context,fileName
两个参数确定之后,就能得到一个sp
对象。这个sp
对象可以作为这层封装类的静态对象,进行实际的读写操作。fileName
,XXXKey
等可以定义为私有的静态字符串在这个包装类中进行隐藏。对外可以提供一些公有的静态方法,聚焦在信息的读写。静态方法的使用是最方便的,比单例访问还少了
getInstance()
这一步骤。以用户信息读写为例,这里做个简单封装,可以是这样的:
public final class SPUserInfo {
// sp 对象
private final static SPCache spCache = new SPCache(MainApplication.getContext(), "SPUserInfo");
// 各种key常数定义
private final static String userNameKey = "userNameKey";
private final static String passwordKey = "passwordKey";
// 读写接口
public static void put(UserInfo userInfo) {
spCache.put(userNameKey, userInfo.getUserName());
spCache.put(passwordKey, userInfo.getPassword());
}
public static UserInfo get() {
String userName = (String) spCache.get(userNameKey, "默认的名字");
String password = (String) spCache.get(passwordKey, "默认的密码");
return new UserInfo(userName, password);
}
}
Demo
-
界面:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<EditText
android:layout_marginTop="20dp"
android:id="@+id/edit_user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="用户名"
android:singleLine="true"
/>
<EditText
android:layout_marginTop="20dp"
android:id="@+id/edit_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="密码"
android:inputType="textPassword"
android:singleLine="true"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="20dp">
<Button
android:id="@+id/input"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="存储"
android:onClick="writeInfo"/>
<Button
android:id="@+id/output"
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="读取"
android:onClick="readInfo"/>
</LinearLayout>
<TextView
android:id="@+id/showtext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginLeft="15dp"
android:text="存储的信息"
android:textSize="25dp"/>
<TextView
android:id="@+id/text_user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginLeft="15dp"
android:text="用户名"
android:textSize="20dp"/>
<TextView
android:id="@+id/text_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginLeft="15dp"
android:text="密码"
android:textSize="20dp"/>
</LinearLayout>
- Activity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void writeInfo(View view) {
EditText userNameEdit = (EditText)findViewById(R.id.edit_user_name);
EditText passwordEdit = (EditText)findViewById(R.id.edit_password);
String userName = userNameEdit.getText().toString();
String password = passwordEdit.getText().toString();
UserInfo userInfo = new UserInfo(userName, password);
SPUserInfo.put(userInfo);
}
public void readInfo(View view) {
TextView userNameText = (TextView)findViewById(R.id.text_user_name);
TextView passwordText = (TextView)findViewById(R.id.text_password);
UserInfo userInfo = SPUserInfo.get();
userNameText.setText(userInfo.getUserName());
passwordText.setText(userInfo.getPassword());
}
}