前言
数据库作为软件开发过程中必不可少的一门语言,一直深受广大后端程序员的关注和喜爱,作为移动开发来讲,虽然有着更多的其他存储方式可以使用,但是其也是并不可少的存在,特别是在某些特殊的应用场景下,因此掌握数据库的使用在移动开发也格外重要。本次我就通过一个存储搜索记录的例子,来一起巩固总结一下数据库在Android上的使用。
功能要点
相信大家应该在不少的App上见到过搜索记录这个功能,很简单的一个小功能,本次博客的内容,就是用数据库实现这个功能,那么要实现这个功能,需要满足那些条件呢?那肯定是针对搜索记录的增、删、查了
- 每次搜索时将搜索的关键字增加到我们的数据库里
- 可以删除某一或者全部搜索记录
- 查询某个页面对应的搜索记录
对此,我设计的表含有以下三个字段,来支持搜索记录功能的实现。
属性 | 类型 | 含义 |
id | INTEGER(主键) | id |
origin | TEXT | 来源(Activity名称) |
word | TEXT | 关键词 |
界面设计
因为在一个App里可能有多个页面需要搜索功能,所以我们存储不同页面的搜索记录,对此我设计了两个页面,主页面和搜索页面,主界面含有两个入口,并根据上面三点需求设计了搜索页。
代码编写
Android自带的数据库为SQLite,要想在Android上使用它,我们需要通过继承官方的SQLiteOpenHelper来实现数据库工具类。
创建ShDbHelper类继承SQLiteOpenHelper并重写onCreate()和onUpgrade()方法,这里我声明了一些常量方便后面使用。
const val DB_VERSION = 1
const val DB_NAME = "my.db"
const val TABLE_NAME = "searchHistory"
const val ID = BaseColumns._ID
const val WORD = "word"
const val ORIGIN = "origin"
class ShDbHelper : SQLiteOpenHelper {
constructor(context: Context) : super(context, DB_NAME, null, DB_VERSION)
override fun onCreate(db: SQLiteDatabase) {
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
}
}
建表
我们要存储记录就要得先有个表,在onCreate()方法里执行sql语句创建。Android提供了两类方法供我们执行sql操作。第一类我称为全sql方法,第二类为半sql方法。全sql方法的和半sql方法的区别就在sql语句上,前者需要编写整串sql语句,对sql语句的熟悉程度要求较高,主要方法有execSQL()、rawQuery()。后者是Android将sql拆分,封装成了一些参数,只需编写部分sql语句。如果对sql语句非常熟悉,可以使用前者,如果怕拼写错误,确保安全的可以使用后者。
- execSQL():用于执行insert、delete、update和CREATE TABLE之类有更改行为的SQL语句。
- rawQuery():用于执行select语句。
本例大部分使用后者进行编写。
//创建表sql语句
val create_sql = "CREATE TABLE $TABLE_NAME ($ID INTEGER PRIMARY KEY AUTOINCREMENT,$ORIGIN TEXT NOT NULL,$WORD TEXT NOT NULL)"
//执行一个不是SELECT的SQL语句或任何其他返回数据的SQL语句。它无法返回任何数据
db.execSQL(create_sql)
插入
存储搜索记录就要往表里执行插入操作,通过一个可写的SQLiteDatabase对象执行insert插入方法,该方法有三个参数,第一个参数为要插入的表的名称,第二个传null就行,第三个参数是一个ContentValues对象,官方提供了ContentValues对象供我们将需要插入的数据封装起来,就像使用map一样。
/*
* 插入搜索记录
* */
fun insert(origin: String, word: String): Long {
val value = ContentValues()
value.put(ORIGIN, origin)
value.put(WORD, word)
return writableDatabase.insert(TABLE_NAME, null, value)
}
查询
SQLiteDatabase提供了query()方法供我们对数据库进行查询操作,具体的参数含义看如下代码注释。
/*
* 查询某页的所有搜索记录
* */
fun query(origin: String): ArrayList<String> {
//需要查询的参数
val columns = arrayOf(WORD)
//查询条件
val selection = "$ORIGIN =?"
//查询条件对应的值
val selectionArgs = arrayOf(origin)
//按ID倒序排列
val orderBy = "$ID DESC"
val cursor = readableDatabase.query(TABLE_NAME, columns, selection, selectionArgs, null, null, orderBy)
if (cursor.count != 0) {
val list = arrayListOf<String>()
while (cursor.moveToNext()) {
list.add(cursor.getString(0))
}
cursor.close()
return list
}
return arrayListOf()
}
如果使用rawQuery(),那么上面这些参数对应的sql语句为
“SELECT word FROM searchHistory WHERE origin =? ORDER BY _id DESC”
这两个方法中sql语句的?代表占位符,具体的值为后面传递的参数。这个值其实也可以直接写进sql,后面那个参数传null。
为了方便使用,我将查询出来的数据封装到了一个列表返回。
删除
SQLiteDatabase提供了delete()方法供我们对数据库进行删除操作。参数和查询类似,含义看如下注释。
/*
* 删除某页某条搜索记录
* */
fun delete(origin: String, word: String): Int {
//表名称、删除条件、条件参数
return writableDatabase.delete(TABLE_NAME, "$ORIGIN =? and $WORD =?", arrayOf(origin, word))
}
当然,有时我们需要删除某个页面所有的记录,对此只需去掉word条件就行。
/*
* 删除某页所有搜索记录
* */
fun deleteAll(origin: String): Int {
return writableDatabase.delete(TABLE_NAME, "$ORIGIN =?", arrayOf(origin))
}
到这我们本次例子所需要的基本功能增、删、查就都有了,不过从用户的使用角度来看本次例子的功能并没有完善,还缺少用户体验,比如
- 搜索同一关键词多次时关键词也被多次存储记录
- 若记录存在相同关键词,当前关键词位置应当显示在记录最前面
- 搜索记录条数未设限制
- 最旧的记录应该删掉
思考一番能发现,其实上面这四个问题都是应该在进行插入操作时做处理的。第一二个问题,都是一种解决办法,那就是在将关键词插入数据库的时候,先查询数据库有没有存储同样的关键词,有就删除再插入。第三四个也可以看做一个问题,对此需要额外设置一个记录最大条数的值,并且在当插入新的关键词时,如果当前记录数已超过最大值,则删掉最旧的一条记录,在执行插入操作。
针对第一二个问题,删除单个关键词的方法已经有了,还缺一个查询单个关键词的方法,再已有查询方法的sql语句上增加一个条件即可。
/*
* 查询某页某条搜索记录
* */
private fun query(origin: String, word: String): Int {
val projection = arrayOf(WORD)
val selection = "$ORIGIN =? and $WORD =?"
val selectionArgs = arrayOf(origin, word)
val cursor = readableDatabase.query(TABLE_NAME, projection, selection, selectionArgs, null, null, null)
return cursor.count
}
对第三四个问题,出了设置最大值,也是先查后删,只不过查询条件变为查询最旧的一条记录的ID,然后在根据此ID删掉对应数据。
private fun queryOldest(origin: String): String {
val projection = arrayOf(ID)
val selection = "$ORIGIN =?"
val selectionArgs = arrayOf(origin)
val orderBy = "$ID ASC"
val cursor = readableDatabase.query(TABLE_NAME, projection, selection, selectionArgs, null, null, orderBy)
cursor.moveToFirst()
val id = cursor.getString(0)
cursor.close()
return id
}
private fun deleteOldest(origin: String) {
val id = queryOldest(origin)
val i = writableDatabase.delete(TABLE_NAME, "$ID =?", arrayOf(id))
if (i > 0)
Log.i("kkk", "已删除最旧的记录")
}
还有一种简单的写法,将查询删除总结到一句sql中。
private fun deleteOldest(origin: String) {
val i = writableDatabase.delete(TABLE_NAME, "$ID =(select min($ID) from $TABLE_NAME where $ORIGIN =?)", arrayOf(origin))
if (i > 0)
Log.i("kkk", "已删除最旧的记录")
}
用全sql写的话就是
DELETE FROM $TABLE_NAME WHERE $ID = (SELECT MIN($ID) FROM $TABLE_NAME WHERE $ORIGIN = $origin)
然后,插入的方法变成这样
/*
* 插入搜索记录
* */
fun insert(origin: String, word: String): Long {
//如若已有记录,删除并从新插入
if (query(origin, word) > 0)
delete(origin, word)
//新增超过每页记录最大存储数时删除最旧当页最旧数据
val size = query(origin).size
if (size >= MAX_COUNT)
deleteOldest(origin)
val value = ContentValues()
value.put(ORIGIN, origin)
value.put(WORD, word)
return writableDatabase.insert(TABLE_NAME, null, value)
}
剩下的跟界面相关的代码我就不贴了,都传到github上去了,有兴趣的小伙伴可以克隆下来看看。戳我
本人不才,例子就写到这里了,都是一些比较基础的东西,数据库的东西还有得研究,写的有什么不正确的地方还希望大家能指出来,与大家共勉~