我有一个项目,用户可以跨多个设备进行多次登录。
现在,用户可以在任何设备上订阅特定主题,并且需要其余的设备登录也应该这样做。类似的情况是当一个设备取消订阅时,其余设备也应该遵循套件。
为了做到这一点,我在每个用户下创建了一个Node,其中所有订阅都保存在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();
}
}
答案 0 :(得分: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.如果正在收听的价值发生了很大的变化,您可能需要重新考虑如何保持用户的设备忙于这些更新。
聆听者本身不是" polling"或者"重试"。整个Firebase套接字连接正在执行此操作。听众不知道幕后发生了什么。它是否正在接收更新。它不知道或关心底层websocket的状态。客户端感兴趣的位置实际上是在服务器上管理的 - 这是最终负责注意更改并将其传播给侦听客户端的事实。