Realm源码分析之copyToRealm与copyToRealmOrUpdate

createObject

  1. Realm源码分析之Writes中已经详细追踪过createObject的执行流程,此处不再赘述。

  2. createObject有如下的两个重载方法,区别是如果Model没有指明主键使用前者,否则使用后者

    createObject(Class<E> clazz)
    
    createObject(Class<E> clazz, Object primaryKeyValue)
  3. 注意:每次使用createObject都会创建新的Model实例。

copyToRealm与copyToRealmOrUpdate

  1. 先上Model示例代码,注意是没有指明主键的:

    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;
        }
    }
    
  2. 使用copyToRealm创建model示例,如下代码:

    public void copyToRealm() {
        realm.executeTransactionAsync(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                Dog myDog = new Dog();
                myDog.setName("Copy");
                myDog.setAge(6);
                realm.copyToRealm(myDog);
            }
        });
    }
  3. 开始追踪copyToRealm,如下源码:

    public <E extends RealmModel> E copyToRealm(E object) {
        checkNotNullObject(object);
        return copyOrUpdate(object, false, new HashMap<RealmModel, RealmObjectProxy>());
    }
  4. 上面代码先做了对象合法性判断,然后调用了copyOrUpdate这个静态方法,如下代码:

    private <E extends RealmModel> E copyOrUpdate(E object, boolean update, Map<RealmModel, RealmObjectProxy> cache) {
        checkIfValid();
        return configuration.getSchemaMediator().copyOrUpdate(this, object, update, cache);
    }
  5. Realm源码分析之初始化中已经分析过这个Mediator实例是DefaultRealmModuleMediator,它是有注解处理器自动生成,如下代码:

    public <E extends RealmModel> E copyOrUpdate(Realm realm, E obj, boolean update, Map<RealmModel, RealmObjectProxy> cache) {
        // This cast is correct because obj is either
        // generated by RealmProxy or the original type extending directly from RealmObject
        @SuppressWarnings("unchecked") Class<E> clazz = (Class<E>) ((obj instanceof RealmObjectProxy) ? obj.getClass().getSuperclass() : obj.getClass());
    
        if (clazz.equals(com.example.app.org.realm.demo.Dog.class)) {
            return clazz.cast(io.realm.DogRealmProxy.copyOrUpdate(realm, (com.example.app.org.realm.demo.Dog) obj, update, cache));
        }
        throw getMissingProxyClassException(clazz);
    }
  6. DefaultRealmModuleMediator只是做了中转,而是由DogRealmProxy负责实现,如下代码:

    public static com.example.app.org.realm.demo.Dog copyOrUpdate(Realm realm, com.example.app.org.realm.demo.Dog object, boolean update, Map<RealmModel,RealmObjectProxy> cache) {
        if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().threadId != realm.threadId) {
            throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance.");
        }
        if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) {
            return object;
        }
        final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get();
        RealmObjectProxy cachedRealmObject = cache.get(object);
        if (cachedRealmObject != null) {
            return (com.example.app.org.realm.demo.Dog) cachedRealmObject;
        }
    
        return copy(realm, object, update, cache);
    }
  7. 上述代码,先是做了合法性判断,然后从缓存中取实例并返回,取不到就调用如下的copy方法:

    public static com.example.app.org.realm.demo.Dog copy(Realm realm, com.example.app.org.realm.demo.Dog newObject, boolean update, Map<RealmModel,RealmObjectProxy> cache) {
        RealmObjectProxy cachedRealmObject = cache.get(newObject);
        if (cachedRealmObject != null) {
            return (com.example.app.org.realm.demo.Dog) cachedRealmObject;
        }
    
        // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields.
        com.example.app.org.realm.demo.Dog realmObject = realm.createObjectInternal(com.example.app.org.realm.demo.Dog.class, false, Collections.<String>emptyList());
        cache.put(newObject, (RealmObjectProxy) realmObject);
    
        DogRealmProxyInterface realmObjectSource = (DogRealmProxyInterface) newObject;
        DogRealmProxyInterface realmObjectCopy = (DogRealmProxyInterface) realmObject;
    
        realmObjectCopy.realmSet$name(realmObjectSource.realmGet$name());
        realmObjectCopy.realmSet$age(realmObjectSource.realmGet$age());
        return realmObject;
    }
  8. 上面方法再次取缓存并返回,取不到就调用createObjectInternal方法创建实例,并未属性赋值。而createObjectInternal已经在Realm源码分析之Writes中已经分析过了,此处不再展开了。

  9. 至此可以得出copyToRealm与createObject的区别:copyToRealm会复用Realm中缓存的Model实例。

  10. 继续使用上述的Dog,使用copyToRealmOrUpdate来创建如下代码:

    public void copyToRealmOrUpdate() {
        realm.executeTransactionAsync(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                Dog myDog = new Dog();
                myDog.setName("Copy Update");
                myDog.setAge(6);
                realm.copyToRealmOrUpdate(myDog);
            }
        });
    }

    直接运行的话会crash,log如下:

    Caused by: java.lang.IllegalArgumentException: A RealmObject with no @PrimaryKey cannot be updated: class com.example.app.org.realm.demo.Dog
       at io.realm.Realm.checkHasPrimaryKey(Realm.java:1615)
       at io.realm.Realm.copyToRealmOrUpdate(Realm.java:1030)
       at com.example.app.org.realm.demo.RealmActivity$5.execute(RealmActivity.java:123)
       at io.realm.Realm$1.run(Realm.java:1508)
       at io.realm.internal.async.BgPriorityRunnable.run(BgPriorityRunnable.java:34)
       at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
       at java.util.concurrent.FutureTask.run(FutureTask.java:237)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
       at java.lang.Thread.run(Thread.java:818)
  11. 上面的log很明显了,目前可以确定:copyToRealmOrUpdate方法是给指明了主键的model使用的。

  12. 让我们来揭开其神奇面纱,代码如下:

    public <E extends RealmModel> E copyToRealmOrUpdate(E object) {
        checkNotNullObject(object);
        checkHasPrimaryKey(object.getClass());
        return copyOrUpdate(object, true, new HashMap<RealmModel, RealmObjectProxy>());
    }
    private void checkHasPrimaryKey(Class<? extends RealmModel> clazz) {
        if (!schema.getTable(clazz).hasPrimaryKey()) {
            throw new IllegalArgumentException("A RealmObject with no @PrimaryKey cannot be updated: " + clazz.toString());
        }
    }
  13. 上述代码先是对象判空,然后进行主键判断,最后调用的是copyOrUpdate方法。到目前为止,copyToRealm与copyToRealmOrUpdate唯一区别是调用copyOrUpdate这个静态方法传递的第二个参数不同,如下:

    //copyToRealm
    copyOrUpdate(object, false, new HashMap<RealmModel, RealmObjectProxy>())
    
    //copyToRealmOrUpdate
    copyOrUpdate(object, true, new HashMap<RealmModel, RealmObjectProxy>())
  14. 紧接着回去看6中的DogRealmProxy的copyOrUpdate与copy方法,可以发现这个boolean参数实际是没有使用的。因此,可以得出:对于未指明主键的model,其copyToRealm与copyToRealmOrUpdate底层实现是一致的。

  15. 接下来修改model并指明主键,如下代码:

    public class Dog extends RealmObject {
        @PrimaryKey
        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;
        }
    }
  16. 发现DogRealmProxy中的copyOrUpdate方法的实现已经改变了,如下代码:

    public static com.example.app.org.realm.demo.Dog copyOrUpdate(Realm realm, com.example.app.org.realm.demo.Dog object, boolean update, Map<RealmModel,RealmObjectProxy> cache) {
        if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().threadId != realm.threadId) {
            throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance.");
        }
        if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) {
            return object;
        }
        final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get();
        RealmObjectProxy cachedRealmObject = cache.get(object);
        if (cachedRealmObject != null) {
            return (com.example.app.org.realm.demo.Dog) cachedRealmObject;
        }
    
        com.example.app.org.realm.demo.Dog realmObject = null;
        boolean canUpdate = update;
        if (canUpdate) {
            Table table = realm.getTable(com.example.app.org.realm.demo.Dog.class);
            long pkColumnIndex = table.getPrimaryKey();
            String value = ((DogRealmProxyInterface) object).realmGet$name();
            long rowIndex = Table.NO_MATCH;
            if (value == null) {
                rowIndex = table.findFirstNull(pkColumnIndex);
            } else {
                rowIndex = table.findFirstString(pkColumnIndex, value);
            }
            if (rowIndex != Table.NO_MATCH) {
                try {
                    objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.schema.getColumnInfo(com.example.app.org.realm.demo.Dog.class), false, Collections.<String> emptyList());
                    realmObject = new io.realm.DogRealmProxy();
                    cache.put(object, (RealmObjectProxy) realmObject);
                } finally {
                    objectContext.clear();
                }
            } else {
                canUpdate = false;
            }
        }
    
        if (canUpdate) {
            return update(realm, realmObject, object, cache);
        } else {
            return copy(realm, object, update, cache);
        }
    }
  17. 相比没有加主键的方法,在取不到缓存之后它增加了去Table中查询主键的过程,如果在Table找到了匹配的主键就会新建一个DogRealmProxy对象并缓存起来,最后执行update操作,其源码如下:

    static com.example.app.org.realm.demo.Dog update(Realm realm, com.example.app.org.realm.demo.Dog realmObject, com.example.app.org.realm.demo.Dog newObject, Map<RealmModel, RealmObjectProxy> cache) {
        DogRealmProxyInterface realmObjectTarget = (DogRealmProxyInterface) realmObject;
        DogRealmProxyInterface realmObjectSource = (DogRealmProxyInterface) newObject;
        realmObjectTarget.realmSet$age(realmObjectSource.realmGet$age());
        return realmObject;
    }
  18. 上面的update方法其实赋值操作,而copyToRealm与copyToRealmOrUpdate就到此为止了。

结论

  1. copyToRealm与copyToRealmOrUpdate:前者是给未指明主键的model使用的,后者则是给指明主键的model使用。

  2. copyToRealm对应无主键参数的createObject,区别是copyToRealm会复用Realm缓存的model示例,不会每次都创建新的model实例。

  3. copyToRealmOrUpdate对应有主键参数的createObject,区别是copyToRealmOrUpdate会复用Realm缓存的model示例,没有缓存实例就要去表中查询是否有匹配这个主键存在的记录,来决定是新建对象还是更新model实例。

    原文作者:Android源码分析
    原文地址: https://blog.csdn.net/tscyds/article/details/76155031
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞