ObjectBox 簡介
ObjectBox 官網:http://objectbox.io/
以前開發專案的時候 ORM 一直用的是 GreenDao ,這次新開專案的時候訪問 GreenDao 的官網的時候卻發現了一行新的 Note: for new apps we recommend ObjectBox, a new object-oriented database that is much faster than SQLite and easier to use. For existing apps based on greenDAO we offer DaoCompat for an easy switch (see also the announcement).
看來 GreenDao 這個濃眉大眼的也準備叛變革命了。。。
好久沒有正兒八經地搞應用開發了,新的輪子已經出現,怎麼能夠停止不前,研究之,那麼這個 ObjectBox 到底是何方神聖呢?
瞅一眼官網簡介:ObjectBox is a super fast mobile database that persists objects. It lets you avoid many repetitive tasks and offers a simple interface to your data. 翻譯一下就是:更快,更簡單。翻了一下 FAQ,對比了一下 ObjectBox 和 谷歌兩位親兒子 Realm 和 Room ,如下圖:
從圖上可以看出除了在載入 100k 的大量資料的時候 ObjectBox 的速度慢於 Realm,在執行其他資料操作的時候 ObjectBox 的效能對其他兩位都是近乎碾壓式的存在。
在引入後對 apk 包的大小影響方面,ObjectBox 和 Realm 分別在 1-1.5MB 和 3-4MB ,Room 因為是對 SQLite 的封裝,只有 50KB 左右。而在增加的方法數量方面,Room 的 300 個方法也遠少於 Room 的 2000 個方法和 ObjectBox 的 1300 個方法。關於三者的對比,可以看這篇文章:https://notes.devlabs.bg/realm-objectbox-or-room-which-one-is-for-you-3a552234fd6e 。
如果不考慮對包的體積大小的影響,只考慮效能的話,似乎就有了選擇 ObjectBox 的理由。
ObjectBox 整合
Note : 通常我選擇 ORM 的首要條件就是支援 RxJava(是時候展示我多年RxJava腦殘粉的身份了),然鵝,ObjectBox 的團隊似乎對 RxJava 不太感冒,主要是介意引入RxJava 之後急劇增加的包體積和方法數,所以 ObjectBox 自己封裝了一套支援 Reactive Extensions 的介面。
首先,在 Project 級別的 build.gradle 檔案裡新增如下指令碼:
buildscript {
ext.objectboxVersion = '1.5.0'
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.1'
classpath "io.objectbox:objectbox-gradle-plugin:$objectboxVersion"
}
}
複製程式碼
然後,在 Module 級別的 build.gradle 檔案裡新增如下指令碼:
dependencies {
// 這一句是新增 RxJava 擴充套件
compile 'io.objectbox:objectbox-rxjava:0.9.8'
// 下面這兩句是 ObjectBox 很騷氣的一個功能——DataBrowser, 通過瀏覽器來除錯和瀏覽資料庫的資料
debugImplementation "io.objectbox:objectbox-android-objectbrowser:$objectboxVersion"
releaseImplementation "io.objectbox:objectbox-android:$objectboxVersion"
}
// ObjectBox browser dependencies must be set before applying the plugin so it does not add objectbox-android
// (would result in two conflicting versions, e.g. "Duplicate files copied in APK lib/armeabi-v7a/libobjectbox.so").
apply plugin: 'io.objectbox'
複製程式碼
注意這裡的 apply plugin: 'io.objectbox' 一定要新增到 dependencies 模組後面(已經踩過這個坑了,直接按照官網的 Get Started 的整合方式有點問題)。
ObjectBox 簡單用法
在 Application 中初始化:
public static final String TAG = "ObjectBoxExample";
public static final boolean EXTERNAL_DIR = false;
private BoxStore boxStore;
@Override
public void onCreate() {
super.onCreate();
boxStore = MyObjectBox.builder().androidContext(App.this).build();
if (BuildConfig.DEBUG) {
new AndroidObjectBrowser(boxStore).start(this);
}
Log.d("App", "Using ObjectBox " + BoxStore.getVersion() + " (" +
BoxStore.getVersionNative() + ")");
}
public BoxStore getBoxStore() {
return boxStore;
}
複製程式碼
實體類格式(最簡單的只要加兩個註解就夠了,更詳細的用法可以參考官方文件):
package io.objectbox.example;
import java.util.Date;
import io.objectbox.annotation.Entity;
import io.objectbox.annotation.Generated;
import io.objectbox.annotation.Id;
import io.objectbox.annotation.apihint.Internal;
@Entity
public class Note {
// 注意這裡的 @Id 註解是必須的,和 GreenDao 不同,GreenDao 可以省略,但是如果你的業務欄位已經有了 一個名字為 id 的欄位,可以取一個別的名字啊~
@Id
long boxId;
String text;
String comment;
Date date;
public Note(long id, String text, String comment, Date date) {
this.boxId = id;
this.text = text;
this.comment = comment;
this.date = date;
}
public Note() {
}
public long getId() {
return this.boxId;
}
public void setId(long id) {
this.boxId = id;
}
public String getText() {
return this.text;
}
public void setText(String text) {
this.text = text;
}
public String getComment() {
return this.comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public Date getDate() {
return this.date;
}
public void setDate(Date date) {
this.date = date;
}
}
複製程式碼
在 Activity 執行查詢(多餘的業務程式碼已經被我省略):
public class NoteActivity extends Activity {
private Box<Note> notesBox;
private Query<Note> notesQuery;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
BoxStore boxStore = ((App) getApplication()).getBoxStore();
notesBox = boxStore.boxFor(Note.class);
// query all notes, sorted a-z by their text
(http://greenrobot.org/objectbox/documentation/queries/)
notesQuery = notesBox.query().order(Note_.text).build();
updateNotes();
}
/** Manual trigger to re-query and update the UI. For a reactive alternative check {@link ReactiveNoteActivity}. */
private void updateNotes() {
List<Note> notes = notesQuery.find();
}
private void addNote() {
Note note = new Note();
note.setText(noteText);
note.setComment(comment);
note.setDate(new Date());
notesBox.put(note);
Log.d(App.TAG, "Inserted new note, ID: " + note.getId());
}
}
複製程式碼
ObjectBox 的 Reactive 用法
同樣在 Activity 中執行查詢:
/** An alternative to {@link NoteActivity} using a reactive query (without RxJava, just plain ObjectBox API). */
public class ReactiveNoteActivity extends Activity {
private Box<Note> notesBox;
private Query<Note> notesQuery;
private DataSubscriptionList subscriptions = new DataSubscriptionList();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
notesBox = ((App) getApplication()).getBoxStore().boxFor(Note.class);
// query all notes, sorted a-z by their text
// (http://greenrobot.org/objectbox/documentation/queries/)
notesQuery = notesBox.query().order(Note_.text).build();
// Reactive query (http://greenrobot.org/objectbox/documentation/data-observers- reactive-extensions/)
notesQuery.subscribe()
.onError(new ErrorObserver() {
@Override
public void onError(Throwable th) {
}
})
// 官方推薦的做法是對 data observers 持有弱引用,防止忘記 cancel subscriptions,
// 但是最好還是記得及時 cancel subscriptions(例如在 onPause、onStop 或者
// onDestroy 方法中)
.weak()
.on(AndroidScheduler.mainThread())
.observer(new DataObserver<List<Note>>() {
@Override
public void onData(List<Note> notes) {
// 只要資料庫裡的資料發生了變化,這裡的方法就會被回撥執行,相當智慧。。。
// 業務程式碼
}
});
}
@Override
protected void onDestroy() {
subscriptions.cancel();
super.onDestroy();
}
private void addNote() {
Note note = new Note();
note.setText(noteText);
note.setComment(comment);
note.setDate(new Date());
notesBox.put(note);
Log.d(App.TAG, "Inserted new note, ID: " + note.getId());
}
}
複製程式碼
上面的用法看上去就像傻瓜版的 RxJava,上手容易,概念理解也簡單,但是並沒有 RxJava那麼強大的功能,所以如果在應對更復雜的業務邏輯的時候,還是需要引入 RxJava ,示例如下:
Query query = box.query().build();
RxQuery.observable(query).subscribe(this);
複製程式碼
RxQuery 可以使用 Flowable、Observable、Single 來訂閱查詢結果,目前 ObjectBox 只支援 RxJava 2 。
除錯
新增許可權
<uses-permission android:name="android.permission.INTERNET" />
複製程式碼
在 Application 開啟除錯
boxStore = MyObjectBox.builder().androidContext(App.this).build();
if (BuildConfig.DEBUG) {
boolean started = new AndroidObjectBrowser(boxStore).start(this);
Log.i("ObjectBrowser", "Started: " + started);
}
複製程式碼
執行命令
adb forward tcp:8090 tcp:8090
複製程式碼
在電腦瀏覽器中訪問
http://localhost:8090/index.html
複製程式碼
效果賊6:
問題
最關鍵的問題是,如果 put、find 這些方法全是同步的,對於大量資料的存和查都是耗時操作,如果直接寫在主執行緒會阻塞主執行緒,尤其是 find 方法,而 ObjectBox 的 Reactive 封裝顯然沒有 RxJava 那麼強大,GreenDao對RxJava的支援非常好,如果要封裝資料庫框架的話進行執行緒切換非常方便,但是我在ObjectBox的官方文件裡暫時還沒發現對各個方法的執行執行緒的明確說明。我已經在 Github 提了 issue ,不過還沒人回我,有待繼續研究。
體會
整合方便簡單,除錯效果拔群,終於不用再用 DDMS + SQLite Export 除錯了(每次手動導資料那叫一個痛苦,後來聽說加了一個自動同步的功能,沒細研究,不過跟 ObjectBox 的 DataBroswer 是沒法比),除錯效果很棒,不用寫 SQL 。但是感覺仍有繼續改進的空間,簡單化的同時也犧牲了一部分功能的強大和靈活性,相比較而言可能 GreenDao 會更成熟一點,當然官網也提供了從 GreenDao 轉到 ObjectBox 的方法。第一次的使用體驗還是很不錯的,有機會的話可以研究一下原始碼,探究一下高效能的原因。