【问题标题】:Firebase Offline Capabilities and addListenerForSingleValueEventFirebase 离线功能和 addListenerForSingleValueEvent
【发布时间】:2016-04-01 21:12:15
【问题描述】:

每当我将addListenerForSingleValueEventsetPersistenceEnabled(true) 一起使用时,我只能设法从服务器获取DataSnapshot 的本地脱机副本,而没有更新后的DataSnapshot

但是,如果我将addValueEventListenersetPersistenceEnabled(true) 一起使用,我可以从服务器获取DataSnapshot 的最新副本。

这对于addListenerForSingleValueEvent 是否正常,因为它仅在本地搜索DataSnapshot(离线)并在成功检索DataSnapshot ONCE(离线或在线)后删除其侦听器?

【问题讨论】:

    标签: android firebase firebase-realtime-database


    【解决方案1】:

    更新(2021 年)一个新的方法调用(get on AndroidgetData on iOS)实现了你想要的行为:它首先尝试从服务器获取最新值,只有在无法到达服务器时才回退到缓存中。使用持久侦听器的建议仍然适用,但至少有一个更简洁的选项,即使您启用了本地缓存,也可以获取一次数据。


    持久性的工作原理

    Firebase 客户端会在内存中保留一份您正在积极收听的所有数据的副本。一旦最后一个监听器断开连接,数据就会从内存中刷新。

    如果您在 Firebase Android 应用程序中启用磁盘持久性:

    Firebase.getDefaultConfig().setPersistenceEnabled(true); 
    

    Firebase 客户端将保存应用最近收听的所有数据的本地副本(在磁盘上)。

    附加监听器时会发生什么

    假设你有以下ValueEventListener

    ValueEventListener listener = new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot snapshot) {
            System.out.println(snapshot.getValue());
        }
    
        @Override
        public void onCancelled(FirebaseError firebaseError) {
            // No-op
        }
    };
    

    当您将ValueEventListener 添加到位置时:

    ref.addValueEventListener(listener); 
    // OR
    ref.addListenerForSingleValueEvent(listener); 
    

    如果位置的值在本地磁盘缓存中,Firebase 客户端将立即从本地缓存中调用 onDataChange() 以获取该值。然后,如果还将启动与服务器的检查,以询问对值的任何更新。如果自上次添加到缓存后服务器上的数据发生更改,它可能随后再次调用 onDataChange()

    当您使用addListenerForSingleValueEvent 时会发生什么

    当您将单值事件监听器添加到同一位置时:

    ref.addListenerForSingleValueEvent(listener);
    

    Firebase 客户端将(与之前的情况一样)立即调用 onDataChange() 以获取本地磁盘缓存中的值。它不会再调用onDataChange() 任何次,即使服务器上的值结果不同。请注意,更新的数据仍将被请求并在后续请求中返回。

    How does Firebase sync work, with shared data? 之前已对此进行了介绍

    解决方案和解决方法

    最好的解决方案是使用addValueEventListener(),而不是单值事件监听器。常规值侦听器将同时获取即时本地事件和来自服务器的潜在更新。

    第二种解决方案是使用新的get method(于 2021 年初推出),它没有这种问题行为。请注意,此方法始终尝试首先从服务器获取值,因此需要更长的时间才能完全完成。如果您的值永远不会改变,使用addListenerForSingleValueEvent 可能会更好(但在这种情况下您可能不会出现在此页面上)。

    作为一种解决方法,您还可以在使用单值事件侦听器的位置上call keepSynced(true)。这样可以确保数据在发生变化时得到更新,从而大大提高单值事件侦听器看到当前值的机会。

    【讨论】:

    • 感谢完美的插图。但是 keepSynced(true) 和 addValueEventListener 会一直保持打开的连接。与 keepSynced(false) 相比,addListenerForSingleValueEvent 将允许 firebase 在一段时间后断开连接。如何强制一次性手动更新?
    • 多么不方便的行为,它让测试几乎不可能。
    • 调用keepSynced(true)然后使用.addListenerForSingleValueEvent(listener);在数据库更改后第一次打开应用程序时不起作用。在注册更改之前需要打开应用两次。
    • @Drew 当您在“keepSynced”之后打一个电话时,它实际上可以工作。创建查询,例如设置 keepSynced、addValue 和 onCompleted keepValue false 以避免一直同步。然后刷新数据(没有检查它是否每次都像需要的那样工作)。另一个似乎可行的解决方案是我在这篇文章中的回答
    • 使用 keepSynced 时的警告:它会导致您的带宽大量消耗,并产生相关成本。请参阅以下内容以获得实际解释:pamartinezandres.com/…
    【解决方案2】:

    所以我有一个可行的解决方案。您所要做的就是使用 ValueEventListener 并在 0.5 秒后移除侦听器,以确保您已经在需要时获取了更新的数据。实时数据库有很好的延迟,所以这是安全的。请参阅下面的安全代码示例;

    public class FirebaseController {
    
    private DatabaseReference mRootRef;
    private Handler mHandler = new Handler();
    
    private FirebaseController() {
        FirebaseDatabase.getInstance().setPersistenceEnabled(true);
    
        mRootRef = FirebaseDatabase.getInstance().getReference();
    }
    
    public static FirebaseController getInstance() {
        if (sInstance == null) {
            sInstance = new FirebaseController();
        }
        return sInstance;
    }
    

    然后是您喜欢使用“addListenerForSingleEvent”的一些方法;

    public void getTime(final OnTimeRetrievedListener listener) {
        DatabaseReference ref = mRootRef.child("serverTime");
        ref.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                if (listener != null) {
                    // This can be called twice if data changed on server - SO DEAL WITH IT!
                    listener.onTimeRetrieved(dataSnapshot.getValue(Long.class));
                }
                // This can be called twice if data changed on server - SO DEAL WITH IT!
                removeListenerAfter2(ref, this);
            }
    
            @Override
            public void onCancelled(DatabaseError databaseError) {
                removeListenerAfter2(ref, this);
            }
        });
    }
    
    // ValueEventListener version workaround for addListenerForSingleEvent not working.
    private void removeListenerAfter2(DatabaseReference ref, ValueEventListener listener) {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                HelperUtil.logE("removing listener", FirebaseController.class);
                ref.removeEventListener(listener);
            }
        }, 500);
    }
    
    // ChildEventListener version workaround for addListenerForSingleEvent not working.
    private void removeListenerAfter2(DatabaseReference ref, ChildEventListener listener) {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                HelperUtil.logE("removing listener", FirebaseController.class);
                ref.removeEventListener(listener);
            }
        }, 500);
    }
    

    即使他们在处理程序执行之前关闭了应用程序,它仍然会被删除。 编辑:这可以被抽象为使用引用路径作为键和数据快照作为值来跟踪 HashMap 中添加和删除的侦听器。您甚至可以包装一个 fetchData 方法,该方法具有“once”的布尔标志,如果这是真的,它将执行此解决方法以获取一次数据,否则它将正常继续。 不客气!

    【讨论】:

      【解决方案3】:

      您可以创建事务并中止它,然后在线(nline 数据)或离线(缓存数据)时会调用 onComplete

      我之前创建的函数只有在数据库连接 lomng 足以进行同步时才起作用。我通过添加超时来解决问题。我将对此进行研究并测试它是否有效。也许以后有空的时候,我会创建android lib并发布它,但到那时它是kotlin中的代码:

      /**
           * @param databaseReference reference to parent database node
           * @param callback callback with mutable list which returns list of objects and boolean if data is from cache
           * @param timeOutInMillis if not set it will wait all the time to get data online. If set - when timeout occurs it will send data from cache if exists
           */
          fun readChildrenOnlineElseLocal(databaseReference: DatabaseReference, callback: ((mutableList: MutableList<@kotlin.UnsafeVariance T>, isDataFromCache: Boolean) -> Unit), timeOutInMillis: Long? = null) {
      
              var countDownTimer: CountDownTimer? = null
      
              val transactionHandlerAbort = object : Transaction.Handler { //for cache load
                  override fun onComplete(p0: DatabaseError?, p1: Boolean, data: DataSnapshot?) {
                      val listOfObjects = ArrayList<T>()
                      data?.let {
                          data.children.forEach {
                              val child = it.getValue(aClass)
                              child?.let {
                                  listOfObjects.add(child)
                              }
                          }
                      }
                      callback.invoke(listOfObjects, true)
                  }
      
                  override fun doTransaction(p0: MutableData?): Transaction.Result {
                      return Transaction.abort()
                  }
              }
      
              val transactionHandlerSuccess = object : Transaction.Handler { //for online load
                  override fun onComplete(p0: DatabaseError?, p1: Boolean, data: DataSnapshot?) {
                      countDownTimer?.cancel()
                      val listOfObjects = ArrayList<T>()
                      data?.let {
                          data.children.forEach {
                              val child = it.getValue(aClass)
                              child?.let {
                                  listOfObjects.add(child)
                              }
                          }
                      }
                      callback.invoke(listOfObjects, false)
                  }
      
                  override fun doTransaction(p0: MutableData?): Transaction.Result {
                      return Transaction.success(p0)
                  }
              }
      

      在代码中,如果设置了超时,那么我设置了计时器,该计时器将通过中止调用事务。即使在离线时也会调用此事务,并将提供在线或缓存数据(在此函数中,此数据被缓存的可能性非常高)。 然后我成功调用事务。 OnComplete 只有在我们从 firebase 数据库得到响应时才会被调用。我们现在可以取消计时器(如果不为空)并将数据发送到回调。

      此实现使开发人员 99% 确定数据来自缓存或在线数据。

      如果你想让离线速度更快(当显然没有连接数据库时不要愚蠢地等待超时)然后在使用上面的函数之前检查数据库是否连接:

      DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
      connectedRef.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot snapshot) {
          boolean connected = snapshot.getValue(Boolean.class);
          if (connected) {
            System.out.println("connected");
          } else {
            System.out.println("not connected");
          }
        }
      
        @Override
        public void onCancelled(DatabaseError error) {
          System.err.println("Listener was cancelled");
        }
      });
      

      【讨论】:

        【解决方案4】:

        当 workinkg 启用持久性时,我计算了侦听器收到 onDataChange() 调用的次数,并在 2 次时停止侦听。为我工作,也许有帮助:

        private int timesRead;
        private ValueEventListener listener;
        private DatabaseReference ref;
        
        private void readFB() {
            timesRead = 0;
            if (ref == null) {
                ref = mFBDatabase.child("URL");
            }
        
            if (listener == null) {
                listener = new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        //process dataSnapshot
        
                        timesRead++;
                        if (timesRead == 2) {
                            ref.removeEventListener(listener);
                        }
                    }
        
                    @Override
                    public void onCancelled(DatabaseError databaseError) {
                    }
                };
            }
            ref.removeEventListener(listener);
            ref.addValueEventListener(listener);
        }
        

        【讨论】:

        • 如果本地缓存数据与服务器数据相同,onDataChange只会被调用一次,即“ref.removeEventListener(listener);”不会被执行
        • 查看我的工作解决方案stackoverflow.com/questions/34486417/…
        猜你喜欢
        • 2021-11-25
        • 1970-01-01
        相关资源
        最近更新 更多