ObjectBox 整合指南

Doctor明發表於2018-06-09

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 ,如下圖:

ObjectBox 整合指南

從圖上可以看出除了在載入 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:

ObjectBox 整合指南

問題

​ 最關鍵的問題是,如果 put、find 這些方法全是同步的,對於大量資料的存和查都是耗時操作,如果直接寫在主執行緒會阻塞主執行緒,尤其是 find 方法,而 ObjectBox 的 Reactive 封裝顯然沒有 RxJava 那麼強大,GreenDao對RxJava的支援非常好,如果要封裝資料庫框架的話進行執行緒切換非常方便,但是我在ObjectBox的官方文件裡暫時還沒發現對各個方法的執行執行緒的明確說明。我已經在 Github 提了 issue ,不過還沒人回我,有待繼續研究。

體會

​ 整合方便簡單,除錯效果拔群,終於不用再用 DDMS + SQLite Export 除錯了(每次手動導資料那叫一個痛苦,後來聽說加了一個自動同步的功能,沒細研究,不過跟 ObjectBox 的 DataBroswer 是沒法比),除錯效果很棒,不用寫 SQL 。但是感覺仍有繼續改進的空間,簡單化的同時也犧牲了一部分功能的強大和靈活性,相比較而言可能 GreenDao 會更成熟一點,當然官網也提供了從 GreenDao 轉到 ObjectBox 的方法。第一次的使用體驗還是很不錯的,有機會的話可以研究一下原始碼,探究一下高效能的原因。

歡迎關注我的公眾號:cmder

ObjectBox 整合指南

相關文章