鍍金池/ 教程/ iOS/ Android 中的 SQLite 數(shù)據(jù)庫支持
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動布局工具箱
動畫
為 iOS 7 重新設(shè)計 App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應(yīng)式 Android 應(yīng)用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項目介紹
Swift 的強(qiáng)大之處
測試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計優(yōu)雅的移動游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動畫
常見的后臺實(shí)踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場
游戲
調(diào)試核對清單
View Controller 容器
學(xué)無止境
XCTest 測試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

Android 中的 SQLite 數(shù)據(jù)庫支持

開袋即食

我們大多數(shù)人都至少熟悉某些 Core Data 提供給我們的直接可用的持久化特性。然而不幸的是,它們中的很多在 Android 平臺上并不是自動化的。 例如,Core Data 抽象出大部分?jǐn)?shù)據(jù)庫的 SQL 語法和數(shù)據(jù)庫標(biāo)準(zhǔn),這些語法和標(biāo)準(zhǔn)都是數(shù)據(jù)庫工程師們每天面對的問題。 因為 Android 僅提供一個簡單的 SQLite 客戶端,所以你扔需要寫 SQL 并且確保你的數(shù)據(jù)庫表被適當(dāng)?shù)臉?biāo)準(zhǔn)化了。

Core Data 允許我們在對象方面考慮。 實(shí)際上,它能自動處理列集和散集對象。 得益于其提供了記錄層的緩存,所以它在移動設(shè)備上的性能很好。每次從存儲中請求同樣的數(shù)據(jù)段時,它不用再創(chuàng)建另外一個的對象實(shí)例。當(dāng)觀察一個對象的變化時,我們甚至不需要刷新那個被觀察對象就能做到。

Android 上可不是這種情況。你要完全負(fù)責(zé)將對象寫入數(shù)據(jù)庫或者從中將它們讀取。這意味著你要自己實(shí)現(xiàn)對象緩存(如果需要的話),管理對象實(shí)例化,以及手動對任何已存在對象進(jìn)行是否需要變更的檢測。

在 Android 中,你需要注意版本特定的功能。不同版本的 Android 使用不同的 SQLite 實(shí)現(xiàn)。這意味著同一個數(shù)據(jù)庫指令可能在其他平臺版本上產(chǎn)生完全不同的結(jié)果。根據(jù)你執(zhí)行的 SQLite 版本不同,一個查詢語句也會執(zhí)行得各不相同。

填缺補(bǔ)空

許多Android開發(fā)者來自企業(yè)。許多年來,對象關(guān)系型映射庫一直在服務(wù)器平臺上用于減輕與數(shù)據(jù)庫的交互的難度。然而,這些庫因為性能問題而導(dǎo)致無法在移動端直接使用。意識到這一點(diǎn),一些開發(fā)者組織起來制作了面向移動端的 ORM 庫來解決這一問題。

在 Android 上給 SQLite 添加 ORM 支持的其中一個使用廣泛的的方法是 OrmLite。OrmLite 提供對持久化對象的自動列集和散集化。它不用寫大量的 SQL,并且提供程序接口來查詢,更新,刪除對象。在 ORM 中另一個競爭者是 greenDAO。它提供許多與 OrmLite 類似的功能,但是承諾具有更好的性能(根據(jù)它的網(wǎng)站上說),例如基于注解的設(shè)置。

對第三方庫通常都是抱怨都是加到項目中以后造成了額外的復(fù)雜度并且使得性能臃腫。有開發(fā)者覺得這實(shí)在很蛋疼,于是他寫了一個叫 Cupboard 的輕量級 Android SQLite 框架的封裝。它聲稱的目標(biāo)是在不使用 ContentValues 和不解析 Cursors 的情況下為 Java 對象的提供持久化存儲,它將會簡單輕量,并整合時不會對核心 Android 類造成任何影響。你仍將需要管理數(shù)據(jù)庫的創(chuàng)建,但是查詢對象會變得簡單很多。

還有開發(fā)者決定完全廢棄 SQLite 并且創(chuàng)建了 Perst。它從開始到接口都是用面向?qū)ο笳Z言設(shè)計的。它善于列集和散集對象,并且在性能跑分中表現(xiàn)很好。這種解決方法確實(shí)是完全替換了部分 Android 框架,但是也存在一定風(fēng)險,因為你可能很難再在以后將其替換成不同方案了。

有這些甚至是更多可用的選擇,為什么大家還都選擇在原始的 Android 數(shù)據(jù)庫框架下開發(fā)呢?這么說吧,框架和封裝相較于它們所解決的問題,有時候能帶來更多麻煩。例如,在一個項目中,我們同時寫入數(shù)據(jù)庫且實(shí)例化很多對象,這就會導(dǎo)致我們的 ORM 庫慢得像爬一樣。因為這個庫不是設(shè)計用來讓我們以這樣的方式使用的。

在評估框架和庫的時候,檢查看看有沒有使用 Java 的反射機(jī)制。反射機(jī)制在 Java 中代價相對較大,因此要謹(jǐn)慎使用。另外,如果你的項目是 Ice Cream Sandwich 前的版本,還要看看你的庫是否在使用 Java 最近解決的一個 bug,它會導(dǎo)致在運(yùn)行時因為注解而導(dǎo)致的性能下降。

最后,還要評估添加框架會不會顯著的增加項目的復(fù)雜度。如果你與其他開發(fā)者共同開發(fā),記住,他們也必須花時間來學(xué)習(xí)該庫。在你決定要不要使用一個第三方解決方案之前,弄明白 Android 到底是如何處理數(shù)據(jù)存儲的這一問題,是非常重要的。

打開數(shù)據(jù)庫

Android 上創(chuàng)建和打開數(shù)據(jù)庫相對簡單。你必須通過子類化 SQLiteOpenHelper 來進(jìn)行實(shí)現(xiàn)。默認(rèn)的構(gòu)造方法中,你要制定數(shù)據(jù)庫名字。如果該數(shù)據(jù)庫已經(jīng)存在,它會被打開。如果不存在,則會被創(chuàng)建。應(yīng)用能有許多單獨(dú)的數(shù)據(jù)庫文件。每個數(shù)據(jù)庫都必須表示為單獨(dú)的 SQLiteOpenHelper 子類。

數(shù)據(jù)庫文件對你的應(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ù)寫日志或外鍵支持等。

改變數(shù)據(jù)庫模式 (schema)

除了在 SQLiteOpenHelper 子類的構(gòu)造方法中指定數(shù)據(jù)庫名字,你還要指定數(shù)據(jù)庫版本號。版本號對于任意一個給定的 release 版本必須是不變的,而且根據(jù)框架的要求,這個數(shù)需要是只增不減的。

SQLiteOpenHelper 使用數(shù)據(jù)庫的版本號來決定是否需要升級或降級。在升級或降級的回調(diào)方法中,你將使用提供給你的 oldVersionnewVersion 參數(shù)來決定哪個 ALTER 語句需要執(zhí)行來更新 schema。為每個新數(shù)據(jù)庫版本提供單獨(dú)的語句是一種很好的方法,這樣就可以處理數(shù)據(jù)庫的跨版本升級了。

連接到數(shù)據(jù)庫

數(shù)據(jù)庫查詢是由 SQLiteDatabase 類來管理的。 在你的 SQLiteOpenHelper 子類調(diào)用 getReadableDatabase()getWritableDatabase() 時,會返回一個 SQLiteDatabase 實(shí)例。要注意這些方法通常會返回同一個對象。唯一一個例外是 getReadableDatabase(),在遇到諸如磁盤空間已滿之類的問題時,它會返回一個只讀的數(shù)據(jù)庫,這時會阻止寫入數(shù)據(jù)庫。由于磁盤問題其實(shí)很少發(fā)生,許多開發(fā)者開發(fā)過程中只調(diào)用 getWritableDatabase()。

數(shù)據(jù)庫創(chuàng)建和模式改變是在你第一次獲得 SQLiteDatabase 實(shí)例后才會進(jìn)行的。因此,你不能在主線程請求 SQLiteDatabase 實(shí)例。你的 SQLiteOpenHelper 子類幾乎都會返回同樣的 SQLiteDatabase 實(shí)例。這意味著在任何線程調(diào)用 SQLiteDatabase.close() 都會關(guān)閉你應(yīng)用中所有的 SQLiteDatabase 實(shí)例。這就導(dǎo)致一大堆難于查找的 bug。實(shí)際上,有些開發(fā)者選擇只在程序啟動時打開 SQLiteDatabase ,只在程序關(guān)閉調(diào)用 close()。

查詢數(shù)據(jù)

SQLiteDatabase 提供了對數(shù)據(jù)庫進(jìn)行查詢,插入,更新及刪除的方法。對于簡單的查詢,你不用寫任何 SQL。但對于更高級的查詢,你還要得自己寫 SQL。SQLiteDatabase 有一個 rawQuery()execSQL() 方法, 這能把整個 SQL 當(dāng)做參數(shù)來執(zhí)行高級查詢,例如使用 unions 和 joins 命令。你可以使用 SQLiteQueryBuilder 來協(xié)助你完成適當(dāng)?shù)牟樵儭?/p>

query()rawQuery() 都返回 Cursor 對象。保持 Cursor 對象的引用并且在應(yīng)用中傳來傳去聽起來十分誘人,但是 Cursor 對象比 單純的 Java 對象 (Plain Old Java Object, POJO) 耗費(fèi)更多的系統(tǒng)資源。因此,Cursor 對象需要盡快散集化到 POJO。在散集化之后你需要調(diào)用 close() 方法釋放資源。

SQLite 中支持事務(wù) (transactions)。你可以通過調(diào)用 SQLiteDatabase.beginTransaction() 開啟一個事務(wù)。事務(wù)能通過調(diào)用 beginTransaction() 嵌套。當(dāng)外層事務(wù)結(jié)束后所有在這個事務(wù)中完成的工作,以及所有嵌套的事務(wù)都需要提交或回滾。所有那些沒有用 setTransactionSuccessful() 方法標(biāo)記為完成的事務(wù)中的變更都會被回滾。

數(shù)據(jù)訪問對象 (Data Access Object)

如前面提到的,Android 不提供任何列集和散集對象的方法。這意味著我們要負(fù)責(zé)管理從 Cursor 中取數(shù)據(jù)到 POJO 中的邏輯。這套邏輯應(yīng)該用 Data Access Object (DAO) 封裝好。

Java 從業(yè)者,順帶上 Android 開發(fā)者,應(yīng)該都對 DAO 模式很熟悉了。它的主要目的是將應(yīng)用的交互從持久化層中抽象出來,而不暴露持久化的實(shí)現(xiàn)細(xì)節(jié)。這可以把應(yīng)用從數(shù)據(jù)模式中隔離出來。這也使得遷移到第三方的數(shù)據(jù)庫實(shí)現(xiàn)時,對應(yīng)用的核心邏輯造成的風(fēng)險能更小。你的應(yīng)用中的所有與數(shù)據(jù)庫的交互都應(yīng)該通過 DAO 實(shí)現(xiàn)。

異步加載數(shù)據(jù)

獲取 SQLiteDatabase 的參照是一個昂貴的操作,因此永遠(yuǎn)不要在主線程執(zhí)行它。擴(kuò)展來說,數(shù)據(jù)庫查詢也不要在主線程執(zhí)行。Android 提供 Loaders 來幫助實(shí)現(xiàn)這一點(diǎn)。它們允許 activity 或 fragment 異步加載數(shù)據(jù)。Loaders 可以解決配置改變時數(shù)據(jù)持久的問題,也能檢測數(shù)據(jù)并在內(nèi)容改變的時候發(fā)送新的結(jié)果。Android 提供 CursorLoader 來從數(shù)據(jù)庫中加載數(shù)據(jù)。

與其他應(yīng)用程序共享數(shù)據(jù)

雖然數(shù)據(jù)庫對于創(chuàng)建它們的應(yīng)用來說是私有的,但是 Android 也提供與其他應(yīng)用程序共享數(shù)據(jù)的方法。Content Providers 提供一個結(jié)構(gòu)化的接口,能使其他應(yīng)用讀取甚至可能修改你的數(shù)據(jù)。和 SQLiteDatabase 類似, Content Providers 開放出 query(),insert(),update()delete() 這些方法來操作數(shù)據(jù)。數(shù)據(jù)以 Cursor 的形式返回,而且對 Content Provider 的訪問默認(rèn)是同步的,這樣可以使訪問是線程安全的。

總結(jié)

Android 數(shù)據(jù)庫與 iOS 上類似的功能相比,實(shí)現(xiàn)更加復(fù)雜。但是切記,不要只是為了減少模板代碼而去使用第三方庫。對于 Android 數(shù)據(jù)庫框架的徹底理解會讓你知道該不該選擇使用第三方庫,以及使用什么樣的第三方庫。Android Developer 網(wǎng)站 提供了兩個操作 SQLite 數(shù)據(jù)庫的樣例工程。你可以詳細(xì)看看 NotePadSearchableDictionary 這兩個項目可以獲得更多信息。