Firebase 离线功能和 addListenerForSingleValueEvent
Posted
技术标签:
【中文标题】Firebase 离线功能和 addListenerForSingleValueEvent【英文标题】:Firebase Offline Capabilities and addListenerForSingleValueEvent 【发布时间】:2015-12-28 00:44:11 【问题描述】:每当我将addListenerForSingleValueEvent
与setPersistenceEnabled(true)
一起使用时,我只能设法从服务器获取DataSnapshot
的本地脱机副本,而没有更新后的DataSnapshot
。
但是,如果我将addValueEventListener
与setPersistenceEnabled(true)
一起使用,我可以从服务器获取DataSnapshot
的最新副本。
这对于addListenerForSingleValueEvent
是否正常,因为它只在本地搜索DataSnapshot
(离线)并在成功检索DataSnapshot
ONCE(离线或在线)后删除其侦听器?
【问题讨论】:
【参考方案1】:更新(2021 年):有一个新方法调用(get
on android 和 getData
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);”不会被执行 查看我的工作解决方案***.com/questions/34486417/…以上是关于Firebase 离线功能和 addListenerForSingleValueEvent的主要内容,如果未能解决你的问题,请参考以下文章
Android 中的 Firebase 事件记录离线和在线 [关闭]