Android開發筆記(八十五)手機資料庫Realm
阿新 • • 發佈:2019-02-09
Realm應用背景
Android自帶的SQLite資料庫,在多數場合能夠滿足我們的需求,但隨著app廣泛使用,SQLite也暴露了幾個不足之處:1、開發者編碼比較麻煩,而且還要求開發者具備SQL語法知識;
2、SQLite預設沒有加密功能,手機一旦丟失容易導致資料庫被破解;
3、SQLite底層採用java程式碼,導致效能提升存在瓶頸;
基於以上幾點,Android上的各種ORM應運而生(ORM全稱Object Relational Mapping,即物件關係對映),最常見的便是greenDAO了。greenDAO是一個將物件對映到SQLite資料庫中的ORM解決方案,它在github上的地址是https://github.com/greenrobot/greenDAO,下面是greenDAO相比直接使用SQLite的幾個改進點:
1、簡化資料庫操作的編碼,開發者可以不用熟悉SQL語法;
2、使用靈活,可在實體類中自定義類和列舉型別;
3、號稱是基於SQLite的ORM框架中效能最好的;(博主沒對比greenDAO與直接使用SQLite的效能差異,所以只能是跟其他ORM框架比較,比如ORMLite、sugarORM等等)
但是greenDAO使用的資料庫引擎還是SQLite,因此某些方面並沒有本質的改善,比如資料庫的加密、資料庫操作的效能等等。
對於Realm來說,這些改善就是可能的了,因為Realm有自己的資料庫引擎,而且引擎使用C++編寫,效能比java引擎的SQLite有數倍提升。Realm使用C++引擎還有一個好處,就是可以跨平臺使用,不但能用於Android,也能用於IOS。Realm的第三個好處是,它具有很多移動裝置專用資料庫的特性,比如支援JSON、流式api、資料變更通知,以及加密支援,這些都為開發者帶來了方便。
Realm環境搭建
0.87.5的Realm下載頁面是https://realm.io/docs/java/0.87.5/#eclipse,github上最新版本的地址是https://github.com/realm/realm-java。把Realm加入到工程,除了引用realm-android-0.87.5.jar,還得加入armeabi目錄下的so檔案librealm-jni.so。現在編譯通過了,可是執行時又坑爹了,居然報錯“java.lang.IllegalArgumentException: Country is not part of the schema for this Realm”,原因是Realm採用了註解Annotation方式,所以得先讓我們的Eclipse支援註解才行。類似的情況,也存在於ButterKnife這個注入框架。
按照Realm官網的說明步驟,竟然發現我們最新的ADT,在“Properties”——“Java Compiler”下並沒有“Annotation Processing”。網上轉悠了一圈,找到了如下解決步驟:
1、依次選擇“Help”——“Install New Software”
2、下拉選擇Juno,即“Juno - http://download.eclipse.org/releases/juno”
3、在Name列表中點開“Programming Languages”,然後勾選“Eclipse Java Development Tools”(最新版本是3.8.2)
4、點選“Next”按鈕,執行安裝操作
5、安裝完畢重啟ADT,就可以在“Java Compiler”下找到“Annotation Processing”了
裝好Annotation外掛,只是萬里長征的第一步,接下來我們還得配置Eclipse,使之支援Annotation,具體步驟如下:
1、右擊我們的工程,依次選擇“Properties”——“Java Compiler”——“Annotation Processing”,勾選“Enable project specific settings”,並點選“Apply”按鈕,然後工程會重新編譯;
2、繼續開啟“Annotation Processing”——“Factory Path”,勾選“Enable project specific settings”,然後點選“Click Add JARs”按鈕,選擇工程libs目錄下的realm-android-0.87.5.jar,點選“OK”按鈕,然後工程又會重新編譯;
3、為了確保註解的處理器一直工作,我們得在所有RealmObject派生類的前一行加上註解:@RealmClass
另外,正式的app都會進行程式碼混淆處理,為了避免混淆操作影響Realm的使用,我們要在proguard-project.txt增加如下配置:
[java]
- -keep class io.realm.annotations.RealmModule
- -keep @io.realm.annotations.RealmModule class *
- -keep class io.realm.internal.Keep
- -keep @io.realm.internal.Keep class * { *; }
- -dontwarn javax.**
- -dontwarn io.realm.**
-keep class io.realm.annotations.RealmModule -keep @io.realm.annotations.RealmModule class * -keep class io.realm.internal.Keep -keep @io.realm.internal.Keep class * { *; } -dontwarn javax.** -dontwarn io.realm.**
Realm編碼開發
資料庫配置RealmConfiguration
RealmConfiguration是Realm的配置工具類,它採用了建造者模式來構建,下面是RealmConfiguration類的常用方法:Builder(context) : 初始化RealmConfiguration的建造器。
Builder.name : 指定資料庫的名稱。如不指定預設名為default。
Builder.encryptionKey : 指定資料庫的金鑰。金鑰可由SecureRandom的nextBytes方法獲得,如不指定金鑰則預設不加密。一旦建立加密的資料庫,如果訪問時金鑰不正確,則Realm會扔出異常“java.lang.IllegalArgumentException: Illegal Argument: Invalid format of Realm file”。
Builder.schemaVersion : 指定資料庫的版本號。如果不指定預設版本號為0,若原版本號與現版本號不一致,Realm會丟擲異常“io.realm.exceptions.RealmMigrationNeededException: RealmMigration must be provided”。
Builder.migration : 指定遷移操作的遷移類,當Realm發現新舊版本號不一致時,會自動使用該遷移類完成遷移操作。
Builder.deleteRealmIfMigrationNeeded : 宣告版本衝突時自動刪除原資料庫。
Builder.inMemory : 宣告資料庫只在記憶體中持久化。這意味著插入資料庫後不能立即關閉資料庫,因為一旦關閉資料庫則記憶體中的資料馬上丟失。若資料採用在檔案中持久化,則無需擔心關閉資料庫導致資料丟失的問題。
build : 完成配置構建。
getRealmFolder : 獲取資料庫的持有者,返回File物件。
getRealmFileName : 獲取資料庫的檔名字串。
getEncryptionKey : 獲取資料庫的加密金鑰。
getSchemaVersion : 獲取資料庫的版本號。
getMigration : 獲取遷移操作的遷移類。
shouldDeleteRealmIfMigrationNeeded : 判斷是否宣告版本衝突時自動刪除原資料庫。
getDurability : 返回資料持久化的方式
資料表物件RealmObject
RealmObject是資料表的實體基類,所有Realm的實體類都要從RealmObject派生而來。Realm實體類除了欄位宣告與set方法、get方法之外,還要加上若干必要的註解,舉例如下:@RealmClass : 加在類名前面,表示這是一個Realm實體類。
@PrimaryKey : 加在欄位前面,表示該欄位是主鍵。
@Required : 加在欄位前面,表示該欄位非空。
@Ignore: 加在欄位前面,表示該欄位不是Realm表的欄位。因為有時我們需要處理一些額外的資訊,但又不需要把這些資訊儲存到資料庫。
下面是宣告一個實體類的程式碼例子:
[java] view plain copy print?
- import io.realm.RealmObject;
- import io.realm.annotations.Ignore;
- import io.realm.annotations.PrimaryKey;
- import io.realm.annotations.RealmClass;
- import io.realm.annotations.Required;
- @RealmClass
- publicclass Country extends RealmObject {
- @PrimaryKey
- private String code;
- @Required
- private String name;
- @Required
- privateint population;
- @Ignore
- private String remark;
- public Country() {
- }
- public String getCode() {
- return code;
- }
- publicvoid setCode(String code) {
- this.code = code;
- }
- public String getName() {
- return name;
- }
- publicvoid setName(String name) {
- this.name = name;
- }
- publicint getPopulation() {
- return population;
- }
- publicvoid setPopulation(int population) {
- this.population = population;
- }
- public String getRemark() {
- return remark;
- }
- publicvoid setRemark(String remark) {
- this.remark = remark;
- }
- }
import io.realm.RealmObject;
import io.realm.annotations.Ignore;
import io.realm.annotations.PrimaryKey;
import io.realm.annotations.RealmClass;
import io.realm.annotations.Required;
@RealmClass
public class Country extends RealmObject {
@PrimaryKey
private String code;
@Required
private String name;
@Required
private int population;
@Ignore
private String remark;
public Country() {
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPopulation() {
return population;
}
public void setPopulation(int population) {
this.population = population;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}
資料庫管理Realm
Realm是資料庫管理工具類,可完成DDL和DML操作,下面是Realm類的常用方法:getInstance : 獲得一個數據庫例項。可傳入RealmConfiguration物件,若沒有傳入RealmConfiguration,則預設操作名為default.realm的資料庫檔案。
setDefaultConfiguration : 設定預設的RealmConfiguration配置。
deleteRealm : 刪除指定配置的資料庫。
isClosed : 判斷資料庫是否關閉。
close : 關閉資料庫。
beginTransaction : 開始事務,需與commitTransaction配合使用。
commitTransaction : 結束事務,需與beginTransaction配合使用。
createObject : 從RealmObject類建立一條資料庫記錄,後面直接使用該類的設定方法即可寫入欄位值。
copyToRealm : 把指定RealmObject類插入資料庫,如已存在主鍵相同的記錄則扔出異常。
copyToRealmOrUpdate : 把指定RealmObject類插入資料庫,如已存在主鍵相同的記錄則更新原記錄。
remove : 刪除指定資料庫記錄。
executeTransaction : 單獨對指定Realm執行事務,用於需要對事務失敗進行處理的場合。
where : 查詢指定表。返回RealmQuery物件。
distinct : 查詢指定表指定記錄的去重佇列。返回RealmResults佇列。
下面是Realm插入記錄的程式碼示例:
[java] view plain copy print?
- mRealm = Realm.getInstance(mConfig);
- mRealm.beginTransaction();
- Country country1 = mRealm.createObject(Country.class);
- country1.setName("北京");
- country1.setPopulation(5165800);
- country1.setCode("beijing");
- Country country2 = new Country();
- country2.setName("上海");
- country2.setPopulation(5999800);
- country2.setCode("shanghai");
- mRealm.copyToRealm(country2);
- Country country3 = new Country();
- country3.setName("福州");
- country3.setPopulation(876580);
- country3.setCode("fuzhou");
- mRealm.copyToRealmOrUpdate(country3);
- mRealm.commitTransaction();
- mRealm.close();
mRealm = Realm.getInstance(mConfig);
mRealm.beginTransaction();
Country country1 = mRealm.createObject(Country.class);
country1.setName("北京");
country1.setPopulation(5165800);
country1.setCode("beijing");
Country country2 = new Country();
country2.setName("上海");
country2.setPopulation(5999800);
country2.setCode("shanghai");
mRealm.copyToRealm(country2);
Country country3 = new Country();
country3.setName("福州");
country3.setPopulation(876580);
country3.setCode("fuzhou");
mRealm.copyToRealmOrUpdate(country3);
mRealm.commitTransaction();
mRealm.close();
資料庫查詢RealmQuery
RealmQuery是資料庫查詢工具類,其物件由Realm的where方法獲得,下面是RealmQuery類的常用方法:查詢條件
isNull : 指定欄位為空。
isNotNull : 指定欄位非空。
equalTo : 指定欄位等於多少。
notEqualTo : 指定欄位不等於多少。
greaterThan : 指定欄位大於多少。
greaterThanOrEqualTo : 指定欄位大等於多少。
lessThan : 指定欄位小於多少。
lessThanOrEqualTo : 指定欄位小等於多少。
between : 指定欄位位於什麼區間。
contains : 指定欄位包含什麼字串。
beginsWith : 指定欄位以什麼字串開頭。
endsWith : 指定欄位以什麼字串結尾。
返回結果集的運算結果
sum : 對指定欄位求和。
average : 對指定欄位求平均值。
min : 對指定欄位求最小值。
max : 對指定欄位求最大值。
count : 求結果集的記錄數量。
findAll : 返回結果集所有欄位,返回值為RealmResults佇列
findAllSorted : 排序返回結果集所有欄位,返回值為RealmResults佇列
下面是Realm查詢操作的程式碼示例:
[java] view plain copy print?
- mRealm = Realm.getInstance(mConfig);
- RealmResults<Country> results = mRealm.where(Country.class)
- .greaterThan("population", 1000000).findAll();
- String desc = String.format("找到%d條記錄", results.size());
- for (int i = 0; i < results.size(); i++) {
- Country result = results.get(i);
- desc = String.format("%s\n其中城市%s的程式碼是%s,人口有%d", desc,
- result.getName(), result.getCode(),
- result.getPopulation());
- }
- tv_hello.setText(desc);
- if (mRealm.isClosed() != true) {
- mRealm.close();
- }
mRealm = Realm.getInstance(mConfig);
RealmResults<Country> results = mRealm.where(Country.class)
.greaterThan("population", 1000000).findAll();
String desc = String.format("找到%d條記錄", results.size());
for (int i = 0; i < results.size(); i++) {
Country result = results.get(i);
desc = String.format("%s\n其中城市%s的程式碼是%s,人口有%d", desc,
result.getName(), result.getCode(),
result.getPopulation());
}
tv_hello.setText(desc);
if (mRealm.isClosed() != true) {
mRealm.close();
}
資料庫遷移RealmMigration
app升級時可能伴隨著資料庫升級,對於Realm來說,資料庫升級就是遷移操作,把原來的資料庫遷移到新結構的資料庫。編碼中應對資料庫遷移有三種方式:1、構建RealmConfiguration時指定資料庫版本號,如果原版本號與現版本號不一致,Realm會丟擲異常RealmMigrationNeededException。程式碼中捕獲異常RealmMigrationNeededException後,呼叫migrateRealm方法執行遷移操作,示例程式碼如下:
[java] view plain copy print?
- RealmConfiguration config0 = new RealmConfiguration.Builder(this)
- .name("default0").schemaVersion(3).build();
- try {
- realm = Realm.getInstance(config0);
- } catch (RealmMigrationNeededException e) {
- e.printStackTrace();
- // You can then manually call Realm.migrateRealm().
- Realm.migrateRealm(config0, new CustomMigration());
- realm = Realm.getInstance(config0);
- }
RealmConfiguration config0 = new RealmConfiguration.Builder(this)
.name("default0").schemaVersion(3).build();
try {
realm = Realm.getInstance(config0);
} catch (RealmMigrationNeededException e) {
e.printStackTrace();
// You can then manually call Realm.migrateRealm().
Realm.migrateRealm(config0, new CustomMigration());
realm = Realm.getInstance(config0);
}
2、構建RealmConfiguration時指定資料庫版本號,同時也指定遷移類,這樣如果原版本號與現版本號不一致,Realm會自動使用遷移類執行遷移操作。示例程式碼如下:
[java] view plain copy print?
- RealmConfiguration config1 = new RealmConfiguration.Builder(this)
- .name("default1").schemaVersion(3)
- .migration(new CustomMigration()).build();
- realm = Realm.getInstance(config1); // Automatically run migration if needed
RealmConfiguration config1 = new RealmConfiguration.Builder(this)
.name("default1").schemaVersion(3)
.migration(new CustomMigration()).build();
realm = Realm.getInstance(config1); // Automatically run migration if needed
3、構建RealmConfiguration時指定資料庫版本號,同時宣告版本衝突時自動刪除原資料庫,不過該方法一般不用,因為該方法會暴力刪除所有資料。示例程式碼如下:
[java] view plain copy print?
- RealmConfiguration config2 = new RealmConfiguration.Builder(this)
- .name("default2").schemaVersion(3)
- .deleteRealmIfMigrationNeeded().build();
- realm = Realm.getInstance(config2); // WARNING: This will delete all data in the Realm though.