第7章
数据存储全方案,详解持久化技术
7.1 什么是持久化技术
很多数据都是瞬时数据,存储在内存中可能会因内存被回收而丢失。持久化技术就是可以让数据从瞬时状态变为持久状态。
Android中主要提供了3种方法用于简单实现数据持久化功能:
- 文件存储
- SharedPreferences存储
- 数据库存储
7.2 文件存储
最基本的存储方式,不对内容进行任何格式化处理。适合存储一些简单的文本数据或二进制数据。
7.2.1 将数据存储到文件中
主要就是这个save函数,和Java流比较类似。奈何没有学过Java,看不太懂啊!实在不行先当一段固定语法记住先。
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
// 书上是onDestroy,但是真机上返回并没有销毁程序,强制关闭不知道为什么也不行,于是改成了onPause
override fun onPause() {
super.onPause()
val inputText = binding.editText.text.toString()
save(inputText)
}
private fun save(inputText: String) {
try {
// 第一个参数是保存的文件名,路径默认;第二个参数表示当指定相同文件名时,会覆盖原有文件
val output = openFileOutput("data", Context.MODE_PRIVATE)
val writer = BufferedWriter(OutputStreamWriter(output))
writer.use { it.write(inputText) }
} catch (e: IOException) {
e.printStackTrace()
}
}
}
7.2.2 从文件中读取数据
上面用到了openFileOutput
函数,这里就用openFileInput
啦。大体上是一致的,请看代码:
class MainActivity : AppCompatActivity() {
...
val inputText = load()
if (inputText.isNotEmpty()) {
binding.editText.setText(inputText)
binding.editText.setSelection(inputText.length) // 将光标设置到文本末尾位置
Toast.makeText(this, "Restoring succeeded", Toast.LENGTH_SHORT).show()
}
}
private 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()
}
...
}
7.3 SharedPreferences存储
SharedPreferences使用键值对存储数据。支持多种不同的数据类型存储:存int读出来也是int,存string读出来也是string。
7.3.1 存储数据进去
首先需要获取SharedPreferences对象,有以下两种方法:
- Context类中的
getSharedPreferences()
方法
接收两个参数:第一个参数用于指定SharedPreferences文件的名称,默认路径是/data/data/<package name>/shared_prefs/目录下;第二个参数用于指定操作模式,目前只有MODE_PRIVATE
这一种可用(它和直接传入0的效果是一样的),其他均已被废弃。 - Activity类中的
getPreferences()
方法
它只接受一个指定操作模式的参数,因为它自动将当前Activity的类名作为文件名。
用法也是super easy,请看代码:
class MainActivity : AppCompatActivity() {
...
binding.saveButton.setOnClickListener {
// 第一步获取Editor对象
val editor = getSharedPreferences("data", 0).edit()
// 第二步存储数据
editor.putString("name", "nayufeng")
editor.putInt("age", 21)
editor.putBoolean("married", false)
// 第三步apply提交
editor.apply()
}
...
}
7.3.2 读取数据出来
步骤是获得SharedPreferences对象,然后用getString等命令获取数据(第一个命令是键,第二个参数是默认值,即表示当传入的键找不到对应的值时会返回什么值)
class MainActivity : AppCompatActivity() {
...
binding.restoreButton.setOnClickListener {
val prefs = getSharedPreferences("data", 0)
val name = prefs.getString("name", "No such value")
val age = prefs.getInt("age", 0)
val married = prefs.getBoolean("married", false)
Log.d("Main", "Name is $name")
Log.d("Main", "Age is $age")
Log.d("Main", "Married is $married")
}
...
}
7.4 SQLite数据库存储
7.4.1 创建数据库
SQLiteOpenHelper是一个抽象类,我们需要新建一个帮助类去继承它。将SQL语法部分作为一个字符串传入db.execSQL
命令中即可执行。
class MyDatabaseHelper(val context: Context, name: String, versoin: Int) :
SQLiteOpenHelper(context, name, null, versoin) {
private val creatBook = "create table Book (" +
"id integer primary key autoincrement," +
"author text," +
"price real," +
"pages integer," +
"name text)"
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(creatBook)
Toast.makeText(context, "Create succeed", Toast.LENGTH_SHORT).show()
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
}
}
创建的表是不能直接查看的,需要安装一个"Database Navigator"插件。
7.4.2 升级数据库
在onUpgrade()
方法中先将已存在的表删除,然后就可以调用onCreate()
方法了。
class MyDatabaseHelper(val context: Context, name: String, versoin: Int) :
SQLiteOpenHelper(context, name, null, versoin) {
...
private val createCategory = "create table Category (" +
"id integer primary key autoincrement," +
"category_name text," +
"category_code integer)"
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(creatBook)
db.execSQL(createCategory)
Toast.makeText(context, "Create succeed", Toast.LENGTH_SHORT).show()
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS Book")
db.execSQL("DROP TABLE IF EXISTS Category")
onCreate(db)
}
}
7.4.3 添加数据
对数据的操作一共有4种:CRUD
- C: create(添加)。SQL命令:
insert
- R: retrieve(查询)。SQL命令:
select
- U: upgrade(更新)。SQL命令:
update
- D: delete(删除)。SQL命令:
delete
本节先看如何添加数据。
SQLiteDatabase
提供了一个insert()
方法,接受3个参数:
- 表名:希望向哪张表里添加数据,就传入该表的名字;
- 用于在未指定添加数据的情况下给某些可为空的列自动赋值
NULL
,一般传入null
即可; ContentValues
对象:提供了一系列的put()
方法重载,用于向ContentValues
种添加数据。
看代码:点击addData这个按钮添加数据
binding.addData.setOnClickListener {
val db = dbHelper.writableDatabase
val values1 = ContentValues().apply {
// 组装第一条数据
put("name", "The Da Vinci Code")
put("author", "Dan Brown")
put("pages", 454)
put("price", 16.96)
}
db.insert("Book", null, values1) // 插入第一条数据
val values2 = ContentValues().apply {
// 组装第二条数据
put("name", "The Lost Symbol")
put("author", "Dan Brown")
put("pages", 510)
put("price", 19.95)
}
db.insert("Book", null, values2) // 插入第二条数据
}
7.4.4 更新数据
SQLiteDatabase
提供了一个update()
方法,接受4个参数:
- 表名:希望向哪张表里添加数据,就传入该表的名字;
ContentValues
对象:更新数据在这里组装进去;- 剩下两个参数用于约束更新某一/几行中的数据,默认更新所有行。
binding.updateData.setOnClickListener {
val db = dbHelper.writableDatabase
val values = ContentValues()
values.put("price", 10.99)
db.update("Book", values, "name = ?", arrayOf("The Da Vinci Code"))
}
这里update()
方法中第三个参数对应的是SQL语句的where部分,表示更新所有name等于 ? 的行,?是占位符,通过第四个参数提供的一个字符串数组为第三个参数种每个占位符指定相应的内容。
7.4.5 删除数据
SQLiteDatabase
提供了一个delete()
方法,接受3个参数:
- 表名:希望向哪张表里添加数据,就传入该表的名字;
- 第二、三个参数用于约束删除某一/几行中的数据,默认删除所有行。
binding.deleteData.setOnClickListener {
val db = dbHelper.writableDatabase
db.delete("Book", "pages > ?", arrayOf("500"))
}
7.4.6 查询数据
query()
方法与SQL部分的对应关系:
query()列方法参数 | 对应SQL | 描 述 |
---|---|---|
table | FROM table_name | 指定查询的表名 |
columns | SELECT column1, column2 | 指定查询的列名 |
selection | WHERE column=value | 指定where的约束条件 |
selectionArgs | - | 为where中的占位符提供具体的值 |
groupby | GROUP BY column | 指定需要groupby的列 |
having | HAVING column=value | 对groupby后的结果进一步约束 |
orderby | ORDER BY column1, column2 | 指定查询结果的排列方式 |
代码示例:
binding.queryData.setOnClickListener {
val db = dbHelper.writableDatabase
val cursor = db.query("Book", null, null, null, null, null, null) // 就是查看所有数据
if (cursor.moveToFirst()) { // 将指针移动到第一行的位置
do {
val name = cursor.getString(cursor.getColumnIndex("name"))
val author = cursor.getString(cursor.getColumnIndex("author"))
val pages = cursor.getInt(cursor.getColumnIndex("pages"))
val price = cursor.getDouble(cursor.getColumnIndex("price"))
Log.d("MainActivity", "book name is $name")
Log.d("MainActivity", "author is $author")
Log.d("MainActivity", "pages is $pages")
Log.d("MainActivity", "price is $price")
} while (cursor.moveToNext()) // 将指针移动到下一行
}
cursor.close()
}
7.4.7 直接使用SQL操作数据库
我感觉这个更简单一点。示例如下:
- 添加数据
db.execSQL("INSERT INTO Book (name, author, pages, price) VALUES(?, ?, ?, ?)",
arrayOf("The Da Vinci Code", "Dan Brown", "454", "16.96")
)
- 更新数据
db.execSQL("UPDATE Book SET price = ? WHERE name = ?", arrayOf("10.99", "The Da Vinci Code"))
- 删除数据
db.execSQL("DELETE FROM Book WHERE pages > ?", arrayOf("500"))
- 查询数据
val cursor = db.rawQuery("SELECT * FROM Book", null)
别的
- 事务:让一系列操作要么全部完成,要么一个都不会完成。
db.beginTransaction() // 开启事务
db.setTransaction() // 事务已经执行成功
db.endTransaction() // 结束事务
- 升级数据库
在onUpgrade()
方法中添加一个if:
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
if (oldVersion <= 1) {
db.execSQL(createCategory1)
}
if (oldVersion <= 2) {
db.execSQL(createCategory2)
}
}
这样就可以在保留前一版数据的同时更新新版本了。
- 简化
SharedPreferences
的方法:getSharedPreferences.edit
getSharedPreferences("data", Context.MODE_PRIVATE).edit {
putString("name", "Tom")
putString("age", 28)
}
- 简化
ContentValues
的方法:contentValuesOf()
val values = contentValuesOf("name" to "Game of Thrones", "author" to "George Martin",
"pages" to 720, "price" to 20.85)
db.insert("Book", null, values)