Realm(Java)數據庫使用文檔(目錄)
Realm使得在多個線程上處理數據變得毫不費力,而不必擔心一致性或性能,因爲 對象 objects 和 查詢 queries 始終在自動更新。您可以對不同線程中的活動對象進行操作,對其進行讀寫操作,而無需擔心其他線程對這些相同對象所做的事情。 如果需要更改數據,則可以使用事務。另一個線程中的其他對象將近乎實時地更新(更新將作爲事件在Looper上進行調度,因此Looper線程將在事件處理後立即進行更新)。
唯一的限制是您不能在線程之間隨機傳遞Realm對象。如果在另一個線程上需要相同的數據,則需要在另一個線程上查詢該數據。此外,您可以使用Realms反應式體系結構觀察更改。請記住,所有對象在線程之間都是最新的-數據更改時,Realm會通知您。
13.1 線程示例
假設我們有一個顯示客戶列表的應用程序。在後臺線程(Android IntentService)中,我們輪詢新客戶的遠程端點,然後將其保存到Realm。 當後臺線程添加新客戶時,UI線程中的數據將自動更新。UI線程通過RealmChangeListener通知,此時我們告訴UI小部件進行自我更新。無需重新查詢,因爲Realm可以使所有內容保持最新。
// in a Fragment or Activity, etc
// 只要不對查詢結果進行垃圾回收,就只會觸發偵聽器,因此請保留對其的強大類引用。
private RealmResults<Customer> customers;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
// ... 爲簡潔起見,省略了樣板
realm = Realm.getDefaultInstance();
// get all the customers
customers = realm.where(Customer.class).findAllAsync();
// ... build a list adapter and set it to the ListView/RecyclerView/etc
// set up a Realm change listener
changeListener = new RealmChangeListener() {
@Override
public void onChange(RealmResults<Customer> results) {
// 每當Realm數據庫在任何線程上更改時,都會調用此方法。
// 請注意,更改偵聽器僅在Looper線程上起作用。
// 對於非循環線程,您必須手動使用Realm.waitForChange()。
updateUi();
}
};
// 當客戶結果發生變化(添加、刪除、更新的項目等)時,告訴Realm通知我們的監聽器。
customers.addChangeListener(changeListener);
}
// In a background service, in another thread
public class PollingService extends IntentService {
@Override
public void onHandleIntent(Intent intent) {
Realm realm = Realm.getDefaultInstance();
try {
// 去做一些網絡電話/等,並獲取一些數據並將其填充到“ json”變量中
String json = customerApi.getCustomers();
realm.beginTransaction();
realm.createObjectFromJson(Customer.class, json); // Save a bunch of new Customer objects
realm.commitTransaction();
// 此時,UI線程中的數據已經是最新的。
// ...
} finally {
realm.close();
}
}
// ...
}
後臺服務將新Customer添加到realm後,Customer列表將在UI中自動更新,而無需您進行任何其他干預。單個對象也是如此。假設您只管理一個對象。只需在一個線程上進行更改,UI線程就會自動擁有新數據。如果您需要對這一更改做出迴應,只需像上面一樣添加一個偵聽器即可。
13.2 跨線程使用Realm
跨線程使用Realm的唯一規則是記住Realm,RealmObject和RealmResults實例不能跨線程傳遞。而是使用異步查詢或異步事務將操作轉移到後臺線程,並將所有結果返回給您。
當您想從其他線程訪問相同數據時,可以獲取一個新的Realm實例(即Realm.getInstance(RealmConfiguration config)或其重構方法),並通過查詢獲取對象。
這些對象將映射到磁盤上的相同數據,並且可以從任何線程讀取和寫入。
13.3 Android框架線程
使用這些類時要小心:
AsyncTask
IntentService
AsyncTask
類包含執行後臺線程的doInBackground()
方法。 IntentService
類包含在工作線程中執行的onHandleIntent(Intent intent)
方法。
如果您需要在這兩種方法中使用Realm,則應打開Realm,執行工作,然後在退出之前關閉Realm。 以下是幾個示例。
13.3.1 異步任務
如下所示,在doInBackground方法中打開和關閉Realm。
private class DownloadOrders extends AsyncTask<Void, Void, Long> {
protected Long doInBackground(Void... voids) {
// Now in a background thread.
// Open the Realm
Realm realm = Realm.getDefaultInstance();
try {
// Work with Realm
realm.createAllFromJson(Order.class, api.getNewOrders());
Order firstOrder = realm.where(Order.class).findFirst();
long orderId = firstOrder.getId(); // Id of order
return orderId;
} finally {
realm.close();
}
}
protected void onPostExecute(Long orderId) {
// 回到主線程,使用orderId進行操作,例如查詢Realm以獲取訂單並執行一些操作。
}
}
13.3.2 IntentService
ChangeListeners
不能在IntentService
中工作。 即使是Looper
線程,每次onHandleIntent
的調用都是一個單獨的事件,不會“循環”。這意味着可以註冊更改偵聽器,但是它們永遠不會被觸發。
如下所示,通過onHandleIntent方法打開和關閉Realm。
public class OrdersIntentService extends IntentService {
public OrdersIntentService(String name) {
super("OrdersIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
// Now in a background thread.
// Open the Realm
Realm realm = Realm.getDefaultInstance();
try {
// Work with Realm
realm.createAllFromJson(Order.class, api.getNewOrders());
Order firstOrder = realm.where(Order.class).findFirst();
long orderId = firstOrder.getId(); // Id of order
} finally {
realm.close();
}
}
}
13.4 多進程支持
可以從多個進程訪問realm,但有一些限制。當從同一APK中的不同進程訪問同一Realm時,包括通知在內的所有內容均應正常工作。
13.5 凍結對象
該功能是Realm 7.0版以來的新增功能,目前(翻譯時間爲2020.3.29)處於測試階段。您正在查看Realm beta版本7.0.0-beta的文檔。要查看最新的穩定版文檔,請參閱Realm 6.0.2。
儘管在許多情況下實時更新線程受限的Realm Objects效果很好,但在某些情況下,例如, 當Realm是基於事件流的體系結構的一部分時,在該對象中,可以從多個線程讀取對象,然後最終以處理後的形式將其發送到UI線程。在這些情況下,凍結Realm、RealmResults、RealmList或Realm對象可能是有益的。
凍結Realm會返回一個不變的版本,可以從任何線程讀取和查詢該版本。來自凍結Realm的所有派生對象也將凍結。
Realm liveRealm = Realm.getDefaultInstance();
Realm frozenRealm = liveRealm.freeze();
new Thread(() -> {
RealmResults<Person> frozenPersons = frozenRealm.where(Person.class).findAll();
frozenPersons.isFrozen(); // true
Person frozenPerson = frozenPersons.first();
frozenPerson.isFrozen(); // true
}).start();
最常見的模式是將更改偵聽器附加到實時查詢結果,然後凍結結果,然後再傳遞給進一步處理:
Realm liveRealm = Realm.getDefaultInstance();
RealmResults<Person> liveResults = liveRealm.where(Person.class).findAllAsync();
liveResults.addChangeListener(result -> {
doExpensiveWorkOnOtherThread(result.freeze());
});
無法在凍結的Realm上開始寫事務,修改凍結的對象或向其添加更改偵聽器。所有這些方法都將引發IllegalStateException。要修改凍結的對象,請在實時Realm中對其進行查詢,然後對其進行修改。
Realm liveRealm = Realm.getDefaultInstance();
Person frozenPerson = liveRealm.where(Person.class).findFirst().freeze();
frozenPerson.addChangeListener(listener); // Throws
new Thread(() -> {
Realm bgLiveRealm = Realm.getDefaultInstance();
bgLiveRealm.beginTransaction();
frozenPerson.setName("Jane"); // Throws
Person bgLivePerson = bgLiveRealm.where(Person.class).equalTo("id", frozenPerson.getId()).findFirst();
bgLivePerson.setName("Jane");
bgLiveRealm.commitTransaction();
bgLiveRealm.close();
}).start();
一旦凍結,就無法解凍對象。使用isFrozen()查詢對象的狀態。即使對於其他線程限制的Realm對象,此方法也是線程安全的。
Realm liveRealm = Realm.getDefaultInstance();
Realm frozenRealm = liveRealm.freeze();
liveRealm.isFrozen(); // false;
frozenRealm.isFrozen(); // true
new Thread(() -> {
liveRealm.isFrozen(); // false;
frozenRealm.isFrozen(); // true
}).start();
只要產生凍結對象的活動Realm保持打開狀態,凍結對象就可用。在使用凍結的對象完成所有線程之前,必須注意不要關閉實時Realm。在關閉實時Realm之前,可以關閉凍結的Realm。
Realm liveRealm = Realm.getDefaultInstance();
Realm frozenRealm = liveRealm.freeze();
Person frozenPerson = frozenRealm.where(Person.class).findFirst();
frozenPerson.isValid(); // true
// 關閉實時Realm,也關閉凍結的數據
liveRealm.close();
frozenPerson.isValid(); // false
請注意,緩存過多的凍結對象可能會對文件大小產生負面影響。
有關更多信息,請參見此處。