野狗 Sync 分析1 -数据监听(Android)

数据监听方式比较

Wilddog Sync 平台: Android

Wilddog Sync 版本: 2.0.1

数据监听

WilddogSync 提供了三种监听数据的方式:

addListenerForSingleValueEvent(ValueEventListener listener)

addValueEventListener(ValueEventListener listener)

addChildEventListener(ChildEventListener listener)

addListenerForSingleValueEvent 主要用于一次性获取当前节点下数据的场景,触发一次后就会失效。
addValueEventListeneraddChildEventListener 都会为当前节点绑定监听事件,持续的监听当前节点数据的变化情况,那么,这三种方式有什么不同呢?

我们运行一下代码测试两种方式的差异。
测试代码如下:

        //SyncReference reference = WilddogSync.getInstance().getReference();
        //设置ListenerForSingleValueEvent
        reference.child("listenerTest").addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                Log.e("single","onDataChange:"+dataSnapshot.toString());
            }

            @Override
            public void onCancelled(SyncError syncError) {
                Log.e("single",syncError.toString());
            }
        });
        //设置ValueEventListener
        reference.child("listenerTest").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                Log.e("value",dataSnapshot.toString());
            }

            @Override
            public void onCancelled(SyncError syncError) {
                Log.e("value",syncError.toString());
            }
        });
        //设置ChildEventListener
        reference.child("listenerTest").addChildEventListener(new ChildEventListener() {
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String s) {
                Log.e("child","added"+dataSnapshot.toString());
            }

            @Override
            public void onChildChanged(DataSnapshot dataSnapshot, String s) {
                Log.e("child","changed"+dataSnapshot.toString());
            }

            @Override
            public void onChildRemoved(DataSnapshot dataSnapshot) {
                Log.e("child",dataSnapshot.toString());
            }

            @Override
            public void onChildMoved(DataSnapshot dataSnapshot, String s) {

            }

            @Override
            public void onCancelled(SyncError syncError) {

            }
        });

获取数据

如果存在历史数据,运行上述代码,则会返回以下的数据:

Logcat日志:

//单次数据获取,addListenerForSingleValueEvent
E/single: onDataChange:DataSnapshot { key = listenerTest, value = {aaa=aaa, bbb={ccc=ccc}, ccc={ddd={eee=eee}}} }
//持续监听节点,addValueEventListener
E/value: onDataChange:DataSnapshot { key = listenerTest, value = {aaa=aaa, bbb={ccc=ccc}, ccc={ddd={eee=eee}}} }
//持续监听节点,addChildEventListener
E/child: onChildAdded:DataSnapshot { key = aaa, value = aaa }
E/child: onChildAdded:DataSnapshot { key = bbb, value = {ccc=ccc} }
E/child: onChildAdded:DataSnapshot { key = ccc, value = {ddd={eee=eee}} }

可以看出:

  • ValueListener会将当前节点下的数据一次性返回;
  • ChildListener将当前节点数据按子节点一个一个返回。

添加数据

当我们为某个节点添加了数据监听之后,就可以在客户端实时同步当前的数据。
现在我们考察一下为节点增加数据时,野狗实时数据同步的行为。

增加一个节点: {aaa=aaa}

E/child: onChildAdded : DataSnapshot { key = aaa, value = aaa }
E/value: onDataChange : DataSnapshot { key = listenerTest, value = {aaa=aaa} }

再增加一个同级节点: {bbb=bbb}

E/child: onChildAdded : DataSnapshot { key = bbb, value = bbb }
E/value: onDataChange : DataSnapshot { key = listenerTest, value = {aaa=aaa, bbb=bbb} }

增加一个带有子节点的节点ccc: {ccc={ddd=ddd}}

E/child: onChildAdded : DataSnapshot { key = ccc, value = {ddd=ddd} }
E/value: onDataChange : DataSnapshot { key = listenerTest, value = {aaa=aaa, bbb=bbb, ccc={ddd=ddd}} }

在ccc节点上增加子节点 {eee=eee}

E/child: onChildChanged : DataSnapshot { key = ccc, value = {ddd=ddd, eee=eee} }
E/value: onDataChange   : DataSnapshot { key = listenerTest, value = {aaa=aaa, bbb=bbb, ccc={ddd=ddd, eee=eee}} }

增加 {fff={ggg={hhh=hhh}}}

E/child: onChildAdded : DataSnapshot { key = fff, value = {ggg={hhh=hhh}} }
E/value: onDataChange : DataSnapshot { key = listenerTest, value = {aaa=aaa, fff={ggg={hhh=hhh}}, bbb=bbb, ccc={ddd=ddd, eee=eee}} }

增加 {fff={iii={jjj={kkk=kkk}}}}

E/child: onChildChanged : DataSnapshot { key = fff, value = {iii={jjj={kkk=kkk}}, ggg={hhh=hhh}} }
E/value: onDataChange   : DataSnapshot { key = listenerTest, value = {aaa=aaa, fff={iii={jjj={kkk=kkk}}, ggg={hhh=hhh}}, bbb=bbb, ccc={ddd=ddd, eee=eee}} }

总结:
1/addValueEventListener 事件监听返回的 DataSnapshot 的 key 始终为当前节点,value 是当前节点变化后的全部字节点值;
addChildEventListener 事件监听的 key 是当前节点的下一级子节点,注意是下一级子节点,哪个下一级子节点的数据有了变化,key就是哪个子节点。
例如我们在 iii 节点下增加了 jjj 子节点,但是返回的数据依然是 DataSnapshot { key = fff, value = {iii={jjj={kkk=kkk}}, ggg={hhh=hhh}} },返回的是listenerTest 下一级子节点 fff 节点的变化数据。

也就是说value事件关注当前节点的数据变化情况,返回的DataSnapshot中value是当前节点下所有数据的值;
child事件关注的是当前节点的子节点数据变化情况,返回的DataSnapshot中value是当前节点某个子节点所有数据的值。

2/value事件监听时,当前节点有数据增加则会触发onDataChange方法;
child事件监听时,增加下一级子节点时如子节点不存在则触发onChildAdded事件,如果子节点存在则触发onChildChanged事件。

删除数据

直接删除子节点,考察野狗实时数据同步的行为。

删除 aaa

此时 ChildEventListener 触发了 onChildRemoved事件,返回的 valueaaa 节点被删除的数据。

E/child: onChildRemoved : DataSnapshot { key = aaa, value = aaa }
E/value: onDataChange   : DataSnapshot { key = listenerTest, value = {fff={iii={jjj={kkk=kkk}}, ggg={hhh=hhh}}, bbb=bbb, ccc={ddd=ddd, eee=eee}} }

删除 ddd

在执行删除操作前 ccc 节点下的数据为 ccc={ddd=ddd, eee=eee}},执行删除 ddd 节点之后, ccc 节点的数据变为 ccc={eee=eee}}
此时触发了 onChildChanged 事件,返回的值为当前 ccc 节点下的数据。

E/child: onChildChanged : DataSnapshot { key = ccc, value = {eee=eee} }
E/value: onDataChange   : DataSnapshot { key = listenerTest, value = {fff={iii={jjj={kkk=kkk}}, ggg={hhh=hhh}}, bbb=bbb, ccc={eee=eee}} }

删除 eee

再次触发了 onChildRemoved 事件,返回被删除的数据,此时整个 ccc 节点一同被删除。

E/child: onChildRemoved : DataSnapshot { key = ccc, value = {eee=eee} }
E/value: onDataChange   : DataSnapshot { key = listenerTest, value = {fff={iii={jjj={kkk=kkk}}, ggg={hhh=hhh}}, bbb=bbb} }

总结:
1/addValueEventListener 事件监听始终返回被监听节点的数据;
addChildEventListener 事件监听返回的是变化的子节点的数据,根据不同情况触发不同监听。

2/删除节点时, ChildEventListener会根据不同情形触发两个不同的事件:

事件 触发条件 结果
onChildRemoved 被删除节点的父节点只有一个子节点 返回被删除的节点数据
onChildChanged 被删除节点的父节点有多个子节点 返回变化后的被删除节点的父节点数据

3/删除节点时,如果被删除节点的父节点只有被删除节点这一个子节点,那么父节点会被同时删除。


应用实践

根据以上实验的结果,我们可以分析这两个不同监听的语义:

ValueEventListener 始终返回被监听节点的数据,所以 Value 指的是当前节点的值,也就是说,Value 关注当前节点值的变化情况。

ChildEventListener 始终返回被监听节点的下一级字节点的数据,那么 Child 指的是当前节点的下一级子节点,或者说直接子节点,其孙节点的变化一样会归为子节点的变化,关注的是当前节点下一级子节点变化情况。

需要注意的是,同一个路径可以重复设置监听,如果多次设置监听之后,每次数据变化都会触发每一个设置的监听,有可能多次处理相同业务,造成逻辑错误。
在一个监听已经失去继续保留的意义的时候,可以使用 removeEventListener() 方法移除不需要的监听。

发表评论

电子邮件地址不会被公开。