数据存储

数据存储

文件存储

存储路径:/data/data/com.example.filepersistencetest/files/目录

将数据存储到文件中

1
2
3
4
5
6
7
8
9
10
11
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()
}
}

读取文件中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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()
}

SharedPreferences存储

存储路径:/data/data/<package name>/shared_prefs/目录下,xml文件形式

SharedPreferences 是 Android 中用于存储简单键值对数据的轻量级存储机制。它提供了一种简单的方式,用于在应用程序中存储少量的基本数据类型的数据,例如布尔值、整数、浮点数、字符串等。SharedPreferences 的数据存储在应用的私有文件系统中,其他应用无法直接访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 获取 SharedPreferences 对象,第一个参数是文件名,第二个参数是模式(通常使用 MODE_PRIVATE)
SharedPreferences preferences = getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
// 获取 SharedPreferences.Editor 对象用于写入数据
SharedPreferences.Editor editor = preferences.edit();

// 写入数据
editor.putString("username", "JohnDoe");
editor.putInt("score", 1000);
editor.putBoolean("isPremium", true);


// 提交更改
editor.apply();

// 读取数据
SharedPreferences preferences = getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
String username = preferences.getString("username", "");
int score = preferences.getInt("score", 0);
boolean isPremium = preferences.getBoolean("isPremium", false);

可以使用kotlin中的高阶函数来简化实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun SharedPreferences.open(block: SharedPreferences.Editor.() -> Unit) {
//由于open函数内拥有SharedPreferences的上下文,因此这里可以直接调用edit()方法来获取SharedPreferences.Editor对象
val editor = edit()
//对函数类型参数进行调用
editor.block()
//提交数据
editor.apply()
}

getSharedPreferences("data", Context.MODE_PRIVATE).open {
//Lambda表达式拥有的是SharedPreferences.Editor的上下文环境
//因此这里可以直接调用相应的put方法来添加数据。
//最后我们也不再需要调用apply()方法来提交数据了,因为open函数会自动完成提交操作。
putString("name", "Tom")
putInt("age", 28)
putBoolean("married", false)
}

其实Google提供的KTX扩展库中已经包含了上述SharedPreferences的简化用法,这个扩展库会在Android Studio创建项目的时候自动引入build.gradle的dependencies中

1
2
3
4
5
getSharedPreferences("data", Context.MODE_PRIVATE).edit {
putString("name", "Tom")
putInt("age", 28)
putBoolean("married", false)
}

SQLite数据库存储

前面我们所学的文件存储和SharedPreferences存储毕竟只适用于保存一些简单的数据和键值对,当需要存储大量复杂的关系型数据的时候,你就会发现以上两种存储方式很难应付得了。

数据库文件会存放在/data/data/<package name>/databases/目录下

Android为了让我们能够更加方便地管理数据库,专门提供了一个SQLiteOpenHelper帮助类,是抽象类,我们想要使用,就要创建一个自己的帮助类去继承它。并重写两个方法onCreate()onUpgrade(),实现创建和升级数据库的逻辑。

有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可

1
2
3
4
5
SQLiteOpenHelper(context, name, null, DB_VERSION);
第一个参数是Context,这个没什么好说的,必须有它才能对数据库进行操作;
第二个参数是数据库名,创建数据库时使用的就是这里指定的名称;
第三个参数允许我们在查询数据的时候返回一个自定义的Cursor,一般传入null即可;
第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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) {
}

}

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<Button
android:id="@+id/createDatabase"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Create Database"
/>

</LinearLayout>

MainActivity

1
2
3
4
5
6
7
8
9
10
11
12
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 1)//版本号指定为1
createDatabase.setOnClickListener {
dbHelper.writableDatabase
}
}

}

这样当第一次点击“Create Database”按钮时,就会检测到当前程序中并没有BookStore.db这个数据库,于是会创建该数据库并调用MyDatabaseHelper中的onCreate()方法,再次点击“Create Database”按钮时,会发现此时已经存在BookStore.db数据库了,因此不会再创建一次。

升级数据库方法:

当在上面的例子中再添加一个数据库表的时候,并不会被创建,原因在上面黄色背景文字。解决方法就是用到了升级数据库的那个方法onUpgrade

1
2
3
4
5
6
7
8
9
10
11
12
class MyDatabaseHelper(val context: Context, name: String, version: Int):
SQLiteOpenHelper(context, name, null, version) {
...
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)
}

}
onCreate()`中的版本号改为2:`val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)

表示我们对数据库进行升级了。

上面这种方法是非常粗暴的,不建议在开发中这么编写,因为:比如你编写的某个应用已经成功上线了,并且还拥有了不错的下载量。现在由于添加了新功能,数据库需要一起升级,结果用户更新了这个版本之后却发现以前程序中存储的本地数据全部丢失了!那么很遗憾,你的用户群体可能已经流失一大半了。

升级数据库的最佳写法:

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
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)
}

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
if (oldVersion <= 1) {
db.execSQL(createCategory)
}
}

}

这样当用户直接安装第2版的程序时,就会进入onCreate()方法,将两张表一起创建。而当用户使用第2版的程序覆盖安装第1版的程序时,就会进入升级数据库的操作中,此时由于Book表已经存在了,因此只需要创建一张Category表即可。

但是没过多久,新的需求又来了,这次要给Book表和Category表之间建立关联,需要在Book表中添加一个category_id字段。再次修改MyDatabaseHelper中的代码,如下所示:

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
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," +
"category_id integer)"

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)
}

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")
}
}

}

每当升级一个数据库版本的时候,onUpgrade()方法里都一定要写一个相应的if判断语句。为什么要这么做呢?这是为了保证App在跨版本升级的时候,每一次的数据库修改都能被全部执行。比如用户当前是从第2版升级到第3版,那么只有第二条判断语句会执行,而如果用户是直接从第1版升级到第3版,那么两条判断语句都会执行。使用这种方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据完全不会丢失。

添加数据、修改数据、删除数据、查询数据:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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) // 插入第二条数据
}

//更新数据
updateData.setOnClickListener {
val db = dbHelper.writableDatabase
val values = ContentValues()
values.put("price", 10.99)
db.update("Book", values, "name = ?", arrayOf("The Da Vinci Code"))
}

//删除数据
deleteData.setOnClickListener {
val db = dbHelper.writableDatabase
db.delete("Book", "pages > ?", arrayOf("500"))
}

//查询数据
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()
}
}

}

query()方法参数的详细解释:

img

ContentValues用法也可以被简化

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
34
35
36
37
38
39
40
41
42
43
44
45
46
//Any?是任何类型,vararg:可变参数列表,Pair:就是A to B 类型
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
}

//方法2
fun cvOf(vararg pairs: Pair<String, Any?>) = ContentValues().apply {
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)
}
}
}

//有了这个cvOf()方法之后,我们使用ContentValues时就会变得更加简单了,比如向数据库中插入一条数据就可以这样写:
val values = cvOf("name" to "Game of Thrones", "author" to "George Martin",
"pages" to 720, "price" to 20.85)
db.insert("Book", null, values)

当然,虽然我们编写了一个非常好用的cvOf()方法,KTX库中也提供了一个具有同样功能的contentValuesOf()方法

1
2
3
val values = contentValuesOf("name" to "Game of Thrones", "author" to "George Martin",
"pages" to 720, "price" to 20.85)
db.insert("Book", null, values)

使用事务

SQLite数据库是支持事务的,事务的特性可以保证让一系列的操作要么全部完成,要么一个都不会完成

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
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)
...
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() // 结束事务
}
}
}

}

我们在删除旧数据的操作完成后手动抛出了一个NullPointerException,这样添加新数据的代码就执行不到了。不过由于事务的存在,中途出现异常会导致事务的失败,此时旧数据应该是删除不掉的。

将手动抛出异常的那行代码删除并重新运行程序,此时点击一下“Replace Data”按钮,就会将Book表中的数据替换成新数据了,你可以再使用“Query Data”按钮来验证一次。

使用SQL操作数据库

直接使用SQL来操作数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//添加数据
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)

数据存储
http://example.com/2024/02/25/数据存储/
作者
zlw
发布于
2024年2月25日
许可协议