Android 中的 SQLite 資料庫支援
開袋即食
我們大多數人都至少熟悉某些 Core Data 提供給我們的直接可用的持久化特性。然而不幸的是,它們中的很多在 Android 平臺上並不是自動化的。 例如,Core Data 抽象出大部分資料庫的 SQL 語法和資料庫標準,這些語法和標準都是資料庫工程師們每天面對的問題。 因為 Android 僅提供一個簡單的 SQLite 客戶端,所以你扔需要寫 SQL 並且確保你的資料庫表被適當的標準化了。
Core Data 允許我們在物件方面考慮。 實際上,它能自動處理列集和散集物件。 得益於其提供了記錄層的快取,所以它在移動裝置上的效能很好。每次從儲存中請求同樣的資料段時,它不用再建立另外一個的物件例項。當觀察一個物件的變化時,我們甚至不需要重新整理那個被觀察物件就能做到。
Android 上可不是這種情況。你要完全負責將物件寫入資料庫或者從中將它們讀取。這意味著你要自己實現物件快取(如果需要的話),管理物件例項化,以及手動對任何已存在物件進行是否需要變更的檢測。
在 Android 中,你需要注意版本特定的功能。不同版本的 Android 使用不同的 SQLite 實現。這意味著同一個資料庫指令可能在其他平臺版本上產生完全不同的結果。根據你執行的 SQLite 版本不同,一個查詢語句也會執行得各不相同。
填缺補空
許多Android開發者來自企業。許多年來,物件關係型對映庫一直在伺服器平臺上用於減輕與資料庫的互動的難度。然而,這些庫因為效能問題而導致無法在移動端直接使用。意識到這一點,一些開發者組織起來製作了面向移動端的 ORM 庫來解決這一問題。
在 Android 上給 SQLite 新增 ORM 支援的其中一個使用廣泛的的方法是 OrmLite。OrmLite 提供對持久化物件的自動列集和散集化。它不用寫大量的 SQL,並且提供程式介面來查詢,更新,刪除物件。在 ORM 中另一個競爭者是 greenDAO。它提供許多與 OrmLite 類似的功能,但是承諾具有更好的效能(根據它的網站上說),例如基於註解的設定。
對第三方庫通常都是抱怨都是加到專案中以後造成了額外的複雜度並且使得效能臃腫。有開發者覺得這實在很蛋疼,於是他寫了一個叫 Cupboard 的輕量級 Android SQLite 框架的封裝。它聲稱的目標是在不使用 ContentValues 和不解析 Cursors 的情況下為 Java 物件的提供持久化儲存,它將會簡單輕量,並整合時不會對核心 Android 類造成任何影響。你仍將需要管理資料庫的建立,但是查詢物件會變得簡單很多。
還有開發者決定完全廢棄 SQLite 並且建立了 Perst。它從開始到介面都是用面嚮物件語言設計的。它善於列集和散集物件,並且在效能跑分中表現很好。這種解決方法確實是完全替換了部分 Android 框架,但是也存在一定風險,因為你可能很難再在以後將其替換成不同方案了。
有這些甚至是更多可用的選擇,為什麼大家還都選擇在原始的 Android 資料庫框架下開發呢?這麼說吧,框架和封裝相較於它們所解決的問題,有時候能帶來更多麻煩。例如,在一個專案中,我們同時寫入資料庫且例項化很多物件,這就會導致我們的 ORM 庫慢得像爬一樣。因為這個庫不是設計用來讓我們以這樣的方式使用的。
在評估框架和庫的時候,檢檢視看有沒有使用 Java 的反射機制。反射機制在 Java 中代價相對較大,因此要謹慎使用。另外,如果你的專案是 Ice Cream Sandwich 前的版本,還要看看你的庫是否在使用 Java 最近解決的一個 bug,它會導致在執行時因為註解而導致的效能下降。
最後,還要評估新增框架會不會顯著的增加專案的複雜度。如果你與其他開發者共同開發,記住,他們也必須花時間來學習該庫。在你決定要不要使用一個第三方解決方案之前,弄明白 Android 到底是如何處理資料儲存的這一問題,是非常重要的。
開啟資料庫
Android 上建立和開啟資料庫相對簡單。你必須通過子類化 SQLiteOpenHelper 來進行實現。預設的構造方法中,你要制定資料庫名字。如果該資料庫已經存在,它會被開啟。如果不存在,則會被建立。應用能有許多單獨的資料庫檔案。每個資料庫都必須表示為單獨的 SQLiteOpenHelper
子類。
資料庫檔案對你的應用來說是私有的,它們存在檔案系統中你的應用的子資料夾下,並且受Linux檔案訪問許可權保護。可惜的是,資料庫檔案沒有加密。
但是建立資料庫檔案是不夠的,在你的 SQLiteOpenHelper
子類中,你要重寫 onCreate()
方法執行SQL語句建立表,檢視以及任何資料庫模式(schema)中的東西。你可以重寫例如 onConfigure()
之類的其他方法來啟用或禁用資料庫功能,比如預寫日誌或外來鍵支援等。
改變資料庫模式 (schema)
除了在 SQLiteOpenHelper
子類的構造方法中指定資料庫名字,你還要指定資料庫版本號。版本號對於任意一個給定的 release 版本必須是不變的,而且根據框架的要求,這個數需要是隻增不減的。
SQLiteOpenHelper
使用資料庫的版本號來決定是否需要升級或降級。在升級或降級的回撥方法中,你將使用提供給你的 oldVersion
和 newVersion
引數來決定哪個 ALTER 語句需要執行來更新 schema。為每個新資料庫版本提供單獨的語句是一種很好的方法,這樣就可以處理資料庫的跨版本升級了。
連線到資料庫
資料庫查詢是由 SQLiteDatabase 類來管理的。 在你的 SQLiteOpenHelper
子類呼叫 getReadableDatabase()
或 getWritableDatabase()
時,會返回一個 SQLiteDatabase
例項。要注意這些方法通常會返回同一個物件。唯一一個例外是 getReadableDatabase()
,在遇到諸如磁碟空間已滿之類的問題時,它會返回一個只讀的資料庫,這時會阻止寫入資料庫。由於磁碟問題其實很少發生,許多開發者開發過程中只調用 getWritableDatabase()
。
資料庫建立和模式改變是在你第一次獲得 SQLiteDatabase
例項後才會進行的。因此,你不能在主執行緒請求 SQLiteDatabase
例項。你的 SQLiteOpenHelper
子類幾乎都會返回同樣的 SQLiteDatabase
例項。這意味著在任何執行緒呼叫 SQLiteDatabase.close()
都會關閉你應用中所有的 SQLiteDatabase
例項。這就導致一大堆難於查詢的 bug。實際上,有些開發者選擇只在程式啟動時開啟 SQLiteDatabase
,只在程式關閉呼叫 close()
。
查詢資料
SQLiteDatabase
提供了對資料庫進行查詢,插入,更新及刪除的方法。對於簡單的查詢,你不用寫任何 SQL。但對於更高階的查詢,你還要得自己寫 SQL。SQLiteDatabase 有一個 rawQuery()
和 execSQL()
方法, 這能把整個 SQL 當做引數來執行高階查詢,例如使用 unions 和 joins 命令。你可以使用 SQLiteQueryBuilder 來協助你完成適當的查詢。
query()
和 rawQuery()
都返回 Cursor 物件。保持 Cursor 物件的引用並且在應用中傳來傳去聽起來十分誘人,但是 Cursor 物件比 單純的 Java 物件 (Plain Old Java Object, POJO) 耗費更多的系統資源。因此,Cursor 物件需要儘快散集化到 POJO。在散集化之後你需要呼叫 close()
方法釋放資源。
SQLite 中支援事務 (transactions)。你可以通過呼叫 SQLiteDatabase.beginTransaction()
開啟一個事務。事務能通過呼叫 beginTransaction()
巢狀。當外層事務結束後所有在這個事務中完成的工作,以及所有巢狀的事務都需要提交或回滾。所有那些沒有用 setTransactionSuccessful()
方法標記為完成的事務中的變更都會被回滾。
資料訪問物件 (Data Access Object)
如前面提到的,Android 不提供任何列集和散集物件的方法。這意味著我們要負責管理從 Cursor 中取資料到 POJO 中的邏輯。這套邏輯應該用 Data Access Object (DAO) 封裝好。
Java 從業者,順帶上 Android 開發者,應該都對 DAO 模式很熟悉了。它的主要目的是將應用的互動從持久化層中抽象出來,而不暴露持久化的實現細節。這可以把應用從資料模式中隔離出來。這也使得遷移到第三方的資料庫實現時,對應用的核心邏輯造成的風險能更小。你的應用中的所有與資料庫的互動都應該通過 DAO 實現。
非同步載入資料
獲取 SQLiteDatabase
的參照是一個昂貴的操作,因此永遠不要在主執行緒執行它。擴充套件來說,資料庫查詢也不要在主執行緒執行。Android 提供 Loaders 來幫助實現這一點。它們允許 activity 或 fragment 非同步載入資料。Loaders 可以解決配置改變時資料持久的問題,也能檢測資料並在內容改變的時候傳送新的結果。Android 提供 CursorLoader 來從資料庫中載入資料。
與其他應用程式共享資料
雖然資料庫對於建立它們的應用來說是私有的,但是 Android 也提供與其他應用程式共享資料的方法。Content Providers 提供一個結構化的介面,能使其他應用讀取甚至可能修改你的資料。和 SQLiteDatabase
類似, Content Providers 開放出 query()
,insert()
,update()
和 delete()
這些方法來操作資料。資料以 Cursor
的形式返回,而且對 Content Provider 的訪問預設是同步的,這樣可以使訪問是執行緒安全的。
總結
Android 資料庫與 iOS 上類似的功能相比,實現更加複雜。但是切記,不要只是為了減少模板程式碼而去使用第三方庫。對於 Android 資料庫框架的徹底理解會讓你知道該不該選擇使用第三方庫,以及使用什麼樣的第三方庫。Android Developer 網站 提供了兩個操作 SQLite 資料庫的樣例工程。你可以詳細看看 NotePad 和 SearchableDictionary 這兩個專案可以獲得更多資訊。