SharedPreferences 的介绍和特点:
Android 提供的一个类似 Redis 的键值对的存储方式、叫 SharedPreferences 。不同于 Map 容器(内存存储)、 这个存储方式为文件存储,最终是存在 Android 文件系统的 xml 文件中。
SharedPreferences 的特点:
1 只支持基本数据类型 ,例如字符串、整形、布尔等
2 不支持夸进程
3 不适合存储大量数据、例如比较大的实体 json 。大量数据建议做 sql 数据库存储
4 查询效率,虽然 SharedPreferences 在调研后知道了它已经内置了内存缓存。但是大量数据的查询效率上还是不如数据库。毕竟数据库有一些调优的手段或者索引等来增加查询速度
SharedPreferences 的封装 SPUtils 在应用中的使用和缺点:
SharedPreferences 虽然简单,但是在 Android 应用中有大量的使用场景,例如一些简单的状态,需要在下次杀死进程后再次启动 App 保留。
目前我调研我们应用中所使用的 SPUtils 封装查询到他在 C 端使用的场景多大 1300 处,可谓很频繁。
根据既有的 SPUtils 的代码做分析,发现他的几个不良的地方:
1 键值对的 key 管理混乱,业务 key 不应该和工具类混合在一起。这个这个工具类拿到其他地方或者其他应用就不可以使用。
2 每次 put 和 get 的时候都强制传 Context ,这样是不合理对使用也是很不方便的。在每个使用的地方我还得想办法把 Context 传过来,另外这样每次都相当于去打开和关闭一个文件句柄对效率也是非常存在影响,正确的做饭是在 application 初始化的时候全局配置一次,后面 put 和 get 应该只需要传关键参数 key value
3 不支持序列化
4 不支持内存缓存(之前认为SharedPreferences 没有内存缓存的时候 )
最终根据以上的痛点,我对 SharedPreferences 进行了重构
先来读一下它的 README.md
我所重构的 SharedPreferencesManager 的特点:
SharedPreferencesManager
一款支持内存映射的 SharedPreferences 工具库
基于 SharedPreferences 键值对的特性,在 SharedPreferences xml 文件缓存(磁盘缓存)前置了一层并发安全内存容器模型 ConcurrentHashMap
优先从内存缓存中取数据,大幅提高了读取数据的效率
支持对象的序列化和反序列化
和原生的 SharedPreferences 一样不支持夸进程
使用
Init
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
SharedPreferencesManager.getInstance().init(this);
}
}
Put
SharedPreferencesManager.getInstance().put("keyString", "value");
Get
String s = (String) SharedPreferencesManager.getInstance().get("keyString", "x");
API
序列化用法
ShaerdPreferencesManager 提供了基于 Json 对非基本数据类型对象的序列化和反序列化的能力,思路为:
要保持类库的无依赖性,低耦合性。ShaerdPreferencesManager 只提供解析接口,对对象的序列化和反序列化不做具体实现,这一部分交由调用者去实现。
每个使用者所采用的 Json 解析方式不一样 (Gson、FastJson、Jackson等) ShaerdPreferencesManager 也不可能做全面覆盖。
public interface JsonParserStrategy {
String encode(Object o);
Object decode(String jsonString, Class<?> c);
}
建议用法:
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
SharedPreferencesManager.getInstance().init(this);
SharedPreferencesManager.getInstance().setJsonParserStrategy(new JsonParserStrategy() {
@Override
public String encode(Object o) {
String jsonString;
//采用 gson 的实现解析方式
Gson gson = new Gson();
jsonString = gson.toJson(o);
//采用 fastjson 的实现解析方式
//jsonString = JSON.toJSONString(o);
return jsonString;
}
@Override
public Object decode(String jsonString, Class<?> c) {
if (jsonString == null) {
return null;
}
Object o;
//采用 gson 的实现解析方式
Gson gson = new Gson();
o = gson.fromJson(jsonString, c);
//采用 fastjson 的实现解析方式
// o = JSON.parseObject(jsonString, c);
return o;
}
});
}
}
Sample:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
User user = new User();
user.name = "张三";
user.age = 30;
user.address = "北京市东城区银河SOHO";
user.sex = "男";
user.phone = "110";
user.isVip = true;
SharedPreferencesManager.getInstance().put("currentUser", user);
User cacheUser = (User) SharedPreferencesManager.getInstance().get("currentUser", new User());
Log.e(SharedPreferencesManager.class.getSimpleName(), cacheUser.toString());
}
class User {
String name;
int age;
String address;
String sex;
String phone;
boolean isVip;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
", sex='" + sex + '\'' +
", phone='" + phone + '\'' +
", isVip=" + isVip +
'}';
}
}
}
TODO
增加内存缓存淘汰策略,淘汰内存缓存中使用低频的数据,防止内存占用过大。这部分数据如果再次需要被使用从磁盘缓存中取
总结:
我所重构的 SharedPreferencesManager 是解决了上述的 4 项痛点
另外在序列化的设计上增加了自认为巧妙的将实际解析接口提供给调用者。由调用者来决定使用什么解析库,或者手动去解析,这样保持工具库的纯净和无依赖性
但是最终这个库还有一个小缺点
就是 get 取数据的时候还需要做强制转化,虽然使用没有什么影响但是还是没那么优雅
最终还因为此工具库是基于 SharedPreferences 的封装,SharedPreferences 原本就不支持跨进程,所以这个库也不支持夸进程。
基于以上的点,最终这个库可能不会被采纳使用到实际项目中(因为微信有使用好几年基于 c/c++ 的成熟解决方法 MMKV),但是我不会因此而遗憾,写完这个库也能感觉自己的代码设计能力又进步了一点点
https://github.com/13120241790/SharedPreferencesManager
https://github.com/Tencent/MMKV