Android Firebase脱机功能和addListenerForSingleValueEvent
每当我将Android Firebase脱机功能和addListenerForSingleValueEvent,android,firebase,firebase-realtime-database,Android,Firebase,Firebase Realtime Database,每当我将addListenerForSingleValueEvent与setPersistenceEnabled(true)一起使用时,我只会设法从服务器上获取DataSnapshot的本地脱机副本,而不更新后的DataSnapshot 但是,如果我将addValueEventListener与setPersistenceEnabled(true)一起使用,我可以从服务器获取DataSnapshot的最新副本 这对于addListenerForSingleValueEvent是否正常,因为它只在
addListenerForSingleValueEvent
与setPersistenceEnabled(true)
一起使用时,我只会设法从服务器上获取DataSnapshot
的本地脱机副本,而不更新后的DataSnapshot
但是,如果我将addValueEventListener
与setPersistenceEnabled(true)
一起使用,我可以从服务器获取DataSnapshot
的最新副本
这对于addListenerForSingleValueEvent
是否正常,因为它只在本地(脱机)搜索DataSnapshot
,并在成功检索DataSnapshot
一次(脱机或联机)后删除其侦听器?持久性的工作原理
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);
ref.addListenerForSingleValueEvent(listener);
如果该位置的值位于本地磁盘缓存中,Firebase客户端将立即从本地缓存中调用该值的onDataChange()
。If随后还将启动与服务器的检查,以请求对值进行任何更新。如果自上次将数据添加到缓存后,服务器上的数据发生了更改,则随后可能再次调用onDataChange()
当您使用addListenerForSingleValueEvent时会发生什么情况
将单值事件侦听器添加到同一位置时:
ref.addValueEventListener(listener);
// OR
ref.addListenerForSingleValueEvent(listener);
ref.addListenerForSingleValueEvent(listener);
Firebase客户端将(与前一种情况一样)立即调用onDataChange()
以获取本地磁盘缓存中的值。它将不再调用onDataChange()
,即使服务器上的值不同。请注意,更新后的数据仍将被请求并在后续请求中返回
这在前面的章节中已经介绍过
解决方案和解决办法
最好的解决方案是使用,而不是使用单值事件侦听器。常规值侦听器将从服务器获取即时本地事件和潜在更新
第二个解决方案是使用新的(在2021年初引入),它没有这种问题行为。请注意,此方法始终尝试首先从服务器获取值,因此需要更长的时间才能完成。如果您的值从未更改,那么最好使用addListenerForSingleValueEvent
(但在这种情况下,您可能不会出现在本页上)
作为一种解决方法,您还可以在使用单值事件侦听器的位置上使用。这确保了数据在每次更改时都会更新,这大大提高了单值事件侦听器看到当前值的可能性。您可以创建事务并中止它,然后在联机(联机数据)或脱机(缓存数据)时调用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)
}
}
在启用持久性的情况下工作时,我计算了侦听器接收到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);
}
所以我有一个有效的解决方案。您所要做的就是使用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”的方法
即使他们在执行处理程序之前关闭应用程序,它也会被删除。
编辑:可以对其进行抽象,以跟踪HashMap中添加和删除的侦听器,使用引用路径作为键,datasnapshot作为值。您甚至可以包装一个fetchData方法,该方法有一个布尔标志表示“一次”。如果这是真的,它将执行此解决方法以获取一次数据,否则它将继续正常运行。
不客气 谢谢你的完美说明。但是keepSynced(true)和addValueEventListener将始终保持打开的连接。与keepSynced(false)和addListenerForSingleValueEvent不同,firebase将允许在一段时间后断开连接。如何强制执行一次手动更新?这是一种多么不令人信服的行为,它使得测试几乎不可能。调用
keepSynced(true)
,然后使用.addListenerForSingleValueEvent(listener)数据库更改后首次打开应用程序时,代码>不起作用。在注册更改之前需要打开应用程序两次。@当您在“keepSynced”之后进行一次调用时,它实际上起作用。创建查询,例如设置keepSynced、addValue和onCompleted keepValue false以避免一直同步。然后数据被刷新(没有检查它是否每次都能正常工作)。另一个似乎有效的解决方案是我在使用keepSynced时在postA警告中给出的答案:它可能会导致带宽的巨大消耗,并带来相关成本。请参阅下面的实用说明:如果本地缓存数据与服务器数据相同,onDataChange将只调用一次,因此“ref.removeEventListener(listener);”将不会被执行请参阅我的工作解决方案