本文介绍Android的键值对存储方式的使用方法,包括:如何将数据保存到共享参数,如何从共享参数读取数据,如何使用共享参数实现登录页面的记住密码功能,如何使用Jetpack集成的数据仓库。
共享参数的用法
SharedPreferences是Android的一个轻量级存储工具,它采用的存储结构是Key-Value的键值对方式,类似于Java的Properties,二者都是把Key-Value的键值对保存在配置文件中。不同的是,Properties的文件内容形如Key=Value,而SharedPreferences的存储介质是XML文件,且以XML标记保存键值对。保存共享参数键值对信息的文件路径为:/data/data/应用包名/shared_prefs/文件名。xml.下面是一个共享参数的XML文件例子:
<?xml version='1.0' encoding='utf-8'standalone='yes'?>
<map>
<string name="name"> Mr Lee </string>
<int name="age" value="30" />
<boolean name="married" value="true" />
<float name="weight" value="100.0" />
</map>
基于XML格式的特点,共享参数主要用于如下场合:
- 简单且孤立的数据。若是复杂且相互关联的数据,则要保存在关系数据库中。
- 文本形式的数据。若是二进制数据,则要保存至文件。
- 需要持久化存储的数据。App退出后再次启动时,之前保存的数据仍然有效。
在实际开发中,共享参数经常存储的数据包括:App的个性化配置信息、用户使用App的行为信息、临时需要保存的片段信息等。
共享参数对数据的存储和读取操作类似于Map,也有存储数据的put方法,以及读取数据的get方法。调用getSharedPreferences方法可以获得共享参数实例,获取代码示例如下:
//从share.xml获取共享参数实例
SharedPreferences shared = getSharedPreferences ("share", MODE_PRIVATE);
由以上代码可知,getSharedPreferences方法的第一个参数是文件名,填share表示共享参数的文件名是share.xml;第二个参数是操作模式,填MODE_PRIVATE表示私有模式。
往共享参数存储数据要借助于Editor类,保存数据的代码示例如下:
android.content.SharedPreferences.Editor editor = mShared.edit(); // 获得编辑器的对象
editor.putString("name", name); // 添加一个名叫name的字符串参数
editor.putInt("age", Integer.parseInt(age)); // 添加一个名叫age的整型参数
editor.putLong("height", Long.parseLong(height)); // 添加一个名叫height的长整型参数
editor.putFloat("weight", Float.parseFloat(weight)); // 添加一个名叫weight的浮点数参数
editor.putBoolean("married", isMarried); // 添加一个名叫married的布尔型参数
editor.putString("update_time", DateUtil.getNowDateTime("yyyy-MM-dd HH:mm:ss"));
editor.commit(); // 提交编辑器中的修改
注意上述代码采用了commit方法提交修改,该方法会把数据直接写入磁盘。如果想要更好的性能,可将commit方法改为apply方法,该方法的提交操作会先将数据写入内存,然后异步把数据写入磁盘。
从共享参数读取数据相对简单,读取数据的代码示例如下:
private void readSharedPreferences() {
// 从share.xml中获取共享参数对象
SharedPreferences shared = getSharedPreferences("share", MODE_PRIVATE);
String desc = "共享参数中保存的信息如下:";
// 获取共享参数保存的所有映射配对信息
Map<String, Object> mapParam = (Map<String, Object>) shared.getAll();
// 遍历该映射对象,并将配对信息形成描述文字
for (Map.Entry<String, Object> item_map : mapParam.entrySet()) {
String key = item_map.getKey(); // 获取该配对的键信息
Object value = item_map.getValue(); // 获取该配对的值信息
if (value instanceof String) { // 如果配对值的类型为字符串
desc = String.format("%s\n %s的取值为%s", desc, key,
shared.getString(key, ""));
} else if (value instanceof Integer) { // 如果配对值的类型为整型数
desc = String.format("%s\n %s的取值为%d", desc, key,
shared.getInt(key, 0));
} else if (value instanceof Float) { // 如果配对值的类型为浮点数
desc = String.format("%s\n %s的取值为%f", desc, key,
shared.getFloat(key, 0.0f));
} else if (value instanceof Boolean) { // 如果配对值的类型为布尔值
desc = String.format("%s\n %s的取值为%b", desc, key,
shared.getBoolean(key, false));
} else if (value instanceof Long) { // 如果配对值的类型为长整型
desc = String.format("%s\n %s的取值为%d", desc, key,
shared.getLong(key, 0L));
} else { // 如果配对值的类型为未知类型
desc = String.format("%s\n参数%s的取值为未知类型", desc, key);
}
}
if (mapParam.size() <= 0) {
desc = "共享参数中保存的信息为空";
}
tv_share.setText(desc);
}
实现记住密码功能
用户退出后重新进入登录页面,App没有回忆起上次的登录密码。现在利用共享参数改造该项目,使之实现记住密码的功能。
改造的内容主要有下列3处:
- 声明一个共享参数对象,并在onCreate中调用getSharedPreferences方法获取共享参数的实例。
- 登录成功时,如果用户勾选了“记住密码”复选框,就使用共享参数保存手机号码与密码。也就是在loginSuccess方法中增加以下代码:
// 如果勾选了“记住密码”,就把手机号码和密码都保存到共享参数中 if (isRemember) { SharedPreferences.Editor editor = mShared.edit(); // 获得编辑器的对象 editor.putString("phone", et_phone.getText().toString()); // 添加名叫phone的手机号码 editor.putString("password", et_password.getText().toString()); // 添加名叫password的密码 editor.commit(); // 提交编辑器中的修改 }
- 再次打开登录页面时,App从共享参数中读取手机号码与密码,并自动填入编辑框。也就是在onCreate方法中增加以下代码:
// 从share_login.xml获取共享参数对象 mShared = getSharedPreferences("share_login", MODE_PRIVATE); // 获取共享参数保存的手机号码 String phone = mShared.getString("phone", ""); // 获取共享参数保存的密码 String password = mShared.getString("password", ""); et_phone.setText(phone); // 往手机号码编辑框填写上次保存的手机号 et_password.setText(password); // 往密码编辑框填写上次保存的密码
代码修改完毕,只要用户上次登录成功时勾选了“记住密码”复选框,下次进入登录页面后App就会自动填写上次登录的手机号码与密码。
更安全的数据仓库
虽然SharedPreferences用起来比较方便,但是在一些特殊场景会产生问题。比如共享参数保存的数据较多时,初始化共享参数会把整个文件加载进内存,加载耗时可能导致主线程堵塞。又如在调用apply方法保存数据时,频繁apply容易导致线程等待超时。为此Android官方推出了数据仓库DataStore,并将其作为Jetpack库的基础组件。DataStore提供了两种实现方式,分别是Preferences DataStore和Proto DataStore,前者采用键值对存储数据,后者采用自定义类型存储数据。其中Preferences DataStore可以直接替代SharedPreferences。
由于DataStore并未集成到SDK中,而是作为第三方框架提供,因此首先要修改模块的build.gradle文件,往dependencies节点添加下面两行配置,表示导入指定版本的DataStore库:
implementation "androidx.datastore:datastore-preferences:1.0.0"
implementation "androidx.datastore:datastore-preferences-rxjava2:1.0.0"
数据仓库的用法类似于共享参数,首先要指定仓库名称,并创建仓库实例,示例代码如下:
private RxDataStore<Preferences> mDataStore; // 声明一个数据仓库实例
private DatastoreUtil(Context context) {
mDataStore = new RxPreferenceDataStoreBuilder(context.getApplicationContext(), "datastore").build();
}
// 获取数据仓库工具的实例
public static DatastoreUtil getInstance(Context context) {
if (instance == null) {
instance = new DatastoreUtil(context);
}
return instance;
}
其次从仓库实例中获取指定键名的数据,下面的代码模板演示了如何从数据仓库中读取字符串值:
// 获取指定名称的字符串值
public String getStringValue(String key) {
Preferences.Key<String> keyId = PreferencesKeys.stringKey(key);
Flowable<String> flow = mDataStore.data().map(prefs -> prefs.get(keyId));
try {
return flow.blockingFirst();
} catch (Exception e) {
return "";
}
}
最后往仓库实例写入指定键值,下面的代码模板演示了如何将字符串写入数据仓库:
// 设置指定名称的字符串值
public void setStringValue(String key, String value) {
Preferences.Key<String> keyId = PreferencesKeys.stringKey(key);
Single<Preferences> result = mDataStore.updateDataAsync(prefs -> {
MutablePreferences mutablePrefs = prefs.toMutablePreferences();
mutablePrefs.set(keyId, value);
return Single.just(mutablePrefs);
});
}
前面把数据仓库的初始化以及读写操作封装在DatastoreUtil中,接下来通过该工具类即可方便的访问数据仓库了。往数据仓库保存数据的代码示例如下:
// 从数据仓库中读取信息
private void readDatastore() {
DatastoreUtil datastore = DatastoreUtil.getInstance(this); // 获取数据仓库工具的实例
String desc = "数据仓库中保存的信息如下:";
desc = String.format("%s\n %s为%s", desc, "姓名",
datastore.getStringValue("name"));
desc = String.format("%s\n %s为%d", desc, "年龄",
datastore.getIntValue("age"));
desc = String.format("%s\n %s为%d", desc, "身高",
datastore.getIntValue("height"));
desc = String.format("%s\n %s为%.2f", desc, "体重",
datastore.getDoubleValue("weight"));
desc = String.format("%s\n %s为%b", desc, "婚否",
datastore.getBooleanValue("married"));
desc = String.format("%s\n %s为%s", desc, "更新时间",
datastore.getStringValue("update_time"));
tv_data.setText(desc);
}
从数据仓库获取数据的代码示例如下:
DatastoreUtil datastore = DatastoreUtil.getInstance(this); // 获取数据仓库工具的实例
datastore.setStringValue("name", name); // 添加一个名叫name的字符串
datastore.setIntValue("age", Integer.parseInt(age)); // 添加一个名叫age的整数
datastore.setIntValue("height", Integer.parseInt(height)); // 添加一个名叫height的整数
datastore.setDoubleValue("weight", Double.parseDouble(weight)); // 添加一个名叫weight的双精度数
datastore.setBooleanValue("married", isMarried); // 添加一个名叫married的布尔值
datastore.setStringValue("update_time", DateUtil.getNowDateTime("yyyy-MM-dd HH:mm:ss"));