每当我将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);
如果位置的值在本地磁盘缓存中,Firebase客户端将立即从本地缓存中为该值调用ondatachange()
。 然后,If还将启动与服务器的检查,以请求对值的任何更新。 如果自上次将数据添加到缓存后服务器上的数据发生了更改,则随后可能再次调用onDataChange()
。
将单个值事件侦听器添加到相同位置时:
ref.addListenerForSingleValueEvent(listener);
Firebase客户端将(与前面的情况类似)立即从本地磁盘缓存中调用onDataChange()
获取值。 它将不再调用onDataChange()
,即使服务器上的值不同。 请注意,更新后的数据仍将被请求,并在后续请求时返回。
这在前面的Firebase sync如何与共享数据一起工作中已经介绍过了?
最好的解决方案是使用AddValueEventListener()
,而不是单值事件侦听器。 常规值侦听器将从服务器获取即时本地事件和潜在更新。
作为一种解决方法,您还可以在使用单值事件侦听器的位置调用keepsynced(true)
。 这样可以确保每当数据发生变化时都会进行更新,这大大提高了单值事件侦听器看到当前值的机会。
所以我有一个可行的解决方案。 您所要做的就是使用ValueEventListener,并在1-2秒后移除侦听器,以确保在需要时已经抓取到更新的数据。 实时数据库有很好的延迟,所以这是安全的。 请参阅下面的安全代码示例;
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);
}
}, 2000);
}
// 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);
}
}, 2000);
}
即使他们在执行处理程序之前关闭了应用程序,它也会被删除。 不用客气!
您可以创建事务并中止它,然后在联机(n行数据)或脱机(缓存数据)时调用onComplete
我以前创建了一个函数,它只在数据库获得足够的连接来进行同步时才起作用。 我通过添加超时修正了问题。 我会在这方面工作,并测试这是否有效。 也许在未来,当我有空闲时间的时候,我会创建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)
}
}
在代码中,如果设置了超时,那么我设置了计时器,它将调用与ABORT的事务。 即使在脱机时也会调用此事务,并将提供联机或缓存数据(在此函数中,此数据是缓存数据的几率很高)。 然后我用成功调用事务。 oncomplete
只有在从firebase数据库得到响应时才会被调用。 我们现在可以取消计时器(如果不是null)并向回调发送数据。
这个实现使dev 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");
}
});