Realm介紹與引入
其實(shí)Realm也已經(jīng)作為移動(dòng)端數(shù)據(jù)庫被使用也有一段時(shí)間了具则,而如果作為新手想要把App中數(shù)據(jù)存儲(chǔ)到本地即纲,可能我們File、SP博肋、SQLite低斋,今天要講的Realm就是代替的SQLite的方法之一,當(dāng)然還有GreenDao等等匪凡,我知道Realm是七月底的時(shí)候膊畴,各個(gè)公眾號(hào)開始介紹和使用它,而我開始使用它卻是從上周才開始的病游。
我在開始學(xué)習(xí)Realm的時(shí)候就是一口氣把官方的文檔看完唇跨,而現(xiàn)在最新的版本是2.2.1稠通,官方文檔也會(huì)有中文的比如2.1.1,所以我覺得大家可以一邊看英文的一邊看中文的买猖,還能鍛煉自己的英文閱讀能力改橘,畢竟Android官方文檔大部分都是英文的,所以在學(xué)習(xí)新技術(shù)的時(shí)候就可以看得出英文閱讀能力到底有多重要玉控。 下面就開始我們Realm的使用之旅吧飞主!
不過所有這種第三方的開源庫使用的第一步都是先添加依賴,然后開始使用高诺。
1.在整個(gè)項(xiàng)目的build.gradle下的dependencies添加:
???? classpath "io.realm:realm-gradle-plugin:2.2.0"
2.在app目錄下的build.gradle添加:
???? apply plugin: 'realm-android'
Realm基礎(chǔ)
接下來在使用之前我們可以先來看一些基礎(chǔ)概念:
1.比如我們?nèi)绾味x一個(gè)數(shù)據(jù)模型碌识,我們可以自定義一個(gè)類繼承RealmObject或者實(shí)現(xiàn)RealmModel接口并在類上面添加@RealmClass這個(gè)注解,這時(shí)我們table就會(huì)在運(yùn)行時(shí)自動(dòng)創(chuàng)建了虱而,代碼如下:
public class TestModel extends RealmObject{
...
}
or
@RealmClass
public class OtherTestModel implements RealmModel{
...
}
當(dāng)我們點(diǎn)進(jìn)RealmObject時(shí)會(huì)發(fā)現(xiàn)其實(shí)它也是實(shí)現(xiàn)RealmModel接口的筏餐,如圖:
2.我們創(chuàng)建了數(shù)據(jù)模型后還需要添加字段,這樣才能真的組成一個(gè)table薛窥,Realm支持的類型如下:boolean胖烛、byte、int诅迷、long佩番、float、double(以及他們的封裝類)罢杉、String趟畏、Date、byte[]滩租、RealmObject赋秀、RealmList<? extends RealmObject>,不過成員變量的修飾符并沒有限制律想。
3.一些屬性:
- 主鍵 @PrimaryKey猎莲,但是Realm不支持自增長(zhǎng)的主鍵,我一般使用的時(shí)候都是定義一個(gè)String類型的主鍵技即,然后通過UUID保證主鍵的唯一性(如果封裝類作為主鍵著洼,主鍵可為null,除非同時(shí)被非空修飾)而叼。
- 非空 @Required
- 忽略 @Ignore 如果該類中的某個(gè)字段被它修飾了就表明它不會(huì)被保存到Realm中身笤。
- 索引 @Index 為字段增加搜索索引,這會(huì)導(dǎo)致插入速度變慢葵陵,同時(shí)數(shù)據(jù)文件體積有所增加液荸,但能加速查詢。
Realm使用
那接下來我們就可以開始使用Realm了脱篙,在代碼中使用Realm前還需要對(duì)它進(jìn)行初始化Realm.init(上下文對(duì)象);我是在全局的Application類中就行的娇钱,代碼如下
public class MyApplication extends Application {
private static Context mContext;
@Override
public void onCreate() {
mContext = getApplicationContext();
Realm.init(mContext);
}
public static Context getContext() {
return mContext;
}
}
然后再在清單文件中聲明一下就可以了
<application
android:allowBackup="true"
...
android:name=".MyApplication">
...
</application>
1.準(zhǔn)備
// 創(chuàng)建實(shí)體類 該類必須包含一個(gè)無參構(gòu)造
public class TestModel extends RealmObject{
@PrimaryKey
private String _id;
private String testTitle;
private String updateTime;
...
}
// 如下配置的 Realm 會(huì)被存儲(chǔ)在 Context.getFilesDir() 并且命名為 default.realm
// 獲取Realm實(shí)例 獲取默認(rèn)配置的Realm
Realm mRealm = Realm.getDefaultInstance();
// 事務(wù) 無論是增刪改查都需要開啟事務(wù)
mRealm.beginTransaction();
// 具體邏輯
...
mRealm .commitTransaction();
// 如果要取消事務(wù)的話
mRealm .cancelTransaction();
// 為result設(shè)置數(shù)據(jù)改變偵聽 賦值的話我是先調(diào)用查詢 查詢就算沒有條目也不會(huì)返回null伤柄,所以無須擔(dān)心
mResults.addChangeListener(new RealmChangeListener<RealmResults<TestModel>>() {
@Override
public void onChange(RealmResults<TestModel> element) {
// 這里無需再次賦值給RealmResults, 因?yàn)樗麜?huì)自動(dòng)更新值(必須要當(dāng)前前程有Looper, 而當(dāng)前是主線程)
mResults = element;
mAdapter.notifyDataSetChanged();
}
});
2.寫入
/**
* 插入假數(shù)據(jù) 這種方式會(huì)產(chǎn)生默認(rèn)值的對(duì)象,然后手動(dòng)設(shè)置值,且如果有主鍵時(shí)需要在createObject中設(shè)置 在后臺(tái)線程進(jìn)行
*/
private void createItemData() {
// executeTransaction系列方法都會(huì)自動(dòng)管理事務(wù) 開啟,關(guān)閉文搂,取消
mRealm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
UUID uuid = UUID.randomUUID();
// 寫入的時(shí)候指明了Id
TestModel testModel = realm.createObject(TestModel.class, uuid.toString());
testModel.setTestTitle("點(diǎn)擊編輯標(biāo)題");
testModel.setUpdateTime(getCurrTime());
}
}, new Realm.Transaction.OnSuccess() {
@Override
public void onSuccess() {
mAdapter.notifyDataSetChanged();
}
}, new Realm.Transaction.OnError() {
@Override
public void onError(Throwable error) {
Log.e("Realm", "保存失敗" + error.getMessage());
}
});
}
// 插入的話還有另一種方法copyToRealm();
// 這個(gè)Dog對(duì)象只是一個(gè)普通的對(duì)象
Dog dog = new Dog();
dog.setName("Rex");
dog.setAge(1);
mRealm.beginTransaction();
// 而這邊返回的是managedDog 才是被持久化的對(duì)象
final managedDog = realm.copyToRealm(dog);
mRealm.commitTransaction();
3.查詢
// 查詢所有 TestModel是我自己寫的類
RealmResults<TestModel> mResults = mRealm.where(TestModel.class).findAllAsync();
/**
* 根據(jù)標(biāo)題查詢數(shù)據(jù) 在UI線程進(jìn)行
*/
private void queryResultByTitle(String title) {
// 根據(jù)Title進(jìn)行模糊查詢
mResults = mRealm.where(TestModel.class).contains("testTitle", title) .findAll();
mAdapter.notifyDataSetChanged();
}
// 支持的查詢的條件 當(dāng)然也支持聚合函數(shù)
- between()响迂、greaterThan()、lessThan()细疚、greaterThanOrEqualTo() 和 lessThanOrEqualTo()
- equalTo() 和 notEqualTo()
- contains()、beginsWith()和 endsWith() (類似模糊查尋)
- isNull() 和 isNotNull()
- isEmpty() 和 isNotEmpty()
// 每個(gè)查詢條件都會(huì)被被隱式地被邏輯和(&)組合在一起川梅,而邏輯或(or)需要顯式地去執(zhí)行 or() 如下:
RealmResults<User> r = realm.where(User.class)
.greaterThan("age", 10) //implicit AND
.beginGroup()
.equalTo("name", "Peter")
.or()
.contains("name", "Jo")
.endGroup()
.findAll();
// 排序
RealmResults<User> result = realm.where(User.class).findAll();
result = result.sort("age"); // 默認(rèn)正序
result = result.sort("age", Sort.DESCENDING);
4.刪除
/**
* 刪除數(shù)據(jù) 在后臺(tái)線程進(jìn)行
*/
private void deleteItemData(final int index) {
mRealm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
mResults.deleteFromRealm(index);
}
});
}
// 刪除的方法還有很多就不一一列舉了
5.更新
/**
* 更新數(shù)據(jù)方式一 通過再次查詢 在后臺(tái)線程進(jìn)行
*/
private void updateItemDataById(final String title) {
mRealm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
TestModel testModel = realm.where(TestModel.class).equalTo("_id", mCurrEditId).findFirst();
testModel.setUpdateTime(getCurrTime()); testModel.setTestTitle(title);
}
}, new Realm.Transaction.OnSuccess() {
@Override
public void onSuccess() {
mAdapter.notifyDataSetChanged();
}
}, new Realm.Transaction.OnError() {
@Override
public void onError(Throwable error) {
Log.e("Realm", "保存失敗" + error.getMessage());
}
});
}
/**
* 更新數(shù)據(jù)方法二 通過Realm的特性(實(shí)時(shí)更新) 在創(chuàng)建RealmResults的線程進(jìn)行
*/
private void updateItemData(final String title) {
mRealm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
mResults.get(mCurrItemIndex).setTestTitle(title);
mResults.get(mCurrItemIndex).setUpdateTime(getCurrTime());
}
});
mAdapter.notifyDataSetChanged();
}
6.數(shù)據(jù)庫配置
// Realm的配置類
RealmConfiguration config = new RealmConfiguration.Builder()
.name("test.realm") // 命名
.schemaVersion(3) // 數(shù)據(jù)庫版本
.migration(new MyMigration()) // 數(shù)據(jù)庫內(nèi)容發(fā)生變化
.build();
// 獲取Realm對(duì)象并手動(dòng)設(shè)置配置
mRealm = Realm.getInstance(config);
/**
* 當(dāng)我們更新數(shù)據(jù)庫字段時(shí)會(huì)使用這個(gè)類
*/
public class MyMigration implements RealmMigration {
@Override
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
RealmSchema schema = realm.getSchema();
// 比如當(dāng)老的版本為3的時(shí)候
if (oldVersion == 3) {
schema.get("TestModel")
// 添加新的Realm支持的字段
.addField("testBassType", int.class)
// 添加新的自定義對(duì)象
.addRealmObjectField("testRealmObject", schema.get("OtherTestModel"))
// 添加新的List對(duì)象疯兼,用于一對(duì)多 多對(duì)多
.addRealmListField("testRealmList", schema.get("OtherTestModel"));
oldVersion++;
}
}
}
// 而我在相應(yīng)的類中已經(jīng)寫了
// 用于測(cè)試數(shù)據(jù)庫字段變更 版本變成4的時(shí)候放開下面的字段
// public int testBassType;
// public OtherTestModel testRealmObject;
// public RealmList<OtherTestModel> testRealmList;
7.釋放資源
@Override
protected void onDestroy() {
super.onDestroy();
// 移除偵聽 并關(guān)閉Realm
mResults.removeChangeListeners();
mRealm.close();}
Realm知識(shí)點(diǎn)補(bǔ)充
1.異步查詢可寫回調(diào)的偵聽,且其回調(diào)是通過Looper被執(zhí)行的(如寫入那條)贫途。
2.Realm的實(shí)體類必須要包含一個(gè)無參構(gòu)造函數(shù)吧彪,如果不寫任何構(gòu)造,Java自帶一個(gè)無參構(gòu)造丢早,若是自己寫了其他參數(shù)的構(gòu)造后無參構(gòu)造會(huì)不可用姨裸,此時(shí)需要手動(dòng)頭添加一個(gè)。
3.主鍵不支持自增長(zhǎng)怨酝。
4.使用查詢后的所返回的RealmResults<自定義類>對(duì)象列表傀缩,里面的對(duì)象并非是拷貝,而是查詢后匹配對(duì)象的引用农猬,所以刪改都可以直接用它來操作赡艰。
5.每個(gè)查詢條件都會(huì)被被隱式地被邏輯和(&)組合在一起,而邏輯或(or)需要顯式地去執(zhí)行 or()斤葱。
6.異步操作可以對(duì)RealmResults設(shè)置addChangeLintener(Callback)慷垮,偵聽結(jié)果集發(fā)生的變化,記得最后要移除掉偵聽揍堕。
7.異步查詢可用isLoaded()檢查是否加載完畢料身,而同步時(shí)該方法永遠(yuǎn)返回true。
8.Realm衩茸、RealmObject 和RealmResults 實(shí)例都不可以跨線程使用芹血,而獲取后都是獲取當(dāng)前線程的實(shí)例,跨線程使用會(huì)報(bào)錯(cuò)递瑰。
9.Realm實(shí)例是基于引用計(jì)數(shù)的祟牲,所以你在同一個(gè)線程中獲取了幾次實(shí)例,也需要相應(yīng)的close()幾次抖部。
10.如果Realm實(shí)例存在于一個(gè)帶有Looper的線程说贝,那么這個(gè)Realm實(shí)例就具有自動(dòng)刷新的功能,這個(gè)時(shí)候就可以使用慎颗,且Listener只工作于 Looper線程乡恕。
11.如果Realm實(shí)例所在的線程沒有Looper言询,則更新需要你手動(dòng)調(diào)用waitForChange()。
我希望可以站在初學(xué)者&自學(xué)者的角度把Android中的知識(shí)點(diǎn)很清楚的介紹給大家傲宜,希望大家喜歡运杭。 如果有錯(cuò)誤希望指出來,有問題或者沒看懂函卒,都可以來問我的
項(xiàng)目代碼的地址:https://github.com/GzwJaaaelu/RealmDemo