1. 程式人生 > >Realm原始碼分析之Writes

Realm原始碼分析之Writes

前言

上篇是Realm原始碼分析的開篇,是關於Realm資料庫的初始化,關鍵一點是要抓住RealmProxyMediator這個代理中介者的例項化過程。在理解了Realm初始化之後,接下來就可以追蹤Realm資料庫讀寫等操作的原始碼了。因篇幅限制,先分析Writes。

Realm Writes

  1. 基礎篇中提到過凡是繼承RealmObject的物件在Realm中被稱作Model,它對應著Realm資料庫中的一張表,而Model中的屬性對映著Realm資料庫表中的列。

  2. 先建立一個的Model類,後面會以這個Model來分析Realm資料庫的讀寫相關的操作,其程式碼如下

    public class
    Dog extends RealmObject {
    private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this
    .age = age; } }
  3. 檢視RealmObject原始碼,它的註釋很詳細,就不再贅述。注意到它是個抽象類,並實現了RealmModel與ManagableObject兩個介面。它還提供了不少方法,目前我們先忽略這些細節。另外RealmObject還使用RealmClass註解,推測應該會在自動生成程式碼的過程中被使用。如下縮略程式碼:

    一大堆的註釋
    ... ...
    @RealmClass
    public abstract class RealmObject implements RealmModel, ManagableObject {
        ... ...
    }
  4. 再看RealmModel,它是個空介面,註釋說得很明白:實現這個介面的並標註RealmClass註解就能由註解處理器自動生成代理類。因此,我們可以推測自己寫的這個Dog類,應該會被註解處理器自動生成一個代理類。

    /**
    * Note: Object implementing this interface needs also to be annotated with {@link RealmClass}, 
    * so the annotation processor can generate the underlining proxy class.
    */
    public interface RealmModel {
    }
  5. 而ManagableObject介面,按註釋的說法是用於判斷RealmObject物件例項的合法性,其程式碼如下:

     /**
     * This internal interface represents a java object that corresponds to data
     * that may be managed in the Realm core. It specifies the operations common to all such objects.
     */
    public interface ManagableObject {
        boolean isManaged();
        boolean isValid();
    }
  6. 在瞭解了RealmObject類關係結構之後,再來建立RealmObject例項,程式碼如下:

    public void test() {
        realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                Dog myDog = realm.createObject(Dog.class);
                myDog.setName("Fido");
                myDog.setAge(1);
            }
        });
    }
  7. 上面的executeTransaction方法會同步執行事務操作,在回撥方法中就完成了Dog表的建立。先看看createObject方法:

    public <E extends RealmModel> E createObject(Class<E> clazz) {
        checkIfValid();
        return createObjectInternal(clazz, true, Collections.<String>emptyList());
    }
  8. createObject做了兩件事:一是Realm執行環境檢查,而是呼叫了createObjectInternal:

    /**
     * Same as {@link #createObject(Class)} but this does not check the thread.
     *
     * @param clazz the Class of the object to create.
     * @param acceptDefaultValue if {@code true}, default value of the object will be applied and
     * if {@code false}, it will be ignored.
     * @return the new object.
     * @throws RealmException if the primary key is defined in the model class or an object cannot be created.
     */
    // Called from proxy classes.
    <E extends RealmModel> E createObjectInternal(
            Class<E> clazz,
            boolean acceptDefaultValue,
            List<String> excludeFields) {
        Table table = schema.getTable(clazz);
        // Checks and throws the exception earlier for a better exception message.
        if (table.hasPrimaryKey()) {
            throw new RealmException(String.format(Locale.US, "'%s' has a primary key, use" +
                    " 'createObject(Class<E>, Object)' instead.", table.getClassName()));
        }
        return configuration.getSchemaMediator().newInstance(clazz, this,
                OsObject.create(table),
                schema.getColumnInfo(clazz),
                acceptDefaultValue, excludeFields);
    }
  9. 注意到createObjectInternal方法前面的最後一行註釋,說這個方法會被代理物件呼叫。它主要做了兩件事:一是建立了一張Table,直接呼叫Mediator建立目標物件。先看Table的類繼承關係:

    /**
     * This class is a base class for all Realm tables. The class supports all low level methods
     * (define/insert/delete/update) a table has. All the native communications to the Realm C++ 
     * library are also handled by this class.
     */
    public class Table implements TableSchema, NativeObject {
    ... ...
    }
    
    public interface TableSchema {
        long addColumn(RealmFieldType type, String name);
        void removeColumn(long columnIndex);
        void renameColumn(long columnIndex, String newName);
    }
    
    /**
     * This abstract class represents a native object from core.
     * It specifies the operations common to all such objects.
     * All Java classes wrapping a core class should implement NativeObject.
     */
    public interface NativeObject {
        long NULLPTR = 0L;
        long getNativePtr();
        long getNativeFinalizerPtr();
    }
  10. 從上述原始碼與註釋,就可以將Table當作讀寫Realm Native層資料庫的API。繼續看Table的例項化程式碼:

    Table getTable(Class<? extends RealmModel> clazz) {
        //classToTable是個快取,因此首次建立Table是會執行到下面的程式碼。
        Table table = classToTable.get(clazz);
        if (table != null) { 
            return table; 
        }
        //獲取原始的ModelClass,即直接繼承RealmObject的那個model類,這裡的例項是Dog.class
        Class<? extends RealmModel> originalClass = Util.getOriginalModelClass(clazz);
        if (isProxyClass(originalClass, clazz)) {
            // If passed 'clazz' is the proxy, try again with model class.
            table = classToTable.get(originalClass);
        }
        //首次例項化會走到這裡,最終是其實是SharedRealm例項化了Table並快取起來,如下getTable原始碼
        if (table == null) {
            table = realm.getSharedRealm()
            .getTable(realm.getConfiguration().getSchemaMediator().getTableName(originalClass));
            classToTable.put(originalClass, table);
        }
        if (isProxyClass(originalClass, clazz)) {
            // 'clazz' is the proxy class for 'originalClass'.
            classToTable.put(clazz, table);
        }
    
        return table;
    }
    public final class SharedRealm implements Closeable, NativeObject {
       ... ...
       public Table getTable(String name) {
            //至此就Table就與Realm native資料庫關聯起來
            long tablePtr = nativeGetTable(nativePtr, name);
            return new Table(this, tablePtr);
        }
    
        // Throw IAE if the table doesn't exist.
        private static native long nativeGetTable(long nativeSharedRealmPtr, String tableName);
    }
  11. Table的建立流程到此為止,回到9再看Mediator是如何建立目標Model物件的。基礎篇已經說過這個Mediator物件其實就是註解處理器知道生成的DefaultRealmModuleMediator,在我的例子中程式碼如下:

    @Override
    public <E extends RealmModel> E newInstance(Class<E> clazz, Object baseRealm, Row row, ColumnInfo columnInfo, boolean acceptDefaultValue, List<String> excludeFields) {
        final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get();
        try {
            objectContext.set((BaseRealm) baseRealm, row, columnInfo, acceptDefaultValue, excludeFields);
            checkClass(clazz);
    
            if (clazz.equals(com.example.app.org.realm.demo.Dog.class)) {
                return clazz.cast(new io.realm.DogRealmProxy());
            }
            throw getMissingProxyClassException(clazz);
        } finally {
            objectContext.clear();
        }
    }
  12. 注意上面方法中的Row例項的是由OsObject.create(table)建立的,看看這個方法的原始碼:

    public static UncheckedRow create(Table table) {
        final SharedRealm sharedRealm = table.getSharedRealm();
        return new UncheckedRow(sharedRealm.context, table,
                nativeCreateNewObject(sharedRealm.getNativePtr(), table.getNativePtr()));
    }
  13. 再回到11中的程式碼,newInstance建立的不是Dog例項,而是其子類DogRealmProxy的例項。而DogRealmProxy則是由註解處理器自動生成,如下部分程式碼:

    public class DogRealmProxy extends com.example.app.org.realm.demo.Dog implements RealmObjectProxy, DogRealmProxyInterface {
    
        ... ...
    
        private ProxyState<com.example.app.org.realm.demo.Dog> proxyState;      
        ... ... 
    
        DogRealmProxy() {
            //這裡的寫法比較神奇,realm$injectObjectContext一定是比較早在此直接注入的
            proxyState.setConstructionFinished();
        }
    
            @Override
        public void realm$injectObjectContext() {
            if (this.proxyState != null) {
                return;
            }
            final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get();
            this.columnInfo = (DogColumnInfo) context.getColumnInfo();
            this.proxyState = new ProxyState<com.example.app.org.realm.demo.Dog>(this);
            proxyState.setRealm$realm(context.getRealm());
            proxyState.setRow$realm(context.getRow());
            proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue());
            proxyState.setExcludeFields$realm(context.getExcludeFields());
        }
    
        ... ...
    
    }
    public interface RealmObjectProxy extends RealmModel {
        void realm$injectObjectContext();
    
        ProxyState realmGet$proxyState();
    
        /**
         * Tuple class for saving meta data about a cached RealmObject.
         */
        class CacheData<E extends RealmModel> {
            public int minDepth;
            public final E object;
    
            public CacheData(int minDepth, E object) {
                this.minDepth = minDepth;
                this.object = object;
            }
        }
    }
    public interface DogRealmProxyInterface {
        public String realmGet$name();
        public void realmSet$name(String value);
        public int realmGet$age();
        public void realmSet$age(int value);
    }
  14. 注意在上面的程式碼中,並沒有發現在java程式碼中直接呼叫realm$injectObjectContext(),依據個人經驗推測是在構建過程中注入的。通過搜尋,發現在realm-transformer模組下的BytecodeModifier.groovy中找到了線索,如下兩個方法:

    /**
     * Modifies a class adding its RealmProxy interface.
     *
     * @param clazz The CtClass to modify
     * @param classPool the Javassist class pool
     */
    public static void addRealmProxyInterface(CtClass clazz, ClassPool classPool) {
        def proxyInterface = classPool.get("io.realm.${clazz.getSimpleName()}RealmProxyInterface")
        clazz.addInterface(proxyInterface)
    }
    
    //這就是注入realm$injectObjectContext()方法的地方
    public static void callInjectObjectContextFromConstructors(CtClass clazz) {
        clazz.getConstructors().each {
            it.insertBeforeBody('if ($0 instanceof io.realm.internal.RealmObjectProxy) {' +
                    ' ((io.realm.internal.RealmObjectProxy) $0).realm$injectObjectContext();' +
                    ' }')
        }
    }
  15. 上面的程式碼就是往Model的代理類新增RealmProxyInterface以及在構造方法中注入injectObjectContext方法的呼叫。比如Dog類,其代理類是DogRealmProxy,增加的介面是DogRealmProxyInterface。

  16. 至此就把6中的realm.createObject(Dog.class)的執行流程追蹤完成了。接著往下看dog的set與get方法,也被BytecodeModifier.groovy修改了。如下程式碼:

    /**
     * Adds Realm specific accessors to a model class.
     * All the declared fields will be associated with a getter and a setter.
     *
     * @param clazz the CtClass to add accessors to.
     */
    public static void addRealmAccessors(CtClass clazz) {
        logger.debug "  Realm: Adding accessors to ${clazz.simpleName}"
        def methods = clazz.getDeclaredMethods()*.name
        clazz.declaredFields.each { CtField field ->
            if (isModelField(field)) {
                if (!methods.contains("realmGet\$${field.name}".toString())) {
                    clazz.addMethod(CtNewMethod.getter("realmGet\$${field.name}", field))
                }
                if (!methods.contains("realmSet\$${field.name}".toString())) {
                    clazz.addMethod(CtNewMethod.setter("realmSet\$${field.name}", field))
                }
            }
        }
    }
    
    /**
     * Modifies a class replacing field accesses with the appropriate Realm accessors.
     *
     * @param clazz The CtClass to modify
     * @param managedFields List of fields whose access should be replaced
     */
    public static void useRealmAccessors(CtClass clazz, List<CtField> managedFields) {
        clazz.getDeclaredBehaviors().each { behavior ->
            logger.debug "    Behavior: ${behavior.name}"
            if (
                (
                    behavior instanceof CtMethod &&
                    !behavior.name.startsWith('realmGet$') &&
                    !behavior.name.startsWith('realmSet$')
                ) || (
                    behavior instanceof CtConstructor
                )
            ) {
                behavior.instrument(new FieldAccessToAccessorConverter(managedFields, clazz, behavior))
            }
        }
    }
  17. 在RealmTransformer.groovy中的transform方法中會被呼叫,程式碼如下:

    @Override
    void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs,
                   TransformOutputProvider outputProvider, boolean isIncremental)
            throws IOException, TransformException, InterruptedException {
    
        ... ...
    
        // Add accessors to the model classes in the target project
        inputModelClasses.each {
            BytecodeModifier.addRealmAccessors(it)
            BytecodeModifier.addRealmProxyInterface(it, classPool)
            BytecodeModifier.callInjectObjectContextFromConstructors(it)
        }
    
        // Use accessors instead of direct field access
        inputClassNames.each {
            logger.debug "  Modifying class ${it}"
            def ctClass = classPool.getCtClass(it)
            BytecodeModifier.useRealmAccessors(ctClass, allManagedFields)
            ctClass.writeFile(getOutputFile(outputProvider).canonicalPath)
        }
    
        ... ...
    }
  18. 由上述轉換程式碼,就把Dog的屬性訪問方式修改了。比如,6中的myDog.setName(“Fido”)方法在構建的時候會被Realm提供的轉換器轉成myDog.realmSet$name(“Fido”)。

  19. 至此在6中的Dog建立例項的三行程式碼就追蹤完畢了,這也意味著executeTransaction方法執行完畢執行了資料庫的Write。

  20. 總之,Realm的Write操作由事務來提供保證,而Write過程的突破口就是追蹤Model物件建立的過程,在此過程中就會接觸到RealmProxy、Realm的realm-annotations-processor和realm-transformer等工具。

相關推薦

Realm原始碼分析Writes

前言 上篇是Realm原始碼分析的開篇,是關於Realm資料庫的初始化,關鍵一點是要抓住RealmProxyMediator這個代理中介者的例項化過程。在理解了Realm初始化之後,接下來就可以追蹤R

Realm原始碼分析copyToRealm與copyToRealmOrUpdate

createObject 在Realm原始碼分析之Writes中已經詳細追蹤過createObject的執行流程,此處不再贅述。 createObject有如下的兩個過載方法,區別是如果Model沒有指明主鍵使用前者,否則使用後者: createObjec

Spark原始碼分析Spark Shell(上)

https://www.cnblogs.com/xing901022/p/6412619.html 文中分析的spark版本為apache的spark-2.1.0-bin-hadoop2.7。 bin目錄結構: -rwxr-xr-x. 1 bigdata bigdata 1089 Dec

Netty 原始碼分析拆包器的奧祕

為什麼要粘包拆包 為什麼要粘包 首先你得了解一下TCP/IP協議,在使用者資料量非常小的情況下,極端情況下,一個位元組,該TCP資料包的有效載荷非常低,傳遞100位元組的資料,需要100次TCP傳送,100次ACK,在應用及時性要求不高的情況下,將這100個有效資料拼接成一個數據包,那會縮短到一個TCP資

Android原始碼分析為什麼在onCreate() 和 onResume() 獲取不到 View 的寬高

轉載自:https://www.jianshu.com/p/d7ab114ac1f7 先來看一段很熟悉的程式碼,可能在最開始接觸安卓的時候,大部分人都寫過的一段程式碼;即嘗試在 onCreate() 和 onResume() 方法中去獲取某個 View 的寬高資訊: 但是列印輸出後,我們會發

netty原始碼分析服務端啟動

ServerBootstrap與Bootstrap分別是netty中服務端與客戶端的引導類,主要負責服務端與客戶端初始化、配置及啟動引導等工作,接下來我們就通過netty原始碼中的示例對ServerBootstrap與Bootstrap的原始碼進行一個簡單的分析。首先我們知道這兩個類都繼承自AbstractB

SNMP原始碼分析(一)配置檔案部分

snmpd.conf想必不陌生。在程序啟動過程中會去讀取配置檔案中各個配置。其中幾個引數需要先知道是幹什麼的:   token:配置檔案的每行的開頭,例如 group MyROGroup v1 readSec 這行token的引數是group。  

【kubernetes/k8s原始碼分析】kubelet原始碼分析cdvisor原始碼分析

  資料流 UnsecuredDependencies -> run   1. cadvisor.New初始化 if kubeDeps.CAdvisorInterface == nil { imageFsInfoProvider := cadv

【kubernetes/k8s原始碼分析】kubelet原始碼分析容器網路初始化原始碼分析

一. 網路基礎   1.1 網路名稱空間的操作 建立網路名稱空間: ip netns add 名稱空間內執行命令: ip netns exec 進入名稱空間: ip netns exec bash   1.2 bridge-nf-c

【kubernetes/k8s原始碼分析】kubelet原始碼分析資源上報

0. 資料流   路徑: pkg/kubelet/kubelet.go   Run函式() ->   syncNodeStatus ()  ->   registerWithAPIServer() ->

【kubernetes/k8s原始碼分析】kubelet原始碼分析啟動容器

主要是呼叫runtime,這裡預設為docker 0. 資料流 NewMainKubelet(cmd/kubelet/app/server.go) -> NewKubeGenericRuntimeManager(pkg/kubelet/kuberuntime/kuberuntime

Android系統原始碼分析-ContentProvider

距離上一次寫部落格已經半年多了,這半年發生了很多事情,也有了很多感觸,最主要是改變了忙碌了工作,更加重視身體的健康,為此也把工作地點從深圳這個一線城市換到了珠海,工作相對沒有那麼累,身體感覺也好了很多。所以在工作完成之餘,也有了更多的時間來自我學習和提高,後續會用更多時間來寫更多實用的東西,幫助我們理解

Vue 原始碼分析proxy代理

Vue 原始碼分析之proxy代理 當我們在使用Vue進行資料設定時,通常初始化格式為: let data = { age: 12, name: 'yang' } // 例項化Vue物件 let vm = new Vue({ data })

Qt原始碼分析事件分發器QEventDispatcherWin32

分析Qt原始碼一則想自己在開發學習中有積累,同時自己也一直有一種理念,使用她那麼就更深入的認識她。 如果有分析不正確的,還煩請各位看官指正。 事件分發器建立 在QCoreApplication建構函式中 if (!QCoreApplicationPrivate

lodash原始碼分析isArguments

lodash原始碼分析之isArguments 有人命中註定要過平庸的生活,默默無聞,因為他們經歷了痛苦或不幸;有人卻故意這樣做,那是因為他們得到的幸福超過了他們的承受能力。 ——卡爾維諾《煙雲》 本文為讀 lodash 原始碼的第二十一篇,後續文章會更新到這個倉庫中,歡迎 star:poc

Netty原始碼分析LengthFieldBasedFrameDecoder

拆包的原理 關於拆包原理的上一篇博文 netty原始碼分析之拆包器的奧祕 中已詳細闡述,這裡簡單總結下:netty的拆包過程和自己寫手工拆包並沒有什麼不同,都是將位元組累加到一個容器裡面,判斷當前累加的位元組資料是否達到了一個包的大小,達到一個包大小就拆開,進而傳遞到上層業務解碼handler 之所以ne

illuminate/routing 原始碼分析註冊路由

我們知道,在 Laravel 世界裡,外界傳進來一個 Request 時,會被 Kernel 處理並返回給外界一個 Response。Kernel 在處理 Request 時,會呼叫 illuminate/routing 包提供的路由功能,來根據當前的 Request,轉發到對應的執行邏輯(執行邏輯的形式可以

Uboot啟動過程原始碼分析第二階段

UBoot的最終目標是啟動核心 1.從Flash中讀出核心 2.啟動核心 通過呼叫lib_arm/board.c中的start_armboot函式進入uboot第二階段 第二階段總結圖 typedef struct global_data { bd_t *bd; unsigned

Uboot啟動過程原始碼分析第一階段(硬體相關)

從上一個部落格知道uboot的入口點在 cpu/arm920t/start.s 開啟cpu/arm920t/start.s 跳轉到reset reset: /* * set the cpu to SVC32 mode// CUP設定為管理模式 */ mrs r0,cps

Java集合原始碼分析LikedList

一、LinkedList結構     LinkedList是一種可以在任何位置進行高效地插入和移除操作的有序序列,它是基於雙向連結串列實現的。   LinkedList 是一個繼承於AbstractSequentialList的雙向連結串列。它也可以被當作堆疊、佇列或雙端佇列進行操作。