第 7 章 数据持久化
7.2 文件存储
7.2.1 将数据存储到文件中
存储简单文本数据或二进制数据
-
放个EditText
-
代码
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onDestroy() {
super.onDestroy()
val inputText = editText.text.toString()
save(inputText)
}
private fun save(inputText: String) {
try{
val output = openFileOutput("data", Context.MODE_PRIVATE)//Context.MODE_PRIVATE 覆盖原本文件内容或创建新文件,Context.MODE_APPEND 如果已有文件,追加内容
val writer = BufferedWriter(OutputStreamWriter(output))
writer.use { //内置拓展函数,在Lambda表达式中代码全部执行完毕之后自动将外层的流关闭
it.write(inputText)
}
}catch (e:IOException){
e.printStackTrace()
}
}
}
- 在/data/data/包名/目录
7.2.2 从文件中读数据
val inputText = load()
if (inputText.isNotEmpty()) {
editText.setText(inputText)
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 { //内置拓展函数,将得到的每一行数据都回调到Lambda表达式
content.append(it)
}
}
}catch (e:IOException){
e.printStackTrace()
}
return content.toString()
}
7.3 SharedPreferences 存储
7.3.1 将数据存储到 SharedPreferences 中
- Context getSharePreferences
- Activitity getPreferences
saveButton.setOnClickListener {
val editor = getSharedPreferences("data",Context.MODE_PRIVATE).edit()
editor.putString("name", "Tom")
editor.putInt("age",28)
editor.putBoolean("married", false)
editor.apply()
}
7.3.2 从 SharedPreferences 中读取数据
restoreButton.setOnClickListener {
val prefs = getSharedPreferences("data", Context.MODE_PRIVATE)
val name = prefs.getString("name","")
val age = prefs.getInt("age", 0)
val married = prefs.getBoolean("married", false)
Log.d(TAG, "name is $name")
Log.d(TAG, "age is $age")
Log.d(TAG, "married is $married")
}
7.3.3 实现记住密码的功能
在广播的最佳实践基础上进行
- 增加勾选框
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/rememberPass"></CheckBox>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:text="Remember password"
></TextView>
</LinearLayout>
- 代码
val prefs = getPreferences(Context.MODE_PRIVATE)
val isRemember = prefs.getBoolean("remember_password", false)
if (isRemember) {
val account = prefs.getString("account","")
val password = prefs.getString("password","")
accountEdit.setText(account)
passwordEdit.setText(password)
rememberPass.isChecked = true
}
login.setOnClickListener {
val account = accountEdit.text.toString()
val password = passwordEdit.text.toString()
if(account == "admin" && password == "123456"){
val editor = prefs.edit()
if (rememberPass.isChecked) {
editor.putBoolean("remember_password", true)
editor.putString("account", account)
editor.putString("password", password)
}else{
editor.clear()
}
editor.apply()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}else{
Toast.makeText(this,"account or password is invalid",Toast.LENGTH_SHORT).show()
}
}
7.4 SQLite 数据库存储
7.4.1 创建数据库
- 实现 SQLiteOpenHelper 抽象类,重写create和upgrade抽象方法,分别用来创建和更新数据库
class MyDatabaseHelper(val context: Context, name:String, version:Int) : SQLiteOpenHelper(context,name,null,version){
//建表语句
private val createBook = "create table Book("+
" id integer primary key autoincrement,"+//将id设置为主键,自增长
"author text,"+
"price real,"+//浮点型
"pages integer,"+
"name text)"
override fun onCreate(p0: SQLiteDatabase) {
p0.execSQL(createBook)
Toast.makeText(context,"Created succeeded",Toast.LENGTH_SHORT).show()
}
override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
}
}
- 编写创建数据库的代码
val dbHelper = MyDatabaseHelper(this,"BookStore.db",1)//数据库名和版本号
createDatabase.setOnClickListener { dbHelper.writableDatabase//创建数据库 }
- 利用插件查看创建的数据库
- 首先找到数据库的文件地址 save as 导出
- 下载Database Navigator
- 加号 - SQLite -修改文件地址为刚刚导出的地方
7.4.2 升级数据库
- 代码
class MyDatabaseHelper(val context: Context, name:String, version:Int) : SQLiteOpenHelper(context,name,null,version){
private val createBook = "create table Book("+
" id integer primary key autoincrement,"+//将id设置为主键,自增长
"author text,"+
"price real,"+//浮点型
"pages integer,"+
"name text)"
//建表语句
private val createCategory = "create table Category ("+
"id integer primary key autoincrement,"+
"category_name text,"+
"category_code integer)"
override fun onCreate(p0: SQLiteDatabase) {
p0.execSQL(createBook)
p0.execSQL(createCategory)//需要新创建的数据库
Toast.makeText(context,"Created succeeded",Toast.LENGTH_SHORT).show()
}
override fun onUpgrade(p0: SQLiteDatabase, p1: Int, p2: Int) {
//将之前的表删除
p0.execSQL("drop table if exists Book")
p0.execSQL("drop table if exists Category")
//重新调用创建语句
onCreate(p0)
}
}
- 升级版本号
val dbHelper = MyDatabaseHelper(this,"BookStore.db",3)
7.4.3 添加数据
addData.setOnClickListener {
//获取数据库对象
val db = dbHelper.writableDatabase
//给ContentValue赋值
val value1 = ContentValues().apply {
put("name","The Da Vinci Code")
put("author","Dan Brown")
put("pages",454)
put("price",16.96)
}
//插入数据
db.insert("Book",null,value1)
val value2 = ContentValues().apply {
put("name","The Lost Symbol")
put("author","Dan Brown")
put("pages",510)
put("price",19.95)
}
db.insert("Book",null,value2)
}
查看添加数据:重新存储数据库文件,然后利用插件重新获取,双击表,点击no filter
7.4.4 更新数据
updateData.setOnClickListener {
val db = dbHelper.writableDatabase
val values = ContentValues()
values.put("price",10.99)
db.update("Book",values,"name = ?", arrayOf("The Da Vinci Code"))//kotlin快捷创建数组
}
//这个表中,名字为The Da Vinci Code的书价格都改为10.99
补充:可以不用插件,直接在App inspection中可以查看表的数据
7.4.5 删除数据
deleteData.setOnClickListener {
val db = dbHelper.writableDatabase
db.delete("Book","pages > ?", arrayOf("500"))
}
//页数大于500全都删除
7.4.6 查询数据
- Query 七个参数
-
table 表名
-
columns 查询哪几列
-
selection 4. selectionArgs 约束查询某一行或某几行
-
groupBy 指定需要group by的列
-
having 上面操作后的进一步过滤
-
orderBy 指定排序方式,不指定则使用默认的排序
- 返回值 Cursor
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.getString(cursor.getColumnIndex("pages"))
val price = cursor.getString(cursor.getColumnIndex("price"))
Log.d(TAG, "book name is $name")
Log.d(TAG, "book author is $author")
Log.d(TAG, "book pages is $pages")
Log.d(TAG, "book price is $price")
}while (cursor.moveToNext())
}
cursor.close()
}
7.5.1 使用事务
事务:保证让一系列操作要么全部执行,要么都不执行
replaceData.setOnClickListener {
val db = dbHelper.writableDatabase
db.beginTransaction()
try {
db.delete("Book",null,null)
if (true) {
//手动抛出异常让事务失败
// throw NullPointerException()
val values = ContentValues().apply {
put("name", "Game of Thrones")
put("author", "George Martin")
put("pages", 720)
put("price",20.85)
}
db.insert("Book", null, values)
db.setTransactionSuccessful()
}
} catch (e: Exception) {
e.printStackTrace()
}finally {
db.endTransaction()
}
}
7.5.2 升级数据库的最佳写法
- 第一版
class MyDatabaseHelper(val context: Context, name:String, version:Int) : SQLiteOpenHelper(context,name,null,version){
private val createBook = "create table Book("+
" id integer primary key autoincrement,"+//将id设置为主键,自增长
"author text,"+
"price real,"+//浮点型
"pages integer,"+
"name text)"
override fun onCreate(p0: SQLiteDatabase) {
p0.execSQL(createBook)
}
override fun onUpgrade(p0: SQLiteDatabase, p1: Int, p2: Int) {
}
}
- 第二版 需要添加Category表
class MyDatabaseHelper(val context: Context, name:String, version:Int) : SQLiteOpenHelper(context,name,null,version){
private val createBook = "create table Book("+
" id integer primary key autoincrement,"+//将id设置为主键,自增长
"author text,"+
"price real,"+//浮点型
"pages integer,"+
"name text)"
private val createCategory = "create table Category ("+
"id integer primary key autoincrement,"+
"category_name text,"+
"category_code integer)"
//如果直接下载版本二就会执行创建表的操作
override fun onCreate(p0: SQLiteDatabase) {
p0.execSQL(createBook)
p0.execSQL(createCategory)
}
//如果使用版本二覆盖版本一,就会创建一张新表
override fun onUpgrade(p0: SQLiteDatabase, oldVersion: Int, p2: Int) {
if(oldVersion <=1){
p0.execSQL(createCategory)
}
}
}
- 第三版 需要Book表中添加category_id字段
class MyDatabaseHelper(val context: Context, name:String, version:Int) : SQLiteOpenHelper(context,name,null,version){
private val createBook = "create table Book("+
" id integer primary key autoincrement,"+//将id设置为主键,自增长
"author text,"+
"price real,"+//浮点型
"pages integer,"+
"name text,"+
"category_id integer)"
private val createCategory = "create table Category ("+
"id integer primary key autoincrement,"+
"category_name text,"+
"category_code integer)"
//如果直接下载版本二就会执行创建表的操作
override fun onCreate(p0: SQLiteDatabase) {
p0.execSQL(createBook)
p0.execSQL(createCategory)
}
//如果使用版本二覆盖版本一,就会创建一张新表
override fun onUpgrade(p0: SQLiteDatabase, oldVersion: Int, p2: Int) {
if(oldVersion <=1){
//如果当前是第一版更新第三版,两个都会执行
p0.execSQL(createCategory)
}
if (oldVersion <= 2) {
//如果当前是第二版更新第三版,就会执行这个创建列
p0.execSQL("alter table Book add column category_id integer")
}
}
}
7.6 Kotlin课堂:高阶函数的应用
7.6.1 简化 SharedPreferences 的用法
最初写法
val editor = getSharedPreferences("data",Context,MODE_PRIVATE).edit()
editor.putString("name","Tom")
editor.apply()
- 定义顶层方法 SharePreferences.kt
fun SharedPreferences.open(block: SharedPreferences.Editor.() -> Unit) {
val editor = edit()
editor.block()
editor.apply()
}
- 调用
getSharedPreferences("data", Context.MODE_PRIVATE).open {
putString("name","Tom")
}
- 这个实现在KTX拓展库中已经有了
getSharedPreferences("data", Context.MODE_PRIVATE).edit {
putString("name","Tom")
}
7.6.2 简化 ContentValues 的用法
- 最初写法
val values = ContentBalues()
values.put("name","Game of Thrones")
db.insert("Book",null,values)
- 补充 mapOf函数允许使用 A to B 的方式来快捷创建一个键值对,返回值是Pair 对象
/**Pair是A to B产生的类型
* Any是所有类的基类,相当于java中的Object
* ?值可以为空
* vararg修饰表示允许向方法传递多个Pair类型的参数
*/
fun cvOf(vararg pairs: Pair<String,Any?>):ContentValues {
val cv = ContentValues()
for (pair in pairs) {
val key = pair.first
val value = pair.second
when (value) {
is Int -> cv.put(key,value)
is Long -> cv.put(key,value)
is Short -> cv.put(key,value)
is Float -> cv.put(key,value)
is Double -> cv.put(key,value)
is Boolean -> cv.put(key,value)
is String -> cv.put(key,value)
is Byte -> cv.put(key,value)
is ByteArray -> cv.put(key,value)
null -> cv.putNull(key)
}
}
return cv
}
- 调用
val values = cvOf("name" to "Game of Thrones","author" to "George Martin","pages" to 720,"price" to 20.85)
db.insert("Book",null,values)
- 进一步优化
//apply的返回值就是调用对象
//单行代码函数语法糖,等号代替返回值的声明
fun cvOf(vararg pairs: Pair<String,Any?>)= ContentValues().apply {
val cv = ContentValues()
for (pair in pairs) {
val key = pair.first
val value = pair.second
when (value) {
is Int -> put(key,value)
is Long -> put(key,value)
is Short -> put(key,value)
is Float -> put(key,value)
is Double -> put(key,value)
is Boolean -> put(key,value)
is String -> put(key,value)
is Byte -> put(key,value)
is ByteArray -> put(key,value)
null -> putNull(key)
}
}
}
- 系统自带的库
val values = contentValuesOf("name" to "Game of Thrones","author" to "George Martin","pages" to 720,"price" to 20.85)
db.insert("Book",null,values)