Android

关于android的alarmmanager使用过程中的坑(包括

2018-04-08  本文已影响1795人  努力深耕Android的小透明

  在最近的开发过程中,需要完成一个本地闹钟的功能,需求是固定的3个闹钟,每日重复,可以手动开关,需要时间精准,闹钟响起时需弹出dialog和播放音乐。
  在alarmmanager的使用过程中主要参考了github上的一个demo , Android-AlarmManagerClock ,遇到了一些比较难解决的问题,现在整理一下以防止以后再采坑。

1.关于不同手机版本精确闹钟的兼容

SDK API < 19

一般情况下,使用 AlarmManager 来执行重复定时任务的代码如下所示:

alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), TIME_INTERVAL, pendingIntent);

或者

alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), TIME_INTERVAL, pendingIntent);

setRepeating 该方法用于设置重复定时任务。

SDK API >= 19 && SDK API < 23

当你写好代码、满心欢喜地将程序跑在手机上的时候,傻眼了!你会发现在 Android 4.4 及以上版本的定时任务不是按照规定时间间隔来执行的。比如你设置了每隔 3 分钟发出一个 HTTP 请求,结果你一看莫名其妙地变成了隔 5 分钟发一次。

然后你查阅 Android 官网中关于 Android 4.4 API 会看到如下几句话:

1017209-9aeac689a801b0a7.png

恍然大悟!原来是 Google 为了追求系统省电,所以“偷偷加工”了一下唤醒的时间间隔。但也正如上面官网中所说的那样,如果在 Android 4.4 及以上的设备还要追求精准的闹钟定时任务,要使用 setExact() 方法。

所以,相应的代码就变成了这样:

// pendingIntent 为发送广播
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);
} else {
    alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), TIME_INTERVAL, pendingIntent);
}

private BroadcastReceiver alarmReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 重复定时任务
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent);
        }
        // to do something
        doSomething();
    }
};

当你写好了“加强版”的 AlarmManager 之后,内心肯定无比小激动。这下总应该行了吧?运行一下,果然没错!在 Android 4.4 上的确按照规定的时间间隔在执行任务。哈哈,这下大功告成了!!!

SDK API >= 23

在 Android 4.4 上品尝到胜利的甜头后,你顺便在 Android 6.0 的设备上测试了一下。结果。。。。。。你又 TMD 傻眼了!

发现在设备关屏静止一段时间后, AlarmManager 又又又不能正常工作了。相必此时你连日狗的心都有了吧!强忍着泪水,再次打开 Android 官网中关于 Android 6.0 变更 ,发现在 Android 6.0 中引入了低电耗模式和应用待机模式。然后接着往下看 对低电耗模式和应用待机模式进行针对性优化 ,发现会有下面一段话:

1017209-958b1bca16fe2651.png

啊啊啊啊啊啊!之前在 Android 4.4 上能用的 setExact() 方法在 Android 6.0 上因为低电耗模式又不能正常使用了。但是,Google 又又又提供了新的方法 setExactAndAllowWhileIdle() 来解决在低电耗模式下的闹钟触发。

所以,Attention!相关的代码又被改写为这样:

// pendingIntent 为发送广播
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);
} else {
    alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), TIME_INTERVAL, pendingIntent);
}

private BroadcastReceiver alarmReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 重复定时任务
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent);
        }
        // to do something
        doSomething();
    }
};

2.Android 7.0 pendingIntent用intent传递的bug

问题比较少见,只有你在跨进程传递数据的时候会碰到,如pendingIntent中

在7.0中通过pendingIntent的bundle传递的数据时,你会发现serializable和parcelable的数据拿不到

如果你只传了string,那是没问题的,但是如果你传了string和一个serializable你会发现,不光serializable拿不到,连string也拿不到了,黑人问好脸吧

原因参考:

https://commonsware.com/blog/2016/07/22/be-careful-where-you-use-custom-parcelables.html

解决方案1:

https://stackoverflow.com/questions/18000093/how-to-marshall-and-unmarshall-a-parcelable-to-a-byte-array-with-help-of-parcel/18000094#18000094

他的解决方法很独特,将所有数据转为基本类型bytes再去传递,当然,这可以解决我们的问题

然后我就想既然byte能解决,只传string也没问题,那为什么不干脆直接传string嗯,所以另一个简单的修改方法就是把所有的对象全部转成jsonstring去传递,也可以解决问题

解决方案2:

继续寻找有没有更加合适的方案时发现google的issuetracker中有个大神发现了一个更简单的方法,直接把所有数据放到bundle里,然后将bundler作为参数传递即intent.putExtra("data",bundle)也可以解决问题,

详情参考:

https://issuetracker.google.com/issues/37097877

参考代码:

        Intent intent = new Intent(ALARM_ACTION);
        Bundle bundle = new Bundle();
        bundle.putInt(AlarmConfig.DATA_BUNDLE_ALARM_ID,id);
        bundle.putLong(AlarmConfig.DATA_BUNDLE_INTERVALMILLIS, intervalMillis);
        intent.putExtra(AlarmConfig.DATA_BUNDLE, bundle);
        // 这个pendingIntent是Intent的包装类, 在闹钟到点的时候能够发送广播给AlarmOnTimeReceiver
        PendingIntent sender = PendingIntent.getBroadcast(context, id, intent, PendingIntent
                .FLAG_CANCEL_CURRENT);

另外两个问题是关于魅族手机适配的问题:
发现在魅族手机上出现2个问题,魅族M6版本 Flyme 6.1.4.6A ,Android 版本 7.1.2 :

  1. 实现一个通知栏通知的功能,闹钟响起来就出现通知通知栏,setContentText()中的内容要是有 感叹号(!) 就通知就出现不了,代码如下 :
    //发送通知
    private void showNotification(AlarmClock clock) {
    int NOTIFICATION_ID = clock.getId();
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
    builder.setContentTitle
    ("闹钟响起了").setContentText("时间到了哦!").setAutoCancel(true).setColor(Color
    .rgb(134, 177, 194)).setSmallIcon(R.mipmap.ic_launcher).setLargeIcon
    (BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)).setDefaults
    (NotificationCompat.DEFAULT_ALL);
    //要跳转到主页面
    Intent intent = new Intent(this, HomeActivity.class);
    PendingIntent operation = PendingIntent.getActivity(this, NOTIFICATION_ID, intent, 0);
    builder.setFullScreenIntent(operation, false); // 悬挂式Notification(5.0 新增)
    builder.setContentIntent(operation);
    Notification notification = builder.build();
    mNotificationManager.notify(NOTIFICATION_ID, notification);
    }

2.使用AlarmManager ,在App中按Home键锁屏后闹钟失效(只有按Home键锁屏这一种情况,按返回键没有这个问题) , AlarmManager接收不到广播 ,导致闹钟失效

解决问题1最终还是没有解决
解决问题2:通过魅族手机-设置-关闭智能休眠模式

魅族手机的问题说明 手机厂商改动系统太多了, 给开发者带来了无数适配的坑.... = =

参考文章:
关于使用AlarmManager的注意事项
Android 7.0 pendingIntent bug(AlarmManager通过PendingIntent传递数据(跨进程数据传递)

上一篇 下一篇

猜你喜欢

热点阅读