我們大多數(shù)人都至少熟悉某些 Core Data 提供給我們的直接可用的持久化特性。然而不幸的是,它們中的很多在 Android 平臺(tái)上并不是自動(dòng)化的。 例如,Core Data 抽象出大部分?jǐn)?shù)據(jù)庫的 SQL 語法和數(shù)據(jù)庫標(biāo)準(zhǔn),這些語法和標(biāo)準(zhǔn)都是數(shù)據(jù)庫工程師們每天面對(duì)的問題。 因?yàn)?Android 僅提供一個(gè)簡(jiǎn)單的 SQLite 客戶端,所以你扔需要寫 SQL 并且確保你的數(shù)據(jù)庫表被適當(dāng)?shù)臉?biāo)準(zhǔn)化了。
Core Data 允許我們?cè)趯?duì)象方面考慮。 實(shí)際上,它能自動(dòng)處理列集和散集對(duì)象。 得益于其提供了記錄層的緩存,所以它在移動(dòng)設(shè)備上的性能很好。每次從存儲(chǔ)中請(qǐng)求同樣的數(shù)據(jù)段時(shí),它不用再創(chuàng)建另外一個(gè)的對(duì)象實(shí)例。當(dāng)觀察一個(gè)對(duì)象的變化時(shí),我們甚至不需要刷新那個(gè)被觀察對(duì)象就能做到。
Android 上可不是這種情況。你要完全負(fù)責(zé)將對(duì)象寫入數(shù)據(jù)庫或者從中將它們讀取。這意味著你要自己實(shí)現(xiàn)對(duì)象緩存(如果需要的話),管理對(duì)象實(shí)例化,以及手動(dòng)對(duì)任何已存在對(duì)象進(jìn)行是否需要變更的檢測(cè)。
在 Android 中,你需要注意版本特定的功能。不同版本的 Android 使用不同的 SQLite 實(shí)現(xiàn)。這意味著同一個(gè)數(shù)據(jù)庫指令可能在其他平臺(tái)版本上產(chǎn)生完全不同的結(jié)果。根據(jù)你執(zhí)行的 SQLite 版本不同,一個(gè)查詢語句也會(huì)執(zhí)行得各不相同。
許多Android開發(fā)者來自企業(yè)。許多年來,對(duì)象關(guān)系型映射庫一直在服務(wù)器平臺(tái)上用于減輕與數(shù)據(jù)庫的交互的難度。然而,這些庫因?yàn)樾阅軉栴}而導(dǎo)致無法在移動(dòng)端直接使用。意識(shí)到這一點(diǎn),一些開發(fā)者組織起來制作了面向移動(dòng)端的 ORM 庫來解決這一問題。
在 Android 上給 SQLite 添加 ORM 支持的其中一個(gè)使用廣泛的的方法是 OrmLite。OrmLite 提供對(duì)持久化對(duì)象的自動(dòng)列集和散集化。它不用寫大量的 SQL,并且提供程序接口來查詢,更新,刪除對(duì)象。在 ORM 中另一個(gè)競(jìng)爭(zhēng)者是 greenDAO。它提供許多與 OrmLite 類似的功能,但是承諾具有更好的性能(根據(jù)它的網(wǎng)站上說),例如基于注解的設(shè)置。
對(duì)第三方庫通常都是抱怨都是加到項(xiàng)目中以后造成了額外的復(fù)雜度并且使得性能臃腫。有開發(fā)者覺得這實(shí)在很蛋疼,于是他寫了一個(gè)叫 Cupboard 的輕量級(jí) Android SQLite 框架的封裝。它聲稱的目標(biāo)是在不使用 ContentValues 和不解析 Cursors 的情況下為 Java 對(duì)象的提供持久化存儲(chǔ),它將會(huì)簡(jiǎn)單輕量,并整合時(shí)不會(huì)對(duì)核心 Android 類造成任何影響。你仍將需要管理數(shù)據(jù)庫的創(chuàng)建,但是查詢對(duì)象會(huì)變得簡(jiǎn)單很多。
還有開發(fā)者決定完全廢棄 SQLite 并且創(chuàng)建了 Perst。它從開始到接口都是用面向?qū)ο笳Z言設(shè)計(jì)的。它善于列集和散集對(duì)象,并且在性能跑分中表現(xiàn)很好。這種解決方法確實(shí)是完全替換了部分 Android 框架,但是也存在一定風(fēng)險(xiǎn),因?yàn)槟憧赡芎茈y再在以后將其替換成不同方案了。
有這些甚至是更多可用的選擇,為什么大家還都選擇在原始的 Android 數(shù)據(jù)庫框架下開發(fā)呢?這么說吧,框架和封裝相較于它們所解決的問題,有時(shí)候能帶來更多麻煩。例如,在一個(gè)項(xiàng)目中,我們同時(shí)寫入數(shù)據(jù)庫且實(shí)例化很多對(duì)象,這就會(huì)導(dǎo)致我們的 ORM 庫慢得像爬一樣。因?yàn)檫@個(gè)庫不是設(shè)計(jì)用來讓我們以這樣的方式使用的。
在評(píng)估框架和庫的時(shí)候,檢查看看有沒有使用 Java 的反射機(jī)制。反射機(jī)制在 Java 中代價(jià)相對(duì)較大,因此要謹(jǐn)慎使用。另外,如果你的項(xiàng)目是 Ice Cream Sandwich 前的版本,還要看看你的庫是否在使用 Java 最近解決的一個(gè) bug,它會(huì)導(dǎo)致在運(yùn)行時(shí)因?yàn)樽⒔舛鴮?dǎo)致的性能下降。
最后,還要評(píng)估添加框架會(huì)不會(huì)顯著的增加項(xiàng)目的復(fù)雜度。如果你與其他開發(fā)者共同開發(fā),記住,他們也必須花時(shí)間來學(xué)習(xí)該庫。在你決定要不要使用一個(gè)第三方解決方案之前,弄明白 Android 到底是如何處理數(shù)據(jù)存儲(chǔ)的這一問題,是非常重要的。
Android 上創(chuàng)建和打開數(shù)據(jù)庫相對(duì)簡(jiǎn)單。你必須通過子類化 SQLiteOpenHelper 來進(jìn)行實(shí)現(xiàn)。默認(rèn)的構(gòu)造方法中,你要制定數(shù)據(jù)庫名字。如果該數(shù)據(jù)庫已經(jīng)存在,它會(huì)被打開。如果不存在,則會(huì)被創(chuàng)建。應(yīng)用能有許多單獨(dú)的數(shù)據(jù)庫文件。每個(gè)數(shù)據(jù)庫都必須表示為單獨(dú)的 SQLiteOpenHelper 子類。
數(shù)據(jù)庫文件對(duì)你的應(yīng)用來說是私有的,它們存在文件系統(tǒng)中你的應(yīng)用的子文件夾下,并且受Linux文件訪問權(quán)限保護(hù)??上У氖牵瑪?shù)據(jù)庫文件沒有加密。
但是創(chuàng)建數(shù)據(jù)庫文件是不夠的,在你的 SQLiteOpenHelper 子類中,你要重寫 onCreate() 方法執(zhí)行SQL語句創(chuàng)建表,視圖以及任何數(shù)據(jù)庫模式(schema)中的東西。你可以重寫例如 onConfigure() 之類的其他方法來啟用或禁用數(shù)據(jù)庫功能,比如預(yù)寫日志或外鍵支持等。
除了在 SQLiteOpenHelper 子類的構(gòu)造方法中指定數(shù)據(jù)庫名字,你還要指定數(shù)據(jù)庫版本號(hào)。版本號(hào)對(duì)于任意一個(gè)給定的 release 版本必須是不變的,而且根據(jù)框架的要求,這個(gè)數(shù)需要是只增不減的。
SQLiteOpenHelper 使用數(shù)據(jù)庫的版本號(hào)來決定是否需要升級(jí)或降級(jí)。在升級(jí)或降級(jí)的回調(diào)方法中,你將使用提供給你的 oldVersion 和 newVersion 參數(shù)來決定哪個(gè) ALTER 語句需要執(zhí)行來更新 schema。為每個(gè)新數(shù)據(jù)庫版本提供單獨(dú)的語句是一種很好的方法,這樣就可以處理數(shù)據(jù)庫的跨版本升級(jí)了。
數(shù)據(jù)庫查詢是由 SQLiteDatabase 類來管理的。 在你的 SQLiteOpenHelper 子類調(diào)用 getReadableDatabase() 或 getWritableDatabase() 時(shí),會(huì)返回一個(gè) SQLiteDatabase 實(shí)例。要注意這些方法通常會(huì)返回同一個(gè)對(duì)象。唯一一個(gè)例外是 getReadableDatabase(),在遇到諸如磁盤空間已滿之類的問題時(shí),它會(huì)返回一個(gè)只讀的數(shù)據(jù)庫,這時(shí)會(huì)阻止寫入數(shù)據(jù)庫。由于磁盤問題其實(shí)很少發(fā)生,許多開發(fā)者開發(fā)過程中只調(diào)用 getWritableDatabase()。
數(shù)據(jù)庫創(chuàng)建和模式改變是在你第一次獲得 SQLiteDatabase 實(shí)例后才會(huì)進(jìn)行的。因此,你不能在主線程請(qǐng)求 SQLiteDatabase 實(shí)例。你的 SQLiteOpenHelper 子類幾乎都會(huì)返回同樣的 SQLiteDatabase 實(shí)例。這意味著在任何線程調(diào)用 SQLiteDatabase.close() 都會(huì)關(guān)閉你應(yīng)用中所有的 SQLiteDatabase 實(shí)例。這就導(dǎo)致一大堆難于查找的 bug。實(shí)際上,有些開發(fā)者選擇只在程序啟動(dòng)時(shí)打開 SQLiteDatabase ,只在程序關(guān)閉調(diào)用 close()。
SQLiteDatabase 提供了對(duì)數(shù)據(jù)庫進(jìn)行查詢,插入,更新及刪除的方法。對(duì)于簡(jiǎn)單的查詢,你不用寫任何 SQL。但對(duì)于更高級(jí)的查詢,你還要得自己寫 SQL。SQLiteDatabase 有一個(gè) rawQuery() 和 execSQL() 方法, 這能把整個(gè) SQL 當(dāng)做參數(shù)來執(zhí)行高級(jí)查詢,例如使用 unions 和 joins 命令。你可以使用 SQLiteQueryBuilder 來協(xié)助你完成適當(dāng)?shù)牟樵儭?/p>
query() 和 rawQuery() 都返回 Cursor 對(duì)象。保持 Cursor 對(duì)象的引用并且在應(yīng)用中傳來傳去聽起來十分誘人,但是 Cursor 對(duì)象比 單純的 Java 對(duì)象 (Plain Old Java Object, POJO) 耗費(fèi)更多的系統(tǒng)資源。因此,Cursor 對(duì)象需要盡快散集化到 POJO。在散集化之后你需要調(diào)用 close() 方法釋放資源。
SQLite 中支持事務(wù) (transactions)。你可以通過調(diào)用 SQLiteDatabase.beginTransaction() 開啟一個(gè)事務(wù)。事務(wù)能通過調(diào)用 beginTransaction() 嵌套。當(dāng)外層事務(wù)結(jié)束后所有在這個(gè)事務(wù)中完成的工作,以及所有嵌套的事務(wù)都需要提交或回滾。所有那些沒有用 setTransactionSuccessful() 方法標(biāo)記為完成的事務(wù)中的變更都會(huì)被回滾。
如前面提到的,Android 不提供任何列集和散集對(duì)象的方法。這意味著我們要負(fù)責(zé)管理從 Cursor 中取數(shù)據(jù)到 POJO 中的邏輯。這套邏輯應(yīng)該用 Data Access Object (DAO) 封裝好。
Java 從業(yè)者,順帶上 Android 開發(fā)者,應(yīng)該都對(duì) DAO 模式很熟悉了。它的主要目的是將應(yīng)用的交互從持久化層中抽象出來,而不暴露持久化的實(shí)現(xiàn)細(xì)節(jié)。這可以把應(yīng)用從數(shù)據(jù)模式中隔離出來。這也使得遷移到第三方的數(shù)據(jù)庫實(shí)現(xiàn)時(shí),對(duì)應(yīng)用的核心邏輯造成的風(fēng)險(xiǎn)能更小。你的應(yīng)用中的所有與數(shù)據(jù)庫的交互都應(yīng)該通過 DAO 實(shí)現(xiàn)。
獲取 SQLiteDatabase 的參照是一個(gè)昂貴的操作,因此永遠(yuǎn)不要在主線程執(zhí)行它。擴(kuò)展來說,數(shù)據(jù)庫查詢也不要在主線程執(zhí)行。Android 提供 Loaders 來幫助實(shí)現(xiàn)這一點(diǎn)。它們?cè)试S activity 或 fragment 異步加載數(shù)據(jù)。Loaders 可以解決配置改變時(shí)數(shù)據(jù)持久的問題,也能檢測(cè)數(shù)據(jù)并在內(nèi)容改變的時(shí)候發(fā)送新的結(jié)果。Android 提供 CursorLoader 來從數(shù)據(jù)庫中加載數(shù)據(jù)。
雖然數(shù)據(jù)庫對(duì)于創(chuàng)建它們的應(yīng)用來說是私有的,但是 Android 也提供與其他應(yīng)用程序共享數(shù)據(jù)的方法。Content Providers 提供一個(gè)結(jié)構(gòu)化的接口,能使其他應(yīng)用讀取甚至可能修改你的數(shù)據(jù)。和 SQLiteDatabase 類似, Content Providers 開放出 query(),insert(),update() 和 delete() 這些方法來操作數(shù)據(jù)。數(shù)據(jù)以 Cursor 的形式返回,而且對(duì) Content Provider 的訪問默認(rèn)是同步的,這樣可以使訪問是線程安全的。
Android 數(shù)據(jù)庫與 iOS 上類似的功能相比,實(shí)現(xiàn)更加復(fù)雜。但是切記,不要只是為了減少模板代碼而去使用第三方庫。對(duì)于 Android 數(shù)據(jù)庫框架的徹底理解會(huì)讓你知道該不該選擇使用第三方庫,以及使用什么樣的第三方庫。Android Developer 網(wǎng)站 提供了兩個(gè)操作 SQLite 數(shù)據(jù)庫的樣例工程。你可以詳細(xì)看看 NotePad 和 SearchableDictionary 這兩個(gè)項(xiàng)目可以獲得更多信息。