Android利用sqlite實現頁面搜索記錄功能

前言

數據庫作爲軟件開發過程中必不可少的一門語言,一直深受廣大後端程序員的關注和喜愛,作爲移動開發來講,雖然有着更多的其他存儲方式可以使用,但是其也是並不可少的存在,特別是在某些特殊的應用場景下,因此掌握數據庫的使用在移動開發也格外重要。本次我就通過一個存儲搜索記錄的例子,來一起鞏固總結一下數據庫在Android上的使用。

功能要點

相信大家應該在不少的App上見到過搜索記錄這個功能,很簡單的一個小功能,本次博客的內容,就是用數據庫實現這個功能,那麼要實現這個功能,需要滿足那些條件呢?那肯定是針對搜索記錄的增、刪、查了

  1. 每次搜索時將搜索的關鍵字增加到我們的數據庫裏
  2. 可以刪除某一或者全部搜索記錄
  3. 查詢某個頁面對應的搜索記錄

對此,我設計的表含有以下三個字段,來支持搜索記錄功能的實現。

屬性       類型  含義
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))
    }

到這我們本次例子所需要的基本功能增、刪、查就都有了,不過從用戶的使用角度來看本次例子的功能並沒有完善,還缺少用戶體驗,比如

  1. 搜索同一關鍵詞多次時關鍵詞也被多次存儲記錄
  2. 若記錄存在相同關鍵詞,當前關鍵詞位置應當顯示在記錄最前面
  3. 搜索記錄條數未設限制
  4. 最舊的記錄應該刪掉

思考一番能發現,其實上面這四個問題都是應該在進行插入操作時做處理的。第一二個問題,都是一種解決辦法,那就是在將關鍵詞插入數據庫的時候,先查詢數據庫有沒有存儲同樣的關鍵詞,有就刪除再插入。第三四個也可以看做一個問題,對此需要額外設置一個記錄最大條數的值,並且在當插入新的關鍵詞時,如果當前記錄數已超過最大值,則刪掉最舊的一條記錄,在執行插入操作。

針對第一二個問題,刪除單個關鍵詞的方法已經有了,還缺一個查詢單個關鍵詞的方法,再已有查詢方法的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上去了,有興趣的小夥伴可以克隆下來看看。戳我

本人不才,例子就寫到這裏了,都是一些比較基礎的東西,數據庫的東西還有得研究,寫的有什麼不正確的地方還希望大家能指出來,與大家共勉~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章