都0202年了,用AlarmManager作为一种后台唤醒手段好像有点落伍了。有如下原因:
- 时间越往后,后台唤醒变得越来越难,不说谷歌官方的安卓6.0doze、8.0startService等限制,光国内厂商,也不会让你痛快地在后台搞事情,各种端外推送不得不接,华为杀后台等让你进程活不了多久。
- 谷歌本身有推出过JobScheduler等组件。然而之前经测试,很多手机的JobScheduler功能不一,甚至有些小米手机似乎裁剪了JobScheduler的能力,使其无法使用,而AlarmManager反而较为稳定。
仍然要在后台进行一小些操作的需求以及AlarmManager的稳定性让我选择了它。
选择了AlarmManager自然要了解它的脾气:
- 把定时时间设置得很短时,在后台时,系统为了在统一的“维护窗口”时间进行处理以节省电量,则会推迟他的时间。所以如果对性能有要求,可以在前后台时做不同的处理以最大限度地提升响应效率。
- 有一些天然坑,如三星手机会出现alarm数超过限制的问题,原因是FLAG_CANCEL_CURRENT标记无法正确地取消alarm,可以使用FLAG_UPDATE_CURRENT标记来规避这种问题。
AlarmManager创建alarm在各个版本的行为不太一样但大致功能相同,封装如下:
private static void schedulePendingIntent(Context ctx, PendingIntent pendingIntent, long delayMs){
long nextAlarmInMilliseconds = System.currentTimeMillis() + delayMs;
AlarmManager alarmManager = (AlarmManager) ctx.getSystemService(Service.ALARM_SERVICE);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds,
pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds,
pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds,
pendingIntent);
}
}
下面测测Alarm的行为:
public class MainActivity extends AppCompatActivity {
BroadcastReceiver receiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("action");
for (int i = 0; i<100; i++){
String action = "action" + i;
intentFilter.addAction(action);
}
registerReceiver(receiver = new TheReceiver(), intentFilter);
scheduleAlarm();
}
private void scheduleAlarm(){
for (int i = 0; i<5; i++){
String action = "action" + i;
Intent intent = new Intent().setAction(action).putExtra("v", i);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
schedulePendingIntent(this, pendingIntent, 1000*1);
}
}
private static void schedulePendingIntent(Context ctx, PendingIntent pendingIntent, long delayMs){
long nextAlarmInMilliseconds = System.currentTimeMillis() + delayMs;
AlarmManager alarmManager = (AlarmManager) ctx.getSystemService(Service.ALARM_SERVICE);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds,
pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds,
pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, nextAlarmInMilliseconds,
pendingIntent);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
}
public class TheReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
int v = intent.getIntExtra("v", 0);
log("action = " + action + " v = " + v);
}
}
private void log(String log){
Log.i("AlarmLog", log);
}
}
执行结果如下:
2020-11-02 11:14:44.645 29772-29772/com.nykj.testalarm I/AlarmLog: action = action0 v = 0
2020-11-02 11:14:49.646 29772-29772/com.nykj.testalarm I/AlarmLog: action = action3 v = 3
2020-11-02 11:14:49.652 29772-29772/com.nykj.testalarm I/AlarmLog: action = action4 v = 4
2020-11-02 11:14:49.656 29772-29772/com.nykj.testalarm I/AlarmLog: action = action2 v = 2
2020-11-02 11:14:49.660 29772-29772/com.nykj.testalarm I/AlarmLog: action = action1 v = 1
requestCode唯一的情况下,action使用不同的值,FLAG_UPDATE_CURRENT不会影响到上一次alarm。
修改代码,让action固定:
private void scheduleAlarm(){
for (int i = 0; i<5; i++){
String action = "action";
Intent intent = new Intent().setAction(action).putExtra("v", i);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
schedulePendingIntent(this, pendingIntent, 1000*1);
}
}
结果:
2020-11-02 19:05:39.937 2168-2168/com.nykj.testalarm I/AlarmLog: action = action v = 4
我们看到,我们提交了5次,最后只响应了1次,FLAG_UPDATE_CURRENT生效了,生效的条件是requestCode固定、action也固定。
我们让requestCode不固定呢?
private void scheduleAlarm(){
for (int i = 0; i<5; i++){
String action = "action";
Intent intent = new Intent().setAction(action).putExtra("v", i);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, i, intent, PendingIntent.FLAG_UPDATE_CURRENT);
schedulePendingIntent(this, pendingIntent, 1000*1);
}
}
结果:
2020-11-02 19:07:42.828 2539-2539/com.nykj.testalarm I/AlarmLog: action = action v = 0
2020-11-02 19:07:47.831 2539-2539/com.nykj.testalarm I/AlarmLog: action = action v = 3
2020-11-02 19:07:47.836 2539-2539/com.nykj.testalarm I/AlarmLog: action = action v = 4
2020-11-02 19:07:47.841 2539-2539/com.nykj.testalarm I/AlarmLog: action = action v = 2
2020-11-02 19:07:47.846 2539-2539/com.nykj.testalarm I/AlarmLog: action = action v = 1
可见,requestCode不同时,相当于这是5个PendingIntent去打开Alarm,它们互不影响,与不固定action且固定requestCode的情况相同。
这也印证了FLAG_UPDATE_CURRENT标记的用法:
/**
* Flag indicating that if the described PendingIntent already exists,
* then keep it but replace its extra data with what is in this new
* Intent. For use with {@link #getActivity}, {@link #getBroadcast}, and
* {@link #getService}. <p>This can be used if you are creating intents where only the
* extras change, and don't care that any entities that received your
* previous PendingIntent will be able to launch it with your new
* extras even if they are not explicitly given to it.
*/
public static final int FLAG_UPDATE_CURRENT = 1<<27;