Android 持久化技术

持久化技术简介

数据持久化就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或计算机关机 的情况下,这些数据仍然不会丢失。保存在内存中的数据是处于瞬时状态的,而保存在存储设 备中的数据是处于持久状态的。持久化技术提供了一种机制,可以让数据在瞬时状态和持久状态之间进行转换。

Android 系统中主要提供了3种方式用于简单地实现数据持久化功能:文件存储、 SharedPreferences 存储以及数据库存储。

文件存储

文件存储是Android 中最基本的数据存储方式,它不对存储的内容进行任何格式化处理,所有数据都是原封不动地保存到文件当中的,因而它比较适合存储一些简单的文本数据或二进制数据。

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
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()
        }
    }
 
    // 重写了onDestroy()方法,这样就可以保证在Activity 销毁之前一定会调用这个方法。
    override fun onDestroy() {
        super.onDestroy()
        val inputText = editText.text.toString() // 获取了EditText 中输入的内容
        save(inputText)
    }
 
    private fun save(inputText: String) {
        try {
            // Context类中提供了一个openFileOutput()方法,可以用于将数据存储到指定的文件中。
            // 这个方法接收两个参数:第一个参数是文件名,在文件创建的时候使用,注意这里指定的文件名不可以包含路径,因为所有的文件都默认存储到/data/data/<package name>/files/ 目录下;
            // 第二个参数是文件的操作模式,主要有MODE_PRIVATE(覆盖)和MODE_APPEND(追加)两种模式可选,默认是MODE_PRIVATE
            // openFileOutput()方法返回的是一个FileOutputStream对象,得到这个对象之后就可以使用Java 流的方式将数据写入文件中了。
            val output = openFileOutput("data", Context.MODE_PRIVATE)
            val writer = BufferedWriter(OutputStreamWriter(output))
            writer.use {
                it.write(inputText)
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
 
    private fun load(): String {
        val content = StringBuilder()
        try {
            //Context类中还提供了一个openFileInput()方法,用于从文件中读取数据。
            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存储

不同于文件的存储方式,SharedPreferences 是使用键值对的方式来存储数据的。当保存一条数据的时候,需要给这条数据提供一个对应的键,这样在读取数据的时候就可以通过这个键把相应的值取出来。而且SharedPreferences 还支持多种不同的数据类型存储

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
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        saveButton.setOnClickListener {
            // Context类中的getSharedPreferences()方法
            val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
            editor.putString("name", "Tom")
            editor.putInt("age", 28)
            editor.putBoolean("married", false)
            editor.apply()//调用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")
        }
 
    }
}

SQLite 数据库存储

SQLite 是一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,通常只需要几百KB的内存就足够了,因而特别适合在移动设备上使用。SQLite 不仅支持标准的SQL语法,还遵循了数据库的ACID事务,SQLite 又比一般的数据库要简单得多,它甚至不用设置用户名和密码就可以使用。Android 正是把这个功能极为强大的数据库嵌入到了系统当中,使得本地 持久化的功能有了一次质的飞跃。

Android 为了让我们能够更加方便地管理数据库,专门提供了一个SQLiteOpenHelper帮助类,借助这个类可以非常简单地对数据库进行创建和升级。

SQLiteOpenHelper中还有两个非常重要的实例方法:getReadableDatabase()和 getWritableDatabase()。这两个方法都可以创建或打开一个现有的数据库(如果数据库已 存在则直接打开,否则要创建一个新的数据库),并返回一个可对数据库进行读写操作的对 象。不同的是,当数据库不可写入的时候(如磁盘空间已满),getReadableDatabase()方 法返回的对象将以只读的方式打开数据库,而getWritableDatabase()方法则将出现异常。

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//SQLiteOpenHelper中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。
//这个构造方法中接收4个参数:
// 第一个参数是Context,这个没什么好说的,必须有它才能对数据库进行操作;
// 第二个参数是数据库名,创建数据库时使用的就是这里指定的名称;
// 第三个参数允许我们在查询数据的时候返回一个自定义的Cursor ,一般传入null即可;
// 第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。
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)
        Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()
        Log.i("MyDatabaseHelper", "Create succeeded")
    }
 
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        Log.i("MyDatabaseHelper", "onUpgrade")
        /*db.execSQL("drop table if exists Book")
        db.execSQL("drop table if exists Category")
        onCreate(db)*/
        // 每当升级一个数据库版本的时候,onUpgrade()方法里都一定要写一个相应的if判断语句。为什么要这么做呢?
        // 这是为了保证App在跨版本升级的时候,每一次的数据库修改都能被全部执行。
        // 比如用户当前是从第2版升级到第3版,那么只有第二条判断语句会执行,而如果用户是直接从第1版升级到第3版,那么两条判断语句都会执行。
        // 使用这种方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据完全不会丢失。
        if (oldVersion <= 1) {
            db.execSQL(createCategory)
        }
        if (oldVersion <= 2) {
            db.execSQL("alter table Book add column category_id integer")
        }
    }
}
 
 
class MainActivity : AppCompatActivity() {
 
    @SuppressLint("Range")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        val dbHelper = MyDatabaseHelper(this, "BookStore.db", 4)
        createDatabase.setOnClickListener {
            // 构建出SQLiteOpenHelper的实例之后,再调用它的getReadableDatabase()或getWritableDatabase()方法就能够创建数据库了,
            // 数据库文件会存放在/data/data/<package name>/databases/ 目录下。
            // 此时,重写的onCreate()方法也会得到执行,所以通常会在这里处理一些创建表的逻辑。
            dbHelper.writableDatabase
        }
        addData.setOnClickListener {
            // 调用SQLiteOpenHelper的getReadableDatabase()或getWritableDatabase()方法是可以用于创建和升级数据库的,
            // 不仅如此,这两个方法还都会返回一个SQLiteDatabase对象,借助这个对象就可以对数据进行CRUD操作了。
            val db = dbHelper.writableDatabase
            // SQLiteDatabase中提供了一个insert()方法,专门用于添加数据。它接收3个参数:
            // 第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字;
            // 第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般我们用不到这个功能,直接传入null即可;
            // 第三个参数是一个ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据
            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) // 插入第二条数据
            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")
            )
        }
        updateData.setOnClickListener {
            val db = dbHelper.writableDatabase
            // SQLiteDatabase中提供了一个非常好用的update()方法,用于对数据进行更新。这个方法接收4个参数:
            // 第一个参数和insert()方法一样,也是表名,指定更新哪张表里的数据;
            // 第二个参数是ContentValues对象,要把更新数据在这里组装进去;
            // 第三、第四个参数用于约束更新某一行或某几行中的数据,不指定的话默认会更新所有行。
            val values = ContentValues()
            values.put("price", 10.99)
            db.update("Book", values, "name = ?", arrayOf("The Da Vinci Code"))
            db.execSQL("update Book set price = ? where name = ?", arrayOf("10.99", "The Da Vinci Code"))
        }
        deleteData.setOnClickListener {
            val db = dbHelper.writableDatabase
            // SQLiteDatabase中提供了一个delete()方法,专门用于删除数据。这个方法接收3个参数:
            // 第一个参数仍然是表名,这个没什么好说的;
            // 第二、第三个参数用于约束删除某一行或某几行的数据,不指定的话默认会删除所有行。
            db.delete("Book", "pages > ?", arrayOf("500"))
            db.execSQL("delete from Book where pages > ?", arrayOf("500"))
        }
        queryData.setOnClickListener {
            val db = dbHelper.writableDatabase
            // 查询Book表中所有的数据
            // SQLiteDatabase 中还提供了一个query()方法用于对数据进行查询。这个方法的参数非常复杂,最短的一个方法重载也需要传入7个参数。
            // 第一个参数不用说,当然还是表名,表示我们希望从哪张表中查询数据。
            // 第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列。
            // 第三、第四个参数用于约束查询某一行或某几行的数据,不指定则默认查询所有行的数据。
            // 第五个参数用于指定需要去group by 的列,不指定则表示不对查询结果进行group by 操作。
            // 第六个参数用于对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤。
            // 第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式。
            val cursor2 = db.query("Book", null, null, null, null, null, null)
            val cursor = db.rawQuery("select * from Book", null)
            Log.d("MainActivity", "---")
            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", "$name $author $pages $price")
                } while (cursor.moveToNext())
            }
            cursor.close()
        }
 
        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() // 结束事务
            }
        }
    }
 
}

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

 

posted @   草木物语  阅读(281)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2017-01-17 js 日期格式化 日期加1天
点击右上角即可分享
微信分享提示