Android Service,AlarmManager組合實現定時任務踩的坑
做專案時遇到一個場景:app需要定時訪問後臺,感知獲取登入使用者有沒有最新的訊息。
我採用了定義一個Service,在onStartCommand()方法中請求後臺獲取最新訊息,接著建立一個AlarmManager來延時傳送廣播,再定義一個廣播接收器,接收到一個廣播後,接收器觸發startService( ),這時service的onStartCommand再次被觸發,就再次請求獲取最新訊息,繼續傳送廣播,如此往復。
使用者名稱userId是登入成功後就能得到的。在登入成功進入主介面後,並立即啟動MyNewTaskService
MyNewTaskService的主要業務邏輯程式碼如下:
public int onStartCommand(Intent intent, int flags, int startId) {
mRealm = Realm.getDefaultInstance();
mUserDao = new UserDao(mRealm);//獲取當前登入使用者id
mTaskTimeDao = new TaskTimeDao(mRealm);
if(intent!=null){
userId = intent.getStringExtra("userId");
}else{
userId = mUserDao.getUserInfo().getUserId();//realm也儲存了使用者資訊,但不一定每次都能獲取到
}
if(null!=userId){
//...做一些事情,比如訪問網路獲取最新訊息
//設定定時操作
AlarmManager am= (AlarmManager)getSystemService(ALARM_SERVICE);
//1分鐘請求一次更新
int elapseTime = 1*60*1000;
long interval = SystemClock.elapsedRealtime()+elapseTime;
//傳遞userId引數給MyNewTaskReceiver,為了到時候回傳回來
Intent i = new Intent(this,MyNewTaskReceiver.class);
i.putExtra("userId",userId);
PendingIntent pi = PendingIntent.getBroadcast(this,0,i,0);
am.set(AlarmManager.ELAPSED_REALTIME,interval,pi);
}
return super.onStartCommand(intent, flags, startId);
}
下面是MyNewTaskReceiver類
public class MyNewTaskReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
Intent i= new Intent(context, MyNewTaskService.class);
i.putExtra("userId",intent.getStringExtra("userId"));
Log.i("user",intent.getStringExtra("userId"));
context.startService(i);
//每1分鐘將接收到一次廣播,這時觸發service的onStartCommand()執行需要的重複操作
}
}
但是app測試時發現,當第一次登入進去後,例如此時使用者名稱是 ‘zs’ ,這是後臺可以每1分鐘接收到’zs’的最新的訊息。然而,現在’zs’退出登入,用’ls’賬號登入,這時後臺返回的訊息仍然還是’zs’的,而不是’ls’的。
後來發現,每次第一個登入的使用者登陸後,後面不管任何其他使用者再次登入同一臺手機,後臺返回的都是第一個使用者的資訊。因為App設計的時第一次登入成功後,Service會一直在後臺執行。所以懷疑第一次建立Service後,Service所持有的userId並沒有更新。
經過除錯,發現,MyNewTaskReceiver接收的Intent中獲取的userId全都是第一次登入使用者的id。猜想可能傳送廣播時傳入的引數有問題。
經過查閱安卓API發現,定時器AlarmManager初始化時傳入的PendingIntent的建構函式的第四個引數需要一個Flag常量。
之前我傳入的是0。沒有作用。
當改為 FLAG_UPDATE_CURRENT 時,每次傳送的廣播傳遞的Intent就能更新了。
將onStartCommand()方法的這一行改為
PendingIntent pi = PendingIntent.getBroadcast(this,0,i,0);
PendingIntent pi = PendingIntent.getBroadcast(this,0,i,FLAG_UPDATE_CURRENT);
這時不管那個使用者登入,後臺都可以定時返回當前使用者的訊息了。
總結&注意:
1 該示例的後臺Service在使用者關閉app後並沒有stop。如果stop,相信不會有這種Bug產生。因為定時任務是依附於Service存在的。
2 曾經嘗試在主Activity的onDestory()中呼叫StopService()方法讓app關閉後service也結束執行,沒有成功。如果service在app關閉後也停止執行,相信不會有這種bug產生。
3 app經過多方面測試才能完善。