Android開發筆記——快速入門(從入門SQLlite到Room放肆)2

語言: CN / TW / HK

Android開發筆記——快速入門(從入門SQLlite到Room放肆)2

本文已參與「新人創作禮」活動.一起開啟掘金創作之路。

Android開發筆記——快速入門(從入門SQLlite到Room放肆)2

軟體環境:

配套程式碼獲取地址: Android的SharePreferences

將資料儲存在SharePreferences當中

1、通過context類的getSharedPreference來獲取物件

2、通過Activity類的getPreferences來獲取

向SharePreferences儲存資料

從SharePreferences讀取資料

使用高階函式來優化

SQLite資料庫入門

建立資料庫管理物件

建立資料庫

升級資料庫

資料庫的增刪改查

向資料庫新增資料

修改對應的資料

刪除對應的資料

查詢對應的資料

軟體環境:

  • Jetbrains Toolbox
  • Android Sudio 2021.1.1 Bumblebee
  • JDK 17.0.2

請先參考前一篇文章複習一下Kotlin的一些語法。

大部分內容參考了郭霖先生的《第一行程式碼》,在書的基礎上針對目前的實際情況進行實踐記錄。

配套程式碼獲取地址:

Gitee直接下載,全部開源

Android的SharePreferences

此部分配套程式碼名稱:DataTest2

將資料儲存在SharePreferences中是一個針對多種資料型別的辦法,使用檔案儲存很不方便,不方便的點在於很難對於具有資料型別的結構化資料進行儲存,一個double儲存進去就變成了文字,這樣很不方便,所以我們來介紹如何使用具有資料型別支援的儲存方案。

將資料儲存在SharePreferences當中

將資料儲存到SharePreferences當中就需要先獲取到SharePreferences物件,獲取的辦法有兩種:

1、通過context類的getSharedPreference來獲取物件

@Override public SharedPreferences getSharedPreferences(String name, int mode) {    return mBase.getSharedPreferences(name, mode); }

此方法接收兩個引數,第一個引數用於指定SharePreferences檔案的名稱,如果指定的檔案不存在則會建立一個,一般儲存在:包路徑下的/share_prefs/目錄下,第二個引數用於指定操作模式,目前只有預設的MODE_PRIVATE這一種模式可以選,他表達的意思是隻有當前的應用程式才可以針對他進行讀寫,其他程式一律不可以。

2、通過Activity類的getPreferences來獲取

public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {    return getSharedPreferences(getLocalClassName(), mode); }

此方法只需要接受一個模式引數即可獲取到物件,與上文方法不同的是,這個方法建立的檔名與呼叫它的Actity類名字一模一樣。可以看到此方法實際上是對上文方法的再封裝。

向SharePreferences儲存資料

儲存的過程分為三步:

  • 通過SharePreferences例項物件的edit()方法,可以獲取到一個SharePreferences.Editor物件。
  • SharePreferences.Editor新增資料,針對每一種型別都有其對應的方法可以新增資料,比如新增一個字串就可以用putString()方法。
  • 呼叫apply方法將資料提交,從而完成資料儲存操作。

下面舉個例子:

fun load_p() {    val editor = getSharedPreferences("data",Context.MODE_PRIVATE).edit()    editor.putString("data","Jszszzy_Preference")    editor.apply() }

這裡使用了context的getSharedPreference來獲取editor物件,然後呼叫putSrtring來存入一個字串,他的key值是data當你要獲取的時候就要呼叫”data“了,最後使用apply方法將資料儲存進去。

怎麼用驗證我們儲存的資料呢?

我可以直接到對應目錄下去檢視,當然你也可以使用讀取再把資料讀取出來。

從SharePreferences讀取資料

你已經體驗到了,我們只需要三行程式碼就可以將資料儲存起來,並且不需要考慮太多的效能細節問題。有一個更好的訊息是,讀取其實更簡單。

讀取只有一個步驟:

通過SharePreferences例項物件的get方法分別傳入鍵值和資料型別,就可以得到對應的資料,這裡我們使用一個Textview來獲取對應的資料,然後在程式啟動的時候,再將讀取到的對應的資料再顯示出來。

onCreate讀取資料程式碼如下:

需要注意的是這裡是在前文已經實現了儲存的基礎上,程式已經建立好了對應的檔案,然後實現的讀取。

override fun onCreate(savedInstanceState: Bundle?) {    super.onCreate(savedInstanceState)    binding = ActivityMainBinding.inflate(layoutInflater)    setContentView(binding.root) ​    load_p()?.let {        binding.datasave.setText(it)   } ​    Toast.makeText(this,"Reload data succeed",Toast.LENGTH_SHORT).show() }

load的程式碼如下:

fun load_p():String? {    val loader = getSharedPreferences("data",Context.MODE_PRIVATE)    return loader.getString("data","Cant get data") }

儲存的程式碼也很簡單,我們只需要在Activity完全消失的時候,呼叫儲存把資料儲存起來,在Activity的回撥函式中來儲存資料:

override fun onStop() {    save_p()    super.onStop() }

因為要儲存顯示的資料,所以儲存的函式也要修改:

fun save_p() {    val editor = getSharedPreferences("data",Context.MODE_PRIVATE).edit()    editor.putString("data",binding.datasave.text.toString())    editor.apply() }

效果如下,開啟應用程式讀取到資料如下:

1-1

修改後儲存,讀取到的資料如下:

1-2

使用高階函式來優化

儘管使用SharePreferences已經足夠簡單了,但實際上還是有一些重複的書寫,我們這裡就使用高階函式來優化一下。

直接舉一個例子:

fun SharedPreferences.open(block:SharedPreferences.Editor.()->Unit){    val editor = edit()    editor.block()//因為這個擴充套件函式實際上提供的是SharedPreferences上下文,二傳入的引數提供的是SharedPreferences.Editor的上下文,需要加入字首    editor.apply() }

以上一段程式碼看起來可能有點複雜,實際上沒有那麼複雜,我們實際上是通過擴充套件函式為SharedPreferences類添加了一個open函式,這個open函式接受一個以SharedPreferences.Editor為上下文的函式引數他的名為block。

我們為什麼要建立這樣一個函式呢?

實際上有兩個目的,一個是省略重複的獲取物件和提交資料的時候呼叫apply函式,二是將函式作為引數可以自定義要實現的資料儲存操作,並且能提供對應的上下文。

如何使用這個函式呢?

我們可以直接呼叫getSharedPreferences方法獲取到這個物件,然後呼叫open函式傳入需要進行的操作即可:

完整的程式碼如下

getSharedPreferences("data",Context.MODE_PRIVATE).open { putString("data",binding.datasave.text.toString()) }

和上文實現相同的操作相比:

val editor = getSharedPreferences("data",Context.MODE_PRIVATE).edit() editor.putString("data",binding.datasave.text.toString()) editor.apply()

省略了不少,尤其當需要新增的資料增多的時候比較方便。

當然為了避免因為kotlin的匿名類產生的開銷,我們使用inline關鍵字來增強一下避免因為匿名類產生的開銷。

SQLite資料庫入門

此部分配套程式碼名稱:DataTest3

前邊兩種方式儲存的實際上都是一些簡單的資料,當我們的資料關係複雜起來的時候,通過前兩種辦法就變的捉襟見肘了。

SQLite是一個輕量級的關係資料庫,還遵循了資料庫的ACID事務,所以只要你以前使用過其他關係型資料庫,就可以很快的上手SQLite,同時他不僅在大小上有很大的優化,使用起來也是更加的方便和快捷。

建立資料庫管理物件

Android為了方便管資料庫,專門提供了一個抽象類來供你使用,建立資料庫的時候只需要單獨寫一個類然後繼承對應的SQLiteOpenHelper類即可,其中包含了兩個抽象方法需要你自己去實現,一個是建立的時候呼叫的override fun onCreate(db: SQLiteDatabase?) 他傳遞進來一個引數指明當前呼叫的具體哪個資料庫,如果還沒有資料庫的表就可以通過SQLiteDatabase的方法執行SQL語句來建立表。

同時還需要重寫的方法還有onUpgrade,他是當資料庫需要升級的時候來呼叫的,具體等到資料庫升級再詳細討論。

一般使用SQLiteOpenHelper的時候都會使用它較少引數的構造方法,這個構造方法如下:

public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, @Nullable CursorFactory factory, int version) { this(context, name, factory, version, null); }

第一個引數context用來定位資料庫的儲存位置,包名等等....,沒什麼好說的,很多函式都需要提供上下文引數,來實現一些資訊獲取或者Context的內建方法獲取。

第二個引數是資料庫的名稱。

第三個引數是允許我們在查詢資料的時候返回一個自定義的Cursor,至於這是什麼我們晚點再來討論。

第四個引數是顯示當前資料的版本號,可用於對資料庫進行升級操作。

建立資料庫

需要注意的是上邊只是建立了一個數據庫助手物件,並沒有將資料庫本身創建出來,當我們第一次去呼叫getWritableDatabase方法的時候才是真正的將資料庫建立了出來,我們可以通過閱讀原始碼來知道這一關鍵點。

``` Create and/or open a database that will be used for reading and writing. The first time this is called, the database will be opened and onCreate, onUpgrade and/or onOpen will be called. Once opened successfully, the database is cached, so you can call this method every time you need to write to the database. (Make sure to call close when you no longer need the database.) Errors such as bad permissions or a full disk may cause this method to fail, but future attempts may succeed if the problem is fixed. Database upgrade may take a long time, you should not call this method from the application main thread, including from ContentProvider.onCreate().

Returns: a read/write database object valid until close is called Throws: SQLiteException – if the database cannot be opened for writing

public SQLiteDatabase getWritableDatabase() { synchronized (this) { return getDatabaseLocked(true); } } ```

原始碼的註釋說的很清楚,我只闡述以下幾個要點,讓你對這個方法有更深的瞭解。

當第一次這個方法被呼叫的時候,會觸發以下生命週期函式:

  • onCreate
  • onUpgrade and onOpen

這個方法的實現不僅包含了在第一次呼叫的時候建立資料庫,同時也包含了在不同生命週期去呼叫onCreate、onDowngrade(非抽象)、onUpgrade方法、onOpen方法(非抽象),這也是為什麼只有第一次呼叫這個方法的時候,資料庫內的表和資料才會被建立。

資料庫的升級過程是個很慢的過程,所以你不應該在某個元件Act或者Fragment的onCreate方法去呼叫getWritableDatabase或者getReadableDatabase。

接下來我們直觀的體驗一下如何建立一個數據庫。

這裡我們要建立一個BookStore.db資料庫並且在資料庫中建立一張表,上文說了,只有資料庫第一次

被建立的時候會呼叫onCreate方法,所以我們初始化資料應該放在onCreate裡面。

建立一個自己的SQLiteOpenHelper類,並重寫初始化方法:

``` 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,"+
        "price real,"+
        "pages integer,"+
        "name text)"

override fun onCreate(db: SQLiteDatabase?) {
    db?.execSQL(createBook)
    Toast.makeText(context,"Create succeed",Toast.LENGTH_SHORT).show()
}

override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {

}

} ```

在類裡面我們聲明瞭一個成員字串變數,在字串中寫下SQL的語句,SQLite所包含的資料類並不多,具體如下:

  • integer 整型
  • real 浮點型別
  • text 文字型別
  • blob 二進位制型別

其中語句中的關鍵字primary key 、autoincrement分別代表,設定為主鍵、內容自動增長。

最後呼叫onCreate方法中的資料庫物件,執行SQL語句,完成建表。

那麼如何檢視建立好的資料庫的內容呢?我們可以藉助資料庫檢視器,下載檢視器

從手機中把資料庫的內容儲存到本地來。

1-5

資料庫儲存位置如下:

1-6

在程式的包中:

1-7

右鍵另存為,將資料庫儲存到PC本地,你可以使用外掛開啟:

1-8

效果如下:

1-9

或者使用剛下載的瀏覽器開啟:

1-10

升級資料庫

在SQLiteOpenHelper中實際上還包含著一個onUpdate方法,他就是剛才說的針對資料庫升級的方法。

目前的資料庫應將建立完成了,但是我們想增加一個Category表用於存放書的各種分類資訊怎麼辦呢?

建立表的語句很簡單:

create table Category(id interger primary autoincrement , category_name text , category_code integer)

接下來我們將這條建表語句新增到MyDatabaseHelper中,程式碼如下所示:

``` private var tablename:String=""

private val front_test= "drop table if exists ${tablename}"

private val createBook = "create table Book ("+ "id integer primary key autoincrement,"+ "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 succeed",Toast.LENGTH_SHORT).show() } ```

我們在oncreate方法中呼叫了db來執行建立表的語句,最後提示表建立成功。

可是這裡就存在一個問題,onCreate方法實際上只會在資料庫建立的時候執行,也就是說,目前想去更新資料庫的內容是無法通過Oncreate方法來執行的,這該怎麼辦呢?這就需要呼叫OnUpdate方法來使用了,在改變版本號的情況下,資料庫會在建立的時候執行OnUpdate方法,我們只需要在建立管理類的時候傳入一個比當前資料庫版本更大的數字就可以了,程式碼如下:

val dbHelper = MyDatabaseHelper(this,"BookStore.db",2)

為了讓其滿足一定的魯棒性,也就是說不管資料庫存在不存在,是否需要升級,我們保留在Oncreate方法中建立資料庫的操作,我們可以在OnUpdate方法中呼叫OnCreate。

最後方法實現結果如下:

override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { tablename="Book" db?.execSQL(front_test) tablename="Category" db?.execSQL(front_test) onCreate(db) }

重新啟動應用程式,效果如下:

image-20220616100553867

需要注意的是,你在使用Android的真機除錯的時候,實際上每次系統都會給你重新安裝程式,也就是說,它會自動執行OncCreate方法重新建立一個數據庫。

資料庫的增刪改查

有時候你可能還沒學習過資料庫的一些基礎內容,不過沒關係,Android幫我們實現了一套不需要SQL語句就能操作資料庫的API,我們對於資料庫的操作也就是SQL實際上功能就分為四種:CRUD。

在前面我們使用getWritableDatabase方法來建立和修改資料庫,實際上這個方法返回的是一個SQLiteDatabase物件,我們藉助這個物件就是可實現CRUD操作了。

向資料庫新增資料

SQLiteDatabase物件下文簡稱:SQL物件,包含一個insert方法:

public long insert(String table, String nullColumnHack, ContentValues values)

第一個引數用來指定是哪一個table,第二個是用於在未指定新增資料的情況下給某些可以為空的列自動賦值為NULL,這裡不需要,所以我們把它設定為NULL。第三個引數很直白,就是傳入一個需要插入的值。需要注意的是,這裡使用的是ContentValues物件。這個物件提供了一系列的方法來將需要的資料新增進去,使用方法如下:

我們通過來獲取對應的SQL物件:

val sql = dbHelper.writableDatabase

通過這個SQL物件來實現新增資料,再新增資料之前,我們先要把對應的資料嵌入到ContentValues物件當中,通過ContentValues物件指定將資料插入到哪一列當中,當然資料的插入要符合對應的資料型別否則會報錯。

最終插入的程式碼如下所示:

binding.button.setOnClickListener { val sql = dbHelper.writableDatabase val values = ContentValues().apply { put("name","The Da Vinci Code") put("pages",188) put("price",16.96) } sql.insert("Book",null,values) }

可以看到我們把對應的插入函式繫結到了按鈕上,只要按下按鈕就會執行對應的插入程式。

插入的結果如下:

image-20220616201956921

這裡有一個需要特殊注意的地方是,我們在本地瀏覽資料庫內容的時候,需要將裝置的資訊同步重新整理下載以後再使用SQLite瀏覽器開啟,並且先關閉原來正在瀏覽的資料庫後刪除本地原來存在的資料庫內容,防止因為正在訪問而導致的本低內容更新失敗,我並不推薦使用外掛因為bug確實存在。

修改對應的資料

對應有insert方法也有update方法,這個方法接收四個引數,第1個引數同樣也是制定了操作的表名,指定更新哪張表的資料。第二個引數是ContentValues,就是需要更新的資料,當然也是通過ContentValues來構造對應的資料,幷包裝起來,第三個、第四個引數用於約束某一行或者某幾行中的資料進行更新,沒有約束的話預設更新所有資料。

舉個例子:

對於引數的使用直接說起來,還是很抽象,我們不妨結合例子來說:

binding.update.setOnClickListener { //Data_update_method val sql = dbHelper.writableDatabase val values = ContentValues() values.put("price",10.99) sql.update("Book",values,"name=?", arrayOf("The Da Vinci Code")) }

第一第二個引數就不再重複了,我們直接來說第三個引數,第三個引數實際上就是where字句,在這裡傳入的字串自動和where拼接起來,實現篩選指定行的目的,比如這裡就相當於:

where name=?

而這裡的?就是一個佔位符號,當你使用第四個引數的時候可以將一個字元陣列的全部內容傳遞過來放到where單中,所以我在第四個資料當中使用了 arrayof來建立一個數組,當然為什麼要用陣列呢?實際上呢一個表示式裡面可以包含多個佔位符,所以這裡使用陣列來滿足多個?的情況。

arrayOf("The Da Vinci Code")

操作結果如下:

可以看到所有的價格都改為了10.99

image-20220616224216557

刪除對應的資料

刪除對應的資料更簡單,引數和上邊基本一樣:

binding.delete.setOnClickListener { //Data_update_delete val sql = dbHelper.writableDatabase sql.delete("Book","name = ?", arrayOf("The Da Vinci Code")) }

最後刪除完成以後效果如下:

可以看到所有內容都被刪除了。

image-20220616225453207

查詢對應的資料

SQL實際上是一種結構化的查詢語言,他的大部分功能體現在“查”這個字上,具體的SQL操作,可以參考我的資料庫專欄。

query相對來說很複雜,一共有七個引數,引數的含義分別如下:

image-20220616230920144

如果你不理解,沒關係,請先參考我的資料庫專欄,裡面有詳細介紹SQL的內容。

我在這裡就很簡單的舉個例子:

binding.query.setOnClickListener { //Data_update_query val sql = dbHelper.writableDatabase val result =sql.query("Book", null,null,null,null,null,null) binding.datasave.setText( result.columnCount.toString()) result.close() }

我們新增一個查詢按鈕,這句查詢語句的意思實際上就是獲取當前表中有多少行,是不是很好理解?

其等價於SQL為

select * from Book

最後通過columnCount成員獲取到對應的行數。

\