SharedPreferences的正确打开姿势

经历过几个大型项目的开发,在使用SharedPreferences(下面简称Sp)的时候踩了许多坑。下面将自己的一些经验总结一下。

不合理用法(个人认为)

  1. 存放大量的数据。(例如:存放接口数据,达到了MB级别)
  2. 当应用中有许多需要保存在Sp中的数据时,整个应用使用同一个Sp
  3. Sp的key使用时定义。
  4. Spcommit方法使用时机不合理。
  5. 同批次的key-value多次提交。

Sp读取数据时,会将整个xml放入内存中,当发生上面1和2的情况时就会影响读取速度,严重时造成卡顿,甚至是ANR,用户体验很差。

Sp的key定义也需要规范起来,不能使用的时候直接“xxx”这种情况,一旦复制粘贴keyName的时候多个空格或者少个字母你就准备怀疑人生吧,非常难发现(本人亲身经历过)。

Sp的提交分为commitapply两种。这两个方法的区别在于:

  • apply没有返回值而commit返回boolean表明修改是否提交成功
  • apply是将修改数据元素提交到内存,而后异步真正提交到硬件磁盘,而commit是同步提交到硬件磁盘,因此,在多个并发提交commit的时候,他们会等待正在处理的commit保存到磁盘后再操作,从而降低了效率。而apply只是先提交到内存,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。
  • apply方法不会提示任何失败的提示。由于在一个进程中,Sp是单实例,一般不会出现并发冲突,如果对提交的结果不关心的话,建议使用apply,当然需要确保提交成功且有后续操作的话,还是需要用commit的。

封装

针对上面提出的一些问题,我这里整理了一些使用规范以及简单的封装。封装的核心目的:为了方便维护,对每个Sp中保存的key-value能快速了解使用。

举个列子:假如项目中,需要将用户的一些信息(name,age,sex,phone,isMarried)保存在Sp中。

创建Spkey的描述类

建议新建一个包,专门存放Sp相关的内容。在新建的包下新建一个SpKeyUser类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class SpKeyUser {
/**
* 姓名
* valueType[String]
* 默认值:""
*/
public static final String NAME="name";
/**
* 姓名
* valueType[int]
* 默认值:0
*/
public static final String AGE="age";
/**
* 姓名
* valueType[String]
* 默认值:"man"
*/
public static final String SEX="sex";
/**
* 姓名
* valueType[String]
* 默认值:""
*/
public static final String PHONE="phone";
/**
* 姓名
* valueType[Boolean]
* 默认值:false
*/
public static final String IS_MARRIED="is_married";
}

这里之所用一个类来描述Sp的key,是为了更清晰地展现这个其保存的所有key,看了上面的注释我相信好处就不用我再一一赘述了。

创建Sp的帮助类

这个类主要作为各个Sp的生产工厂使用,并进一步提供简化的提交,获取值的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
public class SharedPreferencesHelper {
private static final String SP_NAME_USER = "sp_name_user";//用户相关的SP
/**
* 用户相关Sp
* 相关的key见{@link SpKeyUser}
*/
public static SharedPreferences getUserSp() {
return MyApplication.getInstances().getSharedPreferences(SP_NAME_USER, Context.MODE_PRIVATE);
}
/**
* 用户相关Sp
* 相关的key见{@link SpKeyDefault}
*/
public static SharedPreferences getDefaultSp() {
return PreferenceManager.getDefaultSharedPreferences(MyApplication.getInstances());
}
//sharedPreferences 是否为空
public static boolean isEmpty(SharedPreferences sp) {
return sp == null || sp.getAll() == null || 0 == sp.getAll().size();
}
/**
* 默认值为""
*/
public static String getString(SharedPreferences sp, String key) {
return sp.getString(key, Key.NIL);
}
/**
* 具有默认值
*
* @param defValue 默认值
*/
public static String getString(SharedPreferences sp, String key, String defValue) {
return sp.getString(key, defValue);
}
/**
* 默认值为0L
*/
public static long getLong(SharedPreferences sp, String key) {
return sp.getLong(key, 0L);
}
/**
* 默认值为0
*/
public static int getInt(SharedPreferences sp, String key) {
return sp.getInt(key, 0);
}
public static void setPreference(SharedPreferences sp, String key, int value) {
sp.edit().putInt(key, value).apply();
}
public static void setPreference(SharedPreferences sp, String key, String value) {
sp.edit().putString(key, value).apply();
}
public static void setPreference(SharedPreferences sp, String key, boolean value) {
sp.edit().putBoolean(key, value).apply();
}
//批量put数据
public static void setPreferenceWithList(SharedPreferences sp, List<SpItem> spItemList) {
if (sp == null || spItemList == null || spItemList.isEmpty()) {
return;
}
Editor spEditor = sp.edit();
for (SpItem item : spItemList) {
setSpItem(spEditor, item);
}
spEditor.apply();
}
public static void setPreference(SharedPreferences sp, String key, long value) {
sp.edit().putLong(key, value).apply();
}
/**
* 默认值为false
*/
public static boolean getBoolean(SharedPreferences sp, String key) {
return sp.getBoolean(key, false);
}
/**
* 自定义默认值
*/
public static boolean getBoolean(SharedPreferences sp, String key, boolean defValue) {
return sp.getBoolean(key, defValue);
}
/**
* 清除某一个key
*/
public static void remove(SharedPreferences sp, String key) {
if (sp == null || TextUtils.isEmpty(key)) {
return;
}
sp.edit().remove(key).apply();
}
//值类型(String:0,int:1,long:2,float:3,boolean:4)
private static final int VALUE_TYPE_STRING = 0;
private static final int VALUE_TYPE_INT = 1;
private static final int VALUE_TYPE_LONG = 2;
private static final int VALUE_TYPE_FLOAT = 3;
private static final int VALUE_TYPE_BOOLEAN = 4;
//Sp提交多个时使用,用于描述每个提交项
public static class SpItem<T> {
private String mKey;
private T mValue;
private int mValueType;
public SpItem(String key, T value) {
this.mKey = key;
this.mValue = value;
this.mValueType = initValueType(value);
}
private int initValueType(T t) {
int typ = -1;
if (t instanceof String) {
typ = VALUE_TYPE_STRING;
} else if (t instanceof Integer) {
typ = VALUE_TYPE_INT;
} else if (t instanceof Long) {
typ = VALUE_TYPE_LONG;
} else if (t instanceof Float) {
typ = VALUE_TYPE_FLOAT;
} else if (t instanceof Boolean) {
typ = VALUE_TYPE_BOOLEAN;
}
return typ;
}
}
//按类型put数据
private static void setSpItem(Editor spEditor, SpItem spItem) {
if (spItem == null || spEditor == null) {
return;
}
switch (spItem.mValueType) {
case VALUE_TYPE_STRING:
spEditor.putString(spItem.mKey, (String) spItem.mValue);
break;
case VALUE_TYPE_INT:
spEditor.putInt(spItem.mKey, (Integer) spItem.mValue);
break;
case VALUE_TYPE_LONG:
spEditor.putLong(spItem.mKey, (Long) spItem.mValue);
break;
case VALUE_TYPE_FLOAT:
spEditor.putFloat(spItem.mKey, (Float) spItem.mValue);
break;
case VALUE_TYPE_BOOLEAN:
spEditor.putBoolean(spItem.mKey, (Boolean) spItem.mValue);
break;
}
}
}

大家可以根据业务需求对这个帮助类进行改造。

展望

优化永无止境!后面计划将Sp的提交等操作通过注解方式来实现。

当然如果大家有相关优化的建议,欢迎留言反馈。