Android中的数据持久化

可在 Android Studio 打开Device Explorer,在/data/data/<APP包名>中可以看到应用的数据。

SharedPreferences

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// 写数据
val editor = getSharedPreferences("user", Context.MODE_PRIVATE).edit()
editor.putString("name", "张三")
editor.putInt("age", 18)
editor.putBoolean("married", false)
editor.apply() // 将添加的数据提交,从而完成数据存储操作

/// 读数据
val prefs = getSharedPreferences("user", Context.MODE_PRIVATE)
val name = prefs.getString("name", "")
val age = prefs.getInt("age", 0)
val married = prefs.getBoolean("married", false)
Log.d("MyLog", "姓名:$name")
Log.d("MyLog", "年龄:$age")
Log.d("MyLog", "是否已婚:$married")

其中getSharedPreferences的第二个参数Context.MODE_PRIVATE表示只有当前的应用程序才可以对这个SharedPreferences文件进行读写,且只有这一种模式,其他几种操作模式均已被废弃。

SharedPreferences文件是使用XML格式来对数据进行管理的:

SharedPreferences

MMKV

MMKV Github 仓库

安装引入:

1
2
3
4
dependencies {
// 将 "1.3.5" 替换为最新版本
implementation 'com.tencent:mmkv:1.3.5'
}

在APP启动时初始化MMKV,设定MMKV的根目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val mmkvPath = MMKV.initialize(this)
Log.i("MyLog", "MMKV存储路径: $mmkvPath")

val mmkv = MMKV.defaultMMKV()
mmkv.encode("name", "李四")
mmkv.encode("age", 18)
mmkv.encode("married", false)

val name = mmkv.decodeString("name")
val age = mmkv.decodeInt("age")
val married = mmkv.decodeBool("married", false) // 当数据不存在时,取第二个参数为默认值
}

Q:MMKV与SharedPreferences有何区别?MMKV的适用场景?

A:

……

SQLite

Android内置了SQLite数据库,用于本地存储关系复杂的数据。

自定义一个继承SQLiteOpenHelper的类,当调用它的实例的getReadableDatabasegetWritableDatabase方法时,传入数据库名和版本号,就可以打开指定的数据库。若不存在该数据库,则会自动创建,然后触发SQLiteOpenHelper的onCreate方法。若传入的版本号大于数据库的版本号,就会触发onUpgrade方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MyDatabaseHelper(val context: Context, name: String, version: Int) :
SQLiteOpenHelper(context, name, null, version) {
private val createBookSql = """
create table Book (
id integer primary key autoincrement,
name text,
price real,
pages integer
)
"""

override fun onCreate(db: SQLiteDatabase) {
db.execSQL(createBookSql)
Toast.makeText(context, "Book表创建成功", Toast.LENGTH_SHORT).show()
}

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { }
}

val dbHelper = MyDatabaseHelper(this, "BookStore.db", 1)
val db = dbHelper.writableDatabase // 无数据库BookStore,则创建数据库,触发onCreate,创建Book表

dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)
db = dbHelper.writableDatabase // 打开已有数据库,版本号更新,触发onUpgrade

事务是数据库运行的基本单位。当事务执行成功时,所有对数据库的更新操作都会被提交;事务执行中途失败,那么该事务中所有已经进行的操作都会被撤销,恢复到事务执行前的状态。

以买书为例,买方得到书和卖方失去书必须同时成功或失败,要放在一个事务中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 1)
db.beginTransaction() // 开启事务
try {
// 即 DELETE FROM Book WHERE name == "第一行代码"
db.delete("Book", "name == ?", arrayOf("第一行代码"))

if (true) {
// 模拟事务失败
throw NullPointerException()
}

val values = ContentValues().apply {
put("name", "第一行代码")
put("price", 50.00)
put("pages", 810)
}
db.insert("Store", null, values) // 将valus插入Store表中
db.setTransactionSuccessful() // 事务已经执行成功
} catch (e: Exception) {
e.printStackTrace()
} finally {
db.endTransaction() // 结束事务
}

当事务失败时,beginTransactionendTransaction之内的逻辑都不会生效

读写文件

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
/// 写文件
fun save(inputText: String) {
try {
// data为要打开的文件名
// 第二个参数默认为`MODE_PRIVATE`,表示若该文件已存在,则直接覆盖原文件
// `MODE_APPEND`表示若该文件已存在,在原文件基础上追加内容
val output = openFileOutput("data", Context.MODE_PRIVATE)

val writer = BufferedWriter(OutputStreamWriter(output))

// use作用是在Lambda表达式完成后关闭外层的流,无需手动关闭
writer.use {
it.write(inputText)
}
} catch (e: IOException) {
e.printStackTrace()
}
}

/// 读文件
fun load(): String {
val content = StringBuilder()
try {
val input = openFileInput("data")
val reader = BufferedReader(InputStreamReader(input))
reader.use {
reader.forEachLine { content.append(it) }
}
} catch (e: IOException) {
e.printStackTrace()
}
return content.toString()
}