这个问题纠结了很久,Google 官方文档又一直在鼓吹自家的云备份,各种办法似乎最终都要跟云联系起来,找了很久总算明白了解决方法……
这里是借助 SharedPreferences
的 getAll()
方法(Kotlin 中直接 .all
)(Kotlin YES!)
由于 Google 对 Android 权限的收紧,未来直接操作备份文件的方式并不可取也不行,因此我使用了 SAF。
而且用了 SAF,也可以选择直接将备份文件备份到 Google Drive 等采用标准接口接入 SAF 的应用。
准备
创建变量
为了便于后期开发的理解,我们创建两个变量:
private val WRITE_REQUEST_CODE: Int = 43
private val READ_REQUEST_CODE: Int = 42
很好理解,只是为了在回调的时候判断返回的是写入文件(备份)还是读取文件(恢复)
这是 Google 官方文档中使用的变量及变量值,这里直接沿用,但是并非一定是这俩值。
另外,这两个变量并非一定要处于同一份文件中,请根据备份和恢复按钮的文件位置自行判断。
引入 fastjson(可选)
其实并不一定要引入 fastjson,只是我在我的项目中早已引入,而且 fastjson 易于使用。
我调用 fastjson 的目的只是「将 Map 转换为 JSON(String)」以及「将 JSON(String)转换成 Map」,你当然可以用原生 JSON 库,用 Gson 来实现 但我没试过
Github:https://github.com/alibaba/fastjson
这里我引入的是 fastjson 的 Android 版本。
官方声称:「和标准版本相比,Android 版本去掉一些 Android 虚拟机 Dalvik 不支持的功能,使得 jar 更小,同时针对 Dalvik 做了很多性能优化,包括减少方法调用等。parse 为 JSONObject/JSONArray 时比原生 org.json 速度快,序列化反序列化 JavaBean 性能比 jackson/gson 性能更好」
在 Module 级的 build.gradle
下的 dependencies
中加入
implementation 'com.alibaba:fastjson:VERSION_CODE'
版本可以直接查阅 fastjson 官方 Github 库或 Maven.org 或 bintray,截止到这篇文章撰写的时刻,我使用 com.alibaba:fastjson:1.1.72.android
备份数据
首先要明确的一点是,我们最终还是文件操作,但是不同于 File
直接传入文件路径,我们选择通过 SAF 传入 URI 来操作。
这里,我们选择最好理解的 JSON 作为数据存储格式。
创建文档
首先要做的是「创建文档」,我们需要借助 startActivityForResult()
启动一个意图。
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/json"
putExtra(Intent.EXTRA_TITLE, "文件名")
}
startActivityForResult(intent, WRITE_REQUEST_CODE)
文件的 MIME 类型请自行判断。我推荐你使用 System.currentTimeMillis()
来得到时间戳并填入文件名中,以防止备份文件名称重复。
回调
有了 startActivityForResult()
,自然就需要回调,重写 onActivityResult()
:
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData)
if (requestCode == WRITE_REQUEST_CODE && resultData != null && resultData.data != null) {
//备份
if (backupSharedPreferences(resultData.data as Uri)) {
//成功时
}else{
//失败时
}
}
}
实现
理所当然地,我们现在需要开始写 backupSharedPreferences()
方法
private fun backupSharedPreferences(uri: Uri): Boolean {
var spIntent: SharedPreferences = getSharedPreferences("要备份的SP的名字", Context.MODE_PRIVATE)
try {
contentResolver.openFileDescriptor(uri, "w")?.use {
FileOutputStream(it.fileDescriptor).use {
it.write(
JSON.toJSONString(spIntent.all).toByteArray()
)
}
}
return true
} catch (e: FileNotFoundException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
return false
}
这段代码其实不难理解,从 ContentResolver
获取 FileOutputStream
,传入 URI 并使用默认的写入模式("w"),然后将想要的数据写入文件。
这里我们先用 getSharedPreferences()
传入指定参数后获得一个 SharedPreferences
类型的对象,命名为 spIntent
(啥都行,自己决定),目的是通过 spIntent.all
获取此 SharedPreferences 文件的全部数据(Map 类型)。
再通过 fastjson 的 JSON.toJSONString()
方法,将 Map 转换为 JSON 字符串,最终写入 URI 对应的文件,也就是我们在 SAF 中创建的文件。
至此,备份 SharedPreferences 数据完成。
恢复数据
当然了,这里恢复的数据必定得是由上面的办法备份的数据才行。
打开文档
与备份的「创建文档」相对应的,这里使用「打开文档」。
同样的,我们需要借助 startActivityForResult() 启动一个意图。
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/json"
}
startActivityForResult(intent, READ_REQUEST_CODE)
回调
同样的,我们需要回调。如果你的备份和恢复处于同一个 Activity 中,两个 if 需要同时共存于 onActivityResult()
中。重写 onActivityResult()
:
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData)
if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) {
//还原
if (restoreSharedPreferences(resultData.data as Uri)) {
//成功时
}
}
}
实现
同样理所当然地,我们现在需要开始写 restoreSharedPreferences()
方法。
@Throws(IOException::class)
private fun restoreSharedPreferences(uri: Uri): Boolean {
var speIntent: SharedPreferences.Editor = getSharedPreferences("要备份的SP的名字", Context.MODE_PRIVATE).edit()
val stringBuilder = StringBuilder()
contentResolver.openInputStream(uri)?.use { inputStream ->
BufferedReader(InputStreamReader(inputStream)).use { reader ->
var line: String? = reader.readLine()
while (line != null) {
stringBuilder.append(line)
line = reader.readLine()
}
}
}
val map = JSON.parseObject(stringBuilder.toString())
speIntent.clear()
map.forEach {
speIntent.putBoolean(it.key, it.value as Boolean)
}
speIntent.apply()
return true
}
不再过多赘述,我们一番操作后便可以从 stringBuilder.toString()
中拿到所需文件(恢复所用文件)的字符串,通过 fastjson 的 JSON.parseObject()
直接将字符串转换为 Map。
在清除掉原有的 SharedPreferences 数据后,通过遍历 Map,写入 SharedPreferences 中并最终 apply()
提交更改。另外,putBoolean()
只是因为我的 SP 是布尔类型的,请自己判断修改。
至此,恢复 SharedPreferences 数据完成。
共有 0 条评论