文章目录
五、数据持久化
Android的数据持久化3种方式: 文件存储, SharedPreferences, 数据库存储
5.1 文件持久化
5.1.1 文件写入
默认写在/data/data/com.example.FilePersistenceTest/files/data中, 当按back键时即写入该文件
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)
val writer = BufferedWriter(OutputStreamWriter(output))
writer.use {
it.write(inputText)
}
} catch (e: IOException) {
e.printStackTrace()
}
}
}
5.1.2 文件读取
程序加载时, 读取到editText中
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
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 {
content.append(it)
}
}
} catch (e: IOException) {
e.printStackTrace()
}
return content.toString()
}
}
5.2 SharedPreferences 持久化
是键值对的形式, 且保持数据结构(如整型, 如字符串等), 更易用
5.2.1 数据写入
有2种获取SharedPreferences的方式
- 第一种是Context类中的getSharedPreferences()方法, 其有2个参数
- 第1个参数是路径, 在/data/data//shared_prefs/
- 第2个参数是操作模式, 默认是MODE_PRIVATE, 即只有当前应用程序才可对此读写
- 第二种是Activity类的getPreferences()方法
- 只有1个参数
- 自动将当前activity雷鸣作为SharedPreferences的文件名
- 调用方式如下3步骤
- 调用SharedPreferences对象的edit()方法获取一个SharedPreferences.Editor对象
- 向SharedPreferences.Editor对象中添加数据,比如添加一个布尔型数据就使用putBoolean()方法,添加一个字符串则使用putString()方法,以此类推。
- 调用apply()方法将添加的数据提交,从而完成数据存储操作
5.2.2 数据读取
布局如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/saveButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Save Data" />
<Button
android:id="@+id/restoreButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Restore Data" />
</LinearLayout>
- MainActivity如下
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
saveButton.setOnClickListener {
val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
editor.putString("name", "Tom")
editor.putInt("age", 28)
editor.putBoolean("married", false)
editor.apply()
}
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("MainActivity", "name is $name")
Log.d("MainActivity", "age is $age")
Log.d("MainActivity", "married is $married")
}
}
}
通过点击按钮, 会在/data/data/com.example.sharedpreferencestest/shared_prefs/data.xml中存放如下数据
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="name">Tom</string>
<boolean name="married" value="false" />
<int name="age" value="28" />
</map>
点击load按钮, 即可加载数据, 在控制台打印如下
// 刚开始点击load按钮后的打印, 为空值
2022-08-12 09:56:59.452 3376-3376/com.example.sharedpreferencestest D/MainActivity: name is
2022-08-12 09:56:59.452 3376-3376/com.example.sharedpreferencestest D/MainActivity: age is 0
2022-08-12 09:56:59.452 3376-3376/com.example.sharedpreferencestest D/MainActivity: married is false
// 点击save按钮后, 再点击load按钮后的打印, 为save的值
2022-08-12 10:04:05.721 5566-5566/com.example.sharedpreferencestest D/MainActivity: name is Tom
2022-08-12 10:04:05.721 5566-5566/com.example.sharedpreferencestest D/MainActivity: age is 28
2022-08-12 10:04:05.721 5566-5566/com.example.sharedpreferencestest D/MainActivity: married is false
6.2.3 实现记住密码功能
新建一个LoginActivity,布局如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal">
<TextView
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="Account:"
android:textSize="18sp" />
<EditText
android:id="@+id/accountEdit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal">
<TextView
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="Password:"
android:textSize="18sp" />
<EditText
android:id="@+id/passwordEdit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:inputType="textPassword" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/rememberPass"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Remember password"
android:textSize="18sp" />
</LinearLayout>
<Button
android:id="@+id/login"
android:layout_width="200dp"
android:layout_height="60dp"
android:layout_gravity="center_horizontal"
android:text="Login" />
</LinearLayout>
- LoginActivity如下
class LoginActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
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()
// 如果账号是admin且密码是123456,就认为登录成功
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()
}
}
}
}
5.3 Sqlite 数据库持久化
5.3.1 创建数据库
SQLiteOpenHelper类, 有onCreate()和onUpgrade()方法, 实现数据库创建和升级
getReadableDatabase()和getWriteableDatabase()获取数据库实例
点击按钮则创建database, 其中databaseHelper专门负责调用sqliteOpenHelper来调用数据库
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 1)
createDatabase.setOnClickListener {
dbHelper.writableDatabase
}
}
}
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," +
"author text," +
"price real," +
"pages integer," +
"name text)"
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(createBook)
Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
}
}
在/data/data/com.example.databasetest/databases/BookStore.db有sqlite文件, 可导出并用DBBrowser插件查看
5.3.2 升级数据库
目前有了Book表,若希望增加Category表, 则可添加如下代码, 多次调用onCreate(), 但只有第一次才会生效, 因为只有第一次才会建库BookStore.db, 后续若此库存在则不会再建库BookStore.db, 除非卸载app或手动删除BookStore.db文件
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," +
"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(db: SQLiteDatabase) {
db.execSQL(createBook)
db.execSQL(createCategory)
Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()
}
// 为了保证升级兼容性, 一般会在onUpgrade()方法内根据version判断执行不同版本的升级逻辑
// 从低version开始, 保证任何版本都能成功执行
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
if (oldVersion <= 1) {
db.execSQL(createCategory)
}
if (oldVersion <= 2) {
db.execSQL("alter table Book add column category_id integer")
}
}
}
若传入的version大于旧值则调用onUpgrade, 否则若version相等则调onUpdate
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)
// 传入的version>1时, 即会调用onUpgrade()
5.3.3 添加数据
通过如下, 可将数据插入数据库中
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)
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) // 插入第二条数据
}
}
}
最终会发现落库成功
sqlite3 BookStore.db
SQLite version 3.35.4 2021-04-02 15:20:15
Enter ".help" for usage hints.
sqlite> select * from Book;
1|Dan Brown|16.96|454|The Da Vinci Code
2|Dan Brown|19.95|510|The Lost Symbol
3|Dan Brown|16.96|454|The Da Vinci Code
4|Dan Brown|19.95|510|The Lost Symbol
5.3.4 更新数据
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)
updateData.setOnClickListener {
val db = dbHelper.writableDatabase
val values = ContentValues()
values.put("price", 10.99)
db.update("Book", values, "name = ?", arrayOf("The Da Vinci Code"))
}
}
}
更新后如下
sqlite3 BookStore.db
SQLite version 3.35.4 2021-04-02 15:20:15
Enter ".help" for usage hints.
sqlite> select * from Book;
1|Dan Brown|10.99|454|The Da Vinci Code
2|Dan Brown|19.95|510|The Lost Symbol
3|Dan Brown|10.99|454|The Da Vinci Code
4|Dan Brown|19.95|510|The Lost Symbol
5.3.5 删除数据
deleteData.setOnClickListener {
val db = dbHelper.writableDatabase
db.delete("Book", "pages > ?", arrayOf("500"))
}
5.3.6 查询数据
下例为获取表中所有行
queryData.setOnClickListener {
val db = dbHelper.writableDatabase
// 查询Book表中所有的数据
val cursor = db.query("Book", null, null, null, null, null, null)
if (cursor.moveToFirst()) {
do {
// 遍历Cursor对象,取出数据并打印
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", "book author is $author")
Log.d("MainActivity", "book pages is $pages")
Log.d("MainActivity", "book price is $price")
} while (cursor.moveToNext())
}
cursor.close()
}
查出后打印日志如下
2022-08-12 15:10:34.264 7666-7666/com.example.databasetest D/MainActivity: book name is The Da Vinci Code
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book author is Dan Brown
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book pages is 454
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book price is 10.99
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book name is The Lost Symbol
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book author is Dan Brown
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book pages is 510
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book price is 19.95
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book name is The Da Vinci Code
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book author is Dan Brown
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book pages is 454
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book price is 10.99
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book name is The Lost Symbol
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book author is Dan Brown
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book pages is 510
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book price is 19.95
5.3.7 直接用 SQL 操作数据库
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
arrayOf("The Da Vinci Code", "Dan Brown", "454", "16.96")
)
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
arrayOf("The Lost Symbol", "Dan Brown", "510", "19.95")
)
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)
5.3.8 使用事务
先开启事务, 然后在try catch 和 finally中捕获exception, 做对应处理
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() // 结束事务
}
}
日志就会打印printStackTrace对应的方法
2022-08-12 15:30:39.075 8005-8005/com.example.databasetest W/System.err: java.lang.NullPointerException
2022-08-12 15:30:39.080 8005-8005/com.example.databasetest W/System.err: at com.example.databasetest.MainActivity.onCreate$lambda-7(MainActivity.kt:71)
2022-08-12 15:30:39.080 8005-8005/com.example.databasetest W/System.err: at com.example.databasetest.MainActivity.$r8$lambda$Ff3cxcjnoRHI0B8DD9nQh8YQ2cY(Unknown Source:0)