Firebase 事件侦听器的退避政策是啥?

Posted

技术标签:

【中文标题】Firebase 事件侦听器的退避政策是啥?【英文标题】:What is Firebase Event Listener's back-off policy?Firebase 事件侦听器的退避政策是什么? 【发布时间】:2017-02-09 17:39:15 【问题描述】:

我有一个项目,用户可以在多个设备上进行多次登录。

现在用户可以在任何设备上订阅特定主题,并且需要的是其他设备登录也应该这样做。类似的情况是当一台设备取消订阅时,其余设备也应遵循套件。

为了做到这一点,我在每个用户下创建了一个节点,所有订阅都保存在 firebase 数据库中。我有一个 START_STICKY 服务,它将一个 Firebase 侦听器附加到此位置,并在发生更改时从主题中订阅/取消订阅。服务代码附在描述下方。

从观察来看,在常规使用中,我拥有的服务确实会重新生成,因为启动粘滞以防系统将其杀死。如果用户使用开发人员选项篡改它,它也会显式重生。唯一会导致它完全停止的情况是:

    退出 数据已清除 强制停止

我的问题是

    保持监听器连接对电池寿命的影响有多大。当网络套接字断开连接以防止持续消耗电池时,AFAIK Firebase 具有指数回退

    如果连接断开很长一段时间,firebase 侦听器是否可以放弃重新连接?如果是,何时达到退避限制。

    是否有更好的方法来确保跨多个设备订阅和取消订阅主题?

    服务是实现此目的的好方法吗?可以优化以下服务吗?是的,它确实需要不断运行。

代码

public class SubscriptionListenerService extends Service 

    DatabaseReference userNodeSubscriptionRef;

    ChildEventListener subscribedTopicsListener;

    SharedPreferences sessionPref,subscribedTopicsPreference;

    SharedPreferences.Editor subscribedtopicsprefeditor;

    String userid;

    boolean stoppedInternally = false;

    SharedPreferences.OnSharedPreferenceChangeListener sessionPrefChangeListener;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        //do not need a binder over here
        return null;
    

    @Override
    public void onCreate()
        super.onCreate();

        Log.d("FragmentCreate","onCreate called inside service");

        sessionPref = getSharedPreferences("SessionPref",0);

        subscribedTopicsPreference=getSharedPreferences("subscribedTopicsPreference",0);

        subscribedtopicsprefeditor=subscribedTopicsPreference.edit();

        userid = sessionPref.getString("userid",null);

        sessionPrefChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() 
            @Override
            public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) 
                Log.d("FragmentCreate","The shared preference changed "+key);
                stoppedInternally=true;
                sessionPref.unregisterOnSharedPreferenceChangeListener(this);
                if(userNodeSubscriptionRef!=null && subscribedTopicsListener!=null)
                    userNodeSubscriptionRef.removeEventListener(subscribedTopicsListener);
                
                stopSelf();
            
        ;

        sessionPref.registerOnSharedPreferenceChangeListener(sessionPrefChangeListener);

        subscribedTopicsListener = new ChildEventListener() 
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String s) 
                if(!(dataSnapshot.getValue() instanceof Boolean))
                    Log.d("FragmentCreate","Please test subscriptions with a boolean value");
                else 
                    if ((Boolean) dataSnapshot.getValue()) 
                        //here we subscribe to the topic as the topic has a true value
                        Log.d("FragmentCreate", "Subscribing to topic " + dataSnapshot.getKey());
                        subscribedtopicsprefeditor.putBoolean(dataSnapshot.getKey(), true);
                        FirebaseMessaging.getInstance().subscribeToTopic(dataSnapshot.getKey());
                     else 
                        //here we unsubscribed from the topic as the topic has a false value
                        Log.d("FragmentCreate", "Unsubscribing from topic " + dataSnapshot.getKey());
                        subscribedtopicsprefeditor.remove(dataSnapshot.getKey());
                        FirebaseMessaging.getInstance().unsubscribeFromTopic(dataSnapshot.getKey());
                    

                    subscribedtopicsprefeditor.commit();
                
            

            @Override
            public void onChildChanged(DataSnapshot dataSnapshot, String s) 
                //either an unsubscription will trigger this, or a re-subscription after an unsubscription

                if(!(dataSnapshot.getValue() instanceof Boolean))
                    Log.d("FragmentCreate","Please test subscriptions with a boolean value");
                else

                    if((Boolean)dataSnapshot.getValue())
                        Log.d("FragmentCreate","Subscribing to topic "+dataSnapshot.getKey());
                        subscribedtopicsprefeditor.putBoolean(dataSnapshot.getKey(),true);
                        FirebaseMessaging.getInstance().subscribeToTopic(dataSnapshot.getKey());
                    else
                        Log.d("FragmentCreate","Unsubscribing from topic "+dataSnapshot.getKey());
                        subscribedtopicsprefeditor.remove(dataSnapshot.getKey());
                        FirebaseMessaging.getInstance().unsubscribeFromTopic(dataSnapshot.getKey());
                    

                    subscribedtopicsprefeditor.commit();
                

            

            @Override
            public void onChildRemoved(DataSnapshot dataSnapshot) 
                //Log.d("FragmentCreate","Unubscribing from topic "+dataSnapshot.getKey());
                //FirebaseMessaging.getInstance().unsubscribeFromTopic(dataSnapshot.getKey());
            

            @Override
            public void onChildMoved(DataSnapshot dataSnapshot, String s) 
                //do nothing, this won't happen --- rather this isnt important
            

            @Override
            public void onCancelled(DatabaseError databaseError) 
                Log.d("FragmentCreate","Failed to listen to subscriptions node");
            
        ;

        if(userid!=null)

            Log.d("FragmentCreate","Found user id in service "+userid);

            userNodeSubscriptionRef = FirebaseDatabase.getInstance().getReference().child("Users").child(userid).child("subscriptions");

            userNodeSubscriptionRef.addChildEventListener(subscribedTopicsListener);

            userNodeSubscriptionRef.keepSynced(true);

        else
            Log.d("FragmentCreate","Couldn't find user id");
            stoppedInternally=true;
            stopSelf();
        

    

    @Override
    public int onStartCommand(Intent intent,int flags,int startId)
        //don't need anything done over here
        //The intent can have the following extras

        //If the intent was started by the alarm manager ..... it will contain android.intent.extra.ALARM_COUNT
        //If the intent was sent by the broadcast receiver listening for boot/update ... it will contain wakelockid
        //If it was started from within the app .... it will contain no extras in the intent

        //The following will not throw an exception if the intent does not have an wakelockid in extra
        //As per android doc... the following method releases the wakelock if any specified inside the extra and returns true
        //If no wakelockid is specified, it will return false;

        if(intent!=null)
            if(BootEventReceiver.completeWakefulIntent(intent))
                Log.d("FragmentCreate","Wakelock released");
            else
                Log.d("FragmentCreate","Wakelock not acquired in the first place");
            
        else
            Log.d("FragmentCreate","Intent started by regular app usage");
        

        return START_STICKY;
    

    @Override
    public void onDestroy()

        if(userNodeSubscriptionRef!=null)
            userNodeSubscriptionRef.keepSynced(false);
        

        userNodeSubscriptionRef = null;

        subscribedTopicsListener = null;

        sessionPref = null;
        subscribedTopicsPreference = null;

        subscribedtopicsprefeditor = null;

        userid = null;

        sessionPrefChangeListener = null;

        if(stoppedInternally)
            Log.d("FragmentCreate","Service getting stopped due to no userid or due to logout or data clearance...do not restart auto.. it will launch when user logs in or signs up");
        else

            Log.d("FragmentCreate","Service getting killed by user explicitly from running services or by force stop ... attempt restart");

            //well basically restart the service using an alarm manager ... restart after one minute

            AlarmManager alarmManager = (AlarmManager) this.getSystemService(ALARM_SERVICE);

            Intent restartServiceIntent = new Intent(this,SubscriptionListenerService.class);
            restartServiceIntent.setPackage(this.getPackageName());

            //context , uniqueid to identify the intent , actual intent , type of pending intent
            PendingIntent pendingIntentToBeFired = PendingIntent.getService(this,1,restartServiceIntent,PendingIntent.FLAG_ONE_SHOT);

            if(Build.VERSION.SDK_INT>=23)
                alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+600000,pendingIntentToBeFired);
            else
                alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+600000,pendingIntentToBeFired);
            
        

        super.onDestroy();

    

【问题讨论】:

ty for 编辑 Frank @Frank van Puffelen 好心帮助解决这个问题.. 几天以来一直坚持这个问题。我所拥有的服务确实按照我想要的方式工作,但我不知道这是否是一种可靠的方式。 是的,它确实需要持续运行:由于Doze Mode,这在具有 API 23 及更高版本的设备上可能是不可能的 是的,它不会保持连接...但是这里没关系...不会抛出异常。 Firebase 应该在连接打开或维护窗口到来时才连接,对吗?而且我认为这个设置将确保服务重新启动,即使系统由于启动粘滞而将其终止,从而重置监听器。 我还没有看到关于打盹模式如何影响听众的声明。这篇文章报告了设备从打瞌睡中唤醒后监听器无效:***.com/q/39302058/4815718 【参考方案1】:

对于您正在尝试做的事情,服务并不是真正必要的。拥有服务没有任何优势,除了它可能让您的应用程序的进程保持比没有启动服务时更长的时间。如果您不需要实际利用服务的特殊属性,那么使用它是没有意义的(或者您还没有真正提出令人信服的理由,说明为什么它确实需要一直启动)。只需在应用程序进程启动时注册侦听器,然后让它继续运行,直到应用程序进程因任何原因被杀死。如果应用程序没有运行(他们肯定没有使用它!),我非常怀疑您的用户会因为没有订阅更新而感到不安。

The power drain on an open socket that does no I/O is minimal。另外,an open socket will not necessarily keep the device's cell radio on at full power, either. 因此,如果侦听位置没有生成新值,则永远不会调用您的侦听器,并且没有网络 I/O。如果监听的值变化很大,您可能需要重新考虑让用户的设备忙于这些更新的必要性。

侦听器本身不是“轮询”或“重试”。整个 Firebase 套接字连接都在执行此操作。听众不知道幕后发生了什么。它要么接收更新,要么不接收更新。它不知道也不关心底层 websocket 的状态。客户端感兴趣的位置实际上是在服务器上管理的 - 这最终负责注意到更改并将其传播给侦听客户端。

【讨论】:

问题不在于您所说的实际订阅。是退订。订阅主题让人们通过 fcm 向用户发送推送通知。我的想法是,假设用户退订了新闻页面 A,但仍会收到来自该页面的更新通知,他/她会很生气。如果我不将侦听器保留在服务中,则应用程序被终止时发生的任何取消订阅都会丢失,并且它们会不断收到推送消息。 客户端不控制 FCM 消息传递 - 这一切都在您的服务器上。订阅数据只需要在消息发出时服务器上准确,因此服务器上的逻辑可以确定通知哪些客户端。订阅不一定要分发给所有客户才能有所作为。 如果无论如何我都必须单独管理注册令牌和 iid,它就违背了拥有主题的全部目的。无论如何,我遇到了另一个问题,即来自 gms 的 MeasureBrokerService 锁定到我的进程并无缘无故泄漏内存 听起来你的问题实际上更多是关于 FCM 主题的管理而不是你原来的问题。考虑提出一个新问题,详细说明您要完成的工作以及遇到的问题。我怀疑一个好的解决方案与尝试无限期地保持套接字打开无关。 是的,我会这样做

以上是关于Firebase 事件侦听器的退避政策是啥?的主要内容,如果未能解决你的问题,请参考以下文章

使用 nativescript-plugin-firebase 添加带有查询的事件侦听器

当应用程序打开或应用程序处于后台时,Phonegap Firebase 推送通知不会触发事件侦听器

Firebase 事件监听器从未触发(iOS 应用)

[Violation] 向 Angular 4 项目中的滚动阻止“touchstart”事件添加非被动事件侦听器是啥意思?

使用 jQuery,为单选按钮设置 onClick 事件侦听器的最佳方法是啥?

删除/关闭 Firebase 侦听器