Realm 使用說明
Realm
一個跨平臺移動數據庫引擎
導入
-
在項目的 build.gradle 添加:
buildscript { repositories { jcenter() } dependencies { classpath "io.realm:realm-gradle-plugin:2.1.1" } }
-
在模組的 build.gradle 中添加:
apply plugin: 'realm-android'
-
ProGuard
不需要針對 Realm 做 ProGuard 的改動
基本用法
模型
繼承 RealmObject ,或者實現 RealmModel 接口并添加注解 @RealmClass 。
public class User extends RealmObject {
// 主鍵,可為空,默認已索引,String、byte、short、int、long、Byte、Short、Integer、Long
@PrimaryKey
private long id;
// 非空,Boolean、ByteShort、Integer、Long、Float、Double、String、byte[]、Date
@Required
private String name;
// 索引,String、byte、short、int、long、boolean、Date
@Index
private int age;
@Ignore // 忽略
private int tempReference;
private Dog dog; // 對單
private RealmList<Cat> cats; // 對多
// 省略 get/set 方法
}
public class Dog extends RealmObject {
public String name;
}
// 接口+注解,創建的托管對象缺少生成的部分方法,使用 RealmObject 的靜態方法替代
@RealmClass
public class Cat implements RealmModel {
public String name;
}</code></pre>
初始化
// Application 中初始化
Realm.init(context);
Realm實例
Realm 實例是線程單例化的,也就是說多次在同一線程調用靜態構建器會返回同一 Realm 實例。
// Context.getFilesDir() 目錄下的 default.realm 文件
Realm realm = Realm.getDefaultInstance();
RealmConfiguration config = new RealmConfiguration.Builder().build(); // 默認的 RealmConfiguration
Realm.setDefaultConfiguration(configuration); // 設置默認 RealmConfiguration
// 配合 Configuration 使用
Realm.deleteRealm(configuration); // 清除數據
Realm realm = Realm.getInstance(configuration); // 獲取自定義的 Realm</code></pre>
RealmConfiguration config = new RealmConfiguration.Builder()
.name("myrealm.realm") // 庫文件名
.encryptionKey(getKey()) // 加密
.schemaVersion(42) // 版本號
.modules(new MySchemaModule()) // 結構
.migration(new MyMigration()) // 遷移
.build();
// 非持久化的、存在于內存中的 Realm 實例
RealmConfiguration myConfig = new RealmConfiguration.Builder()
.name("myrealm.realm")
.inMemory()
.build();</code></pre>
事務
所有的寫操作(添加、修改和刪除對象),必須包含在寫入事務中,確保線程安全。如果一個寫入事務正在進行,那么其他的線程的寫入事務就會阻塞它們所在的線程,使用異步事務以避免阻塞
讀取事務是隱式的,讀操作可在任何時候進行。當寫入事務被提交到 Realm 時,該 Realm 的所有其他實例都將被通知,讀入隱式事務將自動刷新你每個 Realm 對象。
realm.beginTransaction(); // 開始事務
realm.commitTransaction(); // 提交事務
realm.cancelTransaction(); // 取消事務
// 自動處理寫入事務的開始和提交,并在錯誤發生時取消寫入事務
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
// ...
}
});
// 異步事務,4種重載,onSuccess 和 onError 不是必須,非 Looper 線程中只有空(null)回調函數被允許使用
RealmAsyncTask transaction = realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm bgRealm) {
// 異步不能使用外部的 Realm
User user = bgRealm.createObject(User.class);
user.setName("John");
user.setEmail("john@corporation.com");
}
}, new Realm.Transaction.OnSuccess() {
@Override
public void onSuccess() {
// 事務成功,Looper 傳回前臺執行
}
}, new Realm.Transaction.OnError() {
@Override
public void onError(Throwable error) {
// 事務失敗,自動取消,Looper 傳回前臺執行
}
});
// 退出注意取消事務
public void onStop () {
if (transaction != null && !transaction.isCancelled()) {
transaction.cancel();
}
}</code></pre>
添加
User realmUser = realm.createObject(User.class);
// 有主鍵需要添加主鍵,主鍵無自增
User realmUser = realm.createObject(User.class, primaryKeyValue);
// 普通對象轉化為托管對象,建議有主鍵的bean使用
User user = new User();
User realmUser = realm.copyToRealm(user); // 主鍵沖突時報異常
User realmUser = realm.copyToRealmOrUpdate(user); // 主鍵沖突時更新,無主鍵報異常</code></pre>
刪除
final RealmResults<User> results = realm.where(User.class).findAll();
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
// 刪除一個托管對象
results.get(0).deleteFromRealm();
// 使用以下方法,可避免自動更新集合前,某些元素有可能不在集合內,引起的崩潰
results.deleteFromRealm(0);
// 刪除集合的首末對象
results.deleteFirstFromRealm();
results.deleteLastFromRealm();
// 刪除所有集合內對象
results.deleteAllFromRealm();
// 刪除所有
realm.delete(User.class);
realm.deleteAll();
}
});</code></pre>
修改
直接修改托管對象,即修改了數據庫。
bean 有主鍵時,可使用 copyToRealmOrUpdate() 轉化相同主鍵的對象為托管來修改數據庫。
查詢
-
查詢條件
between()、greaterThan()、lessThan()、greaterThanOrEqualTo()、lessThanOrEqualTo()
equalTo()、notEqualTo()
contains()、beginsWith()、endsWith()
isNull()、isNotNull()
isEmpty()、isNotEmpty()
RealmResults<User> result = realm.where(User.class)
.between("age", 0, 99)
.findAll(); // 執行查詢
User user = realm.where(User.class)
.equalTo("name", "John", Case.INSENSITIVE) // 忽略大小寫
.findFirst(); // 執行查詢</code></pre> </li>
-
關聯查詢
realmresults<user> users = realm.where(user.class)
.equalto("dogs.name", "fluffy") // 關聯查詢,以“.”分隔
.equalto("dogs.color", "brown") // 條件與
.findall();
realmresults<user> users = realm.where(user.class)
.equalto("dogs.name", "fluffy")
.findall()
.where() // 在結果中繼續查詢
.equalto("dogs.color", "brown")
.findall();</code></pre> </li>
-
邏輯運算符
or()、beginGroup()、endGroup()
RealmResults<User> results = realm.where(User.class)
.greaterThan("age", 10) // 大于等于
.beginGroup() // 左括號
.equalTo("name", "Peter")
.or() // 或,如果不加此操作符,默認為于
.contains("name", "Jo")
.endGroup() // 左右括號
.findAll();
-
排序
RealmResults<User> results = realm.where(User.class).findAll();
results = result.sort("age"); // 升序
results = result.sort("age", Sort.DESCENDING); // 降序
RealmResults<User> results = realm.where(User.class)
.findAllSorted("age", Sort.DESCENDING); // 降序</code></pre> </li>
-
聚合
RealmResults<User> results = realm.where(User.class).findAll();
long sum = results.sum("age").longValue();
long min = results.min("age").longValue();
long max = results.max("age").longValue();
double average = results.average("age");
long matches = results.size();
-
異步
</ul>
步查詢需要使用Handler來傳遞查詢結果。在沒有 Looper 的線程中使用異步查詢會導致 IllegalStateException 異常被拋出。
Listener 只工作于 Looper 線程。對于非 Looper 線程請使用 Realm.waitForChange()
private RealmResults<User> results;
public void onStart() {
realm = Realm.getDefaultInstance();
// 立刻返回一個 RealmResults<User>,當其完成時,RealmResults 實例會被更新
results = realm.where(User.class).findAllAsync();
realm.addChangeListener(listener); // Realm 注冊監聽
results.addChangeListener(listener); // 結果注冊監聽
if (results.isLoaded()) {
// 完成加載執行
}
results.load(); // 阻塞線程指導異步完成
}
public void onStop () {
realm.removeChangeListener(listener); // Realm移除監聽
realm.removeAllChangeListeners(); // Realm移除所有監聽
results.removeChangeListener(listener); // 結果移除監聽
results.removeChangeListeners(); // 結果移除所有監聽
}
private RealmChangeListener listener = new RealmChangeListener<RealmResults<User>>() {
@Override
public void onChange(RealmResults<User> results) {
// 在 Looper 線程,每次更新后執行
// 非 Looper 線程,使用 Realm.waitForChange()
}
};</code></pre>
關閉
Realm 實例是基于引用計數的, 調用 getInstance() 獲取了幾次實例,就需要調用 close() 關閉幾次
UI 線程外的 Looper 線程
public class MyThread extends Thread {
private Realm realm;
public void run() {
Looper.prepare();
try {
realm = Realm.getDefaultInstance();
//...
Lopper.loop();
} finally {
if (realm != null) {
realm.close();
}
}
}
}</code></pre>
// AsyncTask
protected Void doInBackground(Void... params) {
Realm realm = Realm.getDefaultInstance();
try {
// ...
} finally {
realm.close();
}
return null;
}</code></pre>
new Thread(new Runnable() {
@Override
public void run() {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
// ...
} finally {
if (realm != null) {
realm.close();
}
}
}
}).start();
注意
- 基本數據類型永遠不能為空, RealmObject 數據類型永遠可以為空
- 目前不支持 final 、 transient 和 volatile 修飾的成員變量
- 支持使用遞歸關系,但要注意死循環的問題, Realm 不會檢查 RealmList 的循環嵌套
- 設置一個類型為 RealmList 的屬性為空值(null)會清空該列表,即列表長度變為0。但并不會刪除列表中的任何 RealmObject
- 在沒有 Looper 的線程中使用異步查詢會導致 IllegalStateException 異常被拋出。
進階用法
JSON
JSON 包含空值(null)屬性,創建更新對象,對象屬性不可為空時拋出異常
Json 和對象的屬性不同的,對象屬性不變
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
Dog dog = realm.createObjectFromJson(Dog.class, "{\"name\": \"dog\"}");
}
});
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
try {
InputStream is = getAssets().open("user.json");
realm.createAllFromJson(User.class, is);
} catch (IOException e) {
e.printStackTrace();
}
}
});</code></pre>
DynamicRealm
某些數據模型在編譯期是無法獲得的。例如在處理數據遷移(migration)或CSV文件的時候,此時使用 DynamicRealm 可以在沒有 RealmObject 子類的情況下操作 Realm 數據
RealmConfiguration realmConfig = new RealmConfiguration.Builder().build();
DynamicRealm realm = DynamicRealm.getInstance(realmConfig);
// 創建 DynamicRealmObject 實例
DynamicRealmObject user = realm.createObject("User");
// 通過字符串訪問數據,而不是 RealmObject 的定義
String name = person.getString("name");
int age = person.getInt("age");
// DynamicRealm 會忽略 schema、migration 以及 schema 版本的檢查,但結構依然存在。獲取不存在的屬性會報異常
person.getString("I don't exist");
// 查詢工作相同
RealmResults<DynamicRealmObject> users = realm.where("User")
.equalTo("name", "John")
.findAll();</code></pre>
schema 結構
-
Realm 使用所有項目中的 Realm 模型類來創建 schema。但這個行為是可以改變的,例如,你可以通過使用 RealmModule 讓 Realm 只包含所有模型類的一個子集。
@RealmModule(classes = { User.class, Dog.class })
public class MyModule {
}
RealmConfiguration config = new RealmConfiguration.Builder()
.modules(new MyModule()) // 設置使用的 schema
.build();
RealmConfiguration config = new RealmConfiguration.Builder()
.modules(new MyModule(), new MyOtherModule()) // 可以設置多個 schema
.build();</code></pre>
-
在庫中使用到的 Realm 必須通過 RealmModule 來暴露和使用其 schema。
// 庫必須使用 library = true,以阻止默認創建。
// allClasses = true,即為使用所有
@RealmModule(library = true, allClasses = true)
public class MyLibraryModule {
}
// 庫需要確切的設置 RealmModule
RealmConfiguration libraryConfig = new RealmConfiguration.Builder()
.name("library.realm")
.modules(new MyLibraryModule())
.build();
// Apps 中添加庫的 RealmModule
RealmConfiguration config = new RealmConfiguration.Builder()
.name("app.realm")
.modules(Realm.getDefaultModule(), new MyLibraryModule())
.build();</code></pre>
數據庫升級
-
不保存舊數據
RealmConfiguration config = new RealmConfiguration.Builder()
.deleteRealmIfMigrationNeeded()
.build()
-
數據遷移
RealmConfiguration config = new RealmConfiguration.Builder()
.schemaVersion(2) // 結構改變時增加,默認初始值為0
.migration(migration) // 數據遷移方案
.build()
RealmMigration migration = new RealmMigration() {
@Override
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
// 動態 Realm 獲取數據庫結構
RealmSchema schema = realm.getSchema();
// 遷移版本 1: 增加一個類
// Example:
// public User extends RealmObject {
// private String name;
// private int age;
// // getters and setters left out for brevity
// }
if (oldVersion == 0) {
schema.create("User")
.addField("name", String.class)
.addField("age", int.class);
oldVersion++;
}
// 遷移版本 2: 增加一個主鍵和對象引用
// Example:
// public Person extends RealmObject {
// @PrimaryKey
// private long id;
// private String name;
// private int age;
// private Dog favoriteDog;
// private RealmList<Dog> dogs;
// // getters and setters left out for brevity
// }
if (oldVersion == 1) {
schema.get("User")
.addField("id", long.class, FieldAttribute.PRIMARY_KEY) // 增加主鍵熟悉“id”
.addRealmObjectField("favoriteDog", schema.get("Dog")) // 增加對象
.addRealmListField("dogs", schema.get("Dog")); // 增加對象列表
oldVersion++;
}
}
}</code></pre> </li>
</ul>
加密
Realm 文件可以通過傳遞一個512位(64字節)的密鑰參數給 Realm.getInstance().encryptionKey() 來加密存儲在磁盤上
byte[] key = new byte[64];
new SecureRandom().nextBytes(key);
RealmConfiguration config = new RealmConfiguration.Builder()
.encryptionKey(key)
.build();
Realm realm = Realm.getInstance(config);</code></pre>
高階用法
Android相關
-
Adapter
ListView 使用 RealmBaseAdapter , RecyclerViews 使用 RealmRecyclerViewAdapter
dependencies {
compile 'io.realm:android-adapters:1.4.0'
}
-
Intent
RealmObject 不能通過 Intent 傳遞,可以通過傳遞屬性然后再查詢
-
AsyncTask
private class DownloadOrders extends AsyncTask<Void, Void, Long> {
@Override
protected Long doInBackground(Void... voids) {
// 后臺子線程,獲取使用并關閉 Realm
Realm realm = Realm.getDefaultInstance();
try {
realm.createAllFromJson(Order.class, api.getNewOrders());
Order firstOrder = realm.where(Order.class).findFirst();
long orderId = firstOrder.getId();
return orderId;
} finally {
realm.close();
}
}
@Override
protected void onPostExecute(Long orderId) {
// 返回主線程,通過id查詢對象,進行操作
}
}</code></pre> </li>
-
IntentService
ChangeListener 在 IntentService 中不會工作。盡管 IntentService 本身是一個 Looper 線程,但每次 onHandleIntent 的調用是獨立的事件。你可以注冊監聽器的調用不會返回失敗,但他們永遠不會被觸發。
public class OrdersIntentService extends IntentService {
public OrdersIntentService(String name) {
super("OrdersIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
// 后臺子線程,獲取使用并關閉 Realm
Realm realm = Realm.getDefaultInstance();
realm.createAllFromJson(Order.class, api.getNewOrders());
Order firstOrder = realm.where(Order.class).findFirst();
long orderId = firstOrder.getId();
realm.close();
}
}</code></pre> </li>
</ul>
Retrofit
GitHubService service = restAdapter.create(GitHubService.class);
List<Repo> repos = service.listRepos("octocat");
// Retrofit 獲取的對象轉換成 Realm 對象
realm.beginTransaction();
List<Repo> realmRepos = realm.copyToRealmOrUpdate(repos);
realm.commitTransaction();</code></pre>
RxJava
Realm 、 RealmResults 、 RealmObject 、 DynamicRealm 、 DynamicRealmObject 可以轉化為 Observable
Realm realm = Realm.getDefaultInstance();
GitHubService api = retrofit.create(GitHubService.class);
// 組合 Realm, Retrofit 和 RxJava (使用 Retrolambda),
realm.where(Person.class)
.isNotNull("username")
.findAllAsync()
.asObservable() // 轉化為 Observable
.filter(persons.isLoaded)
.flatMap(persons -> Observable.from(persons))
.flatMap(person -> api.user(person.getGithubUserName()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(user -> showUser(user));
Parceler
compile "org.parceler:parceler-api:1.0.3"
apt "org.parceler:parceler:1.0.3"
// 項目編譯完成,RealmObject 轉化成 RealmProxy
@Parcel(implementations = { UserRealmProxy.class },
value = Parcel.Serialization.BEAN,
analyze = { User.class })
public class User extends RealmObject {
// ...
}
-
如果你的模型包含 RealmList,那么你需要注冊一個特殊 adapter
-
一旦對象被打包(parcelled),它將變為一個有當前數據快照,不再被 Realm 管理的一個 unmanaged 對象。之后該對象的數據變化不會被 Realm 寫入。
來自:http://www.jianshu.com/p/95f62e739d1b