2017-02-09 2 views
0

私は、ユーザーが複数のデバイス間で複数のログインを持つことができるプロジェクトを持っています。Firebase Event Listenerのバックオフポリシーとは何ですか?

ユーザーは任意のデバイス上の特定のトピックを購読することができ、残りのデバイスログインも同様に行う必要があります。同様のケースは、1つのデバイスがサブスクライブしないときであり、残りはスイートに従うべきである。

これを行うために、firebaseデータベース内のすべてのサブスクリプションが維持されている各ユーザの下にNodeを作成しました。私は、この場所にFirebaseリスナーを添付し、変更が発生したときにトピックからサブサブ/アンサブスクライブするSTART_STICKYサービスを持っています。サービスのコードは説明の下に添付されています。

通常の観測では、システムがそれを殺す場合には、起動がスティッキーなので、私が持っているサービスが再発生します。開発者のオプションを使用してユーザがそれを改ざんした場合、明示的に再生成されます。それは完全に中止するようになります唯一の例は以下のとおりです。

  1. サインアウトデータは
  2. 力が

私の質問は

どのようにひどくは維持されます
  1. ある停止クリアリスナが接続されているとバッテリの寿命に影響します。 AFAIK Firebaseは、ウェブソケットが切断されてバッテリーが一定に流れるのを防ぐために指数関数的バックオフを持っています

  2. 接続がかなりオフになっても、Firebase Listenerは再接続を断念できますか?そうであれば、いつバックオフ限界に達するか。

  3. トピックが複数のデバイスに登録され、登録解除されていることを確認する良い方法はありますか?

  4. これを行うにはサービスが適していますか?以下のサービスを最適化できますか?そして、はい、それは絶え間なく実行する必要があります。

コード

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(); 

    } 
} 
+0

編集用フランク – Kushan

+0

@フランク・ヴァン・プフレンはこの問題を親切に助けてくれました。数日以来これに固執しています。私が持っているサービスは確かに私がそれを望む方法で動作しますが、それが確実なショットの方法であるかどうかわかりません。 – Kushan

+0

_はい、それは常に実行する必要があります_:[ドーズモード]のため、API 23以上のデバイスでは不可能です(https://developer.android.com/training/monitoring-device-state/doze-standby .html) –

答えて

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の状態を知らない、または気にしません。場所がクライアントにとって重要であるという事実は、実際にはサーバー上で管理されます。つまり、変更に気づき、それをリスニングクライアントに伝播させる責任が最終的にあります。

+0

あなたの言ったように、問題は実際のサブスクリプションではありません。それは脱退です。トピックを購読すると、fcmを使用してユーザーにプッシュ通知が送信されます。私の考えは、ユーザーがニュースページAを放棄しているが、そのページからの更新情報がまだ通知されると、彼/彼女は怒られるだろうと考えました。リスナーをサービスに入れないと、アプリが強制終了されたときに発生する登録解除が失われ、プッシュメッセージが引き続き表示されます。 – Kushan

+0

クライアントはFCMメッセージングを制御しません。これはすべてあなたのサーバー上にあります。サブスクリプション・データは、メッセージの時点でサーバー上で正確である必要があります。したがって、サーバー上のロジックは、どのクライアントに通知するかを決定できます。差をつけるために、サブスクリプションをすべてのクライアントに配布する必要はありません。 –

+0

登録トークンとiidを個別に管理する必要がある場合は、トピックを持つという目的を全面的に破ります。とにかく、gmsのMeasureBrokerServiceが私のプロセスにラッチされ、明白な理由でメモリがリークする別の問題に遭遇しています – Kushan

関連する問題