android定时闹钟:Service+BroadcastReceiver+AlarmManager+NotificationManager

在开发android的过程中,需要一个定时提醒的功能,原本以为是一个很简单的功能,但是发现网上能找到的讲解或者代码都很或多或少地缺少一部分的设置和关键代码,所以我才注册了简书账户,分享一下。

在研究的这几天,遇到了以下四个问题:

1、标题中的技术都没有接触过;

2、开发过程中android studio项目的SDK过高导致的方法报错;

3、安装APK的手机版本过高导致状态栏无法显示通知信息;

4、高版本的手机对于软件的权限管理非常严格;

下面我就以我的代码来分享一下。


软件环境:

android studio3.6.3

项目选择的SDK:android 4.1(API:16)


使用的技术:

AlarmManager:

AlarmManager:AlarmManager类是android提供的用于在未来的制定时间弹出一个警告信息,或者完成指定操作的类。实际上AlarmManage是一个全局的定时器,在AlarmManage设置警告后,android将自动开启目标应用,即使手机处于休眠状态。所以,使用AlarmManage类也可以实现关机后仍可响应的闹钟。

AlarmManage对象的常用方法(其余的方法可以另外搜索):

set(int type, long triggerAtTime, PendingIntent operation); // 执行一次闹钟

setRepeating(int type, long triggerAtTime, long interval, PendingIntent operation); //循环闹钟

参数介绍:

type:服务类型包含四中类型

ELAPSED_REALTIME

在指定的延时过后,发送广播,但不唤醒设备(闹钟在睡眠状态下不可用)。如果在系统休眠时闹钟触发,它将不会被传递,直到下一次设备唤醒。

ELAPSED_REALTIME_WAKEUP

在指定的延时过后,发送广播,并唤醒设备(即使关机也会执行operation所对应的组件)。

延时是要把系统启动的时间SystemClock.elapsedRealtime()算进去的,具体用法看代码。

RTC

指定当系统调用System.currentTimeMillis()方法返回的与triggerAtTime相等时启动operation所对应的设备(在指定的时刻,发送广播,但不唤醒设备)。如果在系统休眠时闹钟触发,它将不会被传递,直到下一次设备唤醒(闹钟在睡眠状态下不可用)。

RTC_WAKEUP

指定当系统调用System.currentTimeMillis()方法返回的与triggerAtTime相等时启动operation所对应的设备(在指定的时刻,发送广播,并唤醒设备)。即使系统关机也会执行operation所对应的组件。

triggerAtTime:表示闹钟执行时间。

PendingIntent operation:表示闹钟响应动作:

如果是通过启动服务来实现闹钟提示的话,PendingIntent对象的获取就应该采用Pending.getService(Contextc,int i,Intentintent,int j)方法;

如果是通过广播来实现闹钟提示的话,PendingIntent对象的获取就应该采用PendingIntent.getBroadcast(Context c,inti,Intentintent,int j)方法;

如果是采用Activity的方式来实现闹钟提示的话,PendingIntent对象的获取就应该采用PendingIntent.getActivity(Contextc,inti,Intent intent,int j)方法。

如果这三种方法错用了的话,虽然不会报错,但是看不到闹钟提示效果。

interval:为闹钟间隔,以毫秒为单位。

内置的几个变量如下:

INTERVAL_DAY:设置闹钟,间隔一天

INTERVAL_HALF_DAY:设置闹钟,间隔半天

INTERVAL_FIFTEEN_MINUTES:设置闹钟,间隔15分钟

INTERVAL_HALF_HOUR:设置闹钟,间隔半个小时

INTERVAL_HOUR:设置闹钟,间隔一个小时

使用步骤:

第一步:获取AlarmManage对象;

AlarmManage alarm = (AlarmManage) getSystemService(Context.ALARM_SERVICE);

第二步:指定时间(判断当前时间和指定的时间之间的大小,如果当前时间 > 指定时间,则时间添加一天);

long firstTime = SystemClock.elapsedRealtime(); // 开机之后到现在的运行时间(包括睡眠时间)

long systemTime = System.currentTimeMillis();

Calendar calendar = Calendar.getInstance();

calendar.setTimeInMillis(System.currentTimeMillis());

// 这里时区需要设置一下,不然会有8个小时的时间差

calendar.setTimeZone(TimeZone.getTimeZone("GMT+8"));

calendar.set(Calendar.MINUTE, 11);

calendar.set(Calendar.HOUR_OF_DAY, 56);

calendar.set(Calendar.SECOND, 0);

calendar.set(Calendar.MILLISECOND, 0);

// 选择的定时时间

long selectTime = calendar.getTimeInMillis();

// 如果当前时间大于设置的时间,那么就从第二天的设定时间开始

if(systemTime > selectTime) {

Toast.makeText(MyService.this,"设置的时间小于当前时间", Toast.LENGTH_SHORT).show();

    calendar.add(Calendar.DAY_OF_MONTH, 1);

    selectTime = calendar.getTimeInMillis();

}

// 计算现在时间到设定时间的时间差

long time = selectTime - systemTime;

firstTime += time;

第三步:设置一个闹钟

1、设置一次性闹钟

(AlarmManager)alarm.set(AlarmManager.RTC_WAKEUP,firstTime,pi);

2、设置循环闹钟

(AlarmManager)alarm.setRepeating(AlarmManager.RTC_WAKEUP,firstTime,AlarmManager.INTERVAL_DAY, pi);

注意:pi为闹钟响应动作,详细介绍请看上面参数介绍。


BroadcastReceiver

BroadcastReceiver世界收广播通知的组件。BroadcastReceiver类是所有广播接收器的抽象基类。其实现类用来对发送出来的广播进行筛选并作出响应。广播接收器的生命周期非常简单,当消息到达时,接收器调用onReceive()(是实现BroadcastReceiver类时需要重写的方法)方法,在该方法结束后,BroadcastReceiver实例失效。

当用户需要进行广播时,可以通过Activity程序中的sendBroadcast()方法触发所有的广播组件,而每一个广播组件在进行广播启动之前,必须判断用户所传递的广播操作是否是指定的Action类型。如果是,则进行广播处理。

注意:sendBroadcast()为普通广播发送;sendOrderedBroadcast()为有序广播发送。

切记!!!一定要在AndroidManifest.xml文件中注册BroadcastReceiver。

可以手动注册:

    <receiver

    android:name=".RingReceived"

    android:enabled="true"

    android:exported="true"></receiver>

可以创建文件自动注册:

创建Broadcast


Service

Service(服务)是能够在后台长时间运行,并且不提供用户界面的应用程序组件。其他应用程序组件能启动Service,并且即便用户切换到另一个应用程序,Service还可以再后台运行。

Service分类:

按照启动方式可以分为两种类型:

Started Service:当应用程序组件(如Activity)通过调用startService()方法启动Service时,Service处于启动状态。一旦启动,Service能在后台无限期运行。

Bound Service:当应用程序组件通过调用bindService()方法绑定Service时,Service处于绑定状态。多个组件可以同时绑定到一个Service上,当它们都接触绑定时,Service被销毁。

Service创建:


创建Service


Notification

使用Notification类在状态栏上显示通知;

状态栏位于手机屏幕的最上方,一般用于显示手机当前的网络状态,系统时间以及电池状态等信息。

Android提供了Notification类和NotificationManager类处理状态上的通知信息。其中,Notification类代表的是具有全局效果的通知,而NotificationManager类则是用来发送Notification通知的系统服务。

使用步骤:

第一步,调用getSystemService()方法用于获取系统的NotificationManager服务。

NotificationManager mNotifyManager=(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

第二步,创建一个Notification对象。

NotificationCompat.Builder mBuilder =new NotificationCompat.Builder(context,PUSH_CHANNEL_ID);

mBuilder.setContentTitle(/*通知的标题*/)

.setContentText(/*通知的内容*/)

.setSmallIcon(/*通知的图标*/)

.setChannelId(/*通知的ID*/);


项目介绍:


第一步:

在AndroidManifest.xml文件中声明权限和注册Service、receiver。

添加权限:

<!-- 添加振动权限 -->

<uses-permission android:name="android.permission.VIBRATE" />

<!--允许应用在手机屏幕关闭后后台进程仍然运行的权限-->

<uses-permission android:name="android.permission.RECEIVE_USER_PRESENT" />

<!--接收开机后的广播权限-->

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

注册Service、receiver:

<receiver

   android:name=".RingReceived"

    android:enabled="true"

    android:exported="true"></receiver>

<service

    android:name=".MyService"

    android:enabled="true"

    android:exported="true"

    android:persistent="true"

    android:priority="1000"></service>

第二步:

在主活动loginActivity的initView() 中启动Service。loginActivity继承BaseActivity。如果继承的是AppCompatActivity,则在onCreate()中启动Service。

Intent intent =new Intent(loginActivity.this, MyService.class);

startService(intent);

第三步:

在MyService的onStartCommand()方法中设置闹钟。

import android.app.AlarmManager;

import android.app.PendingIntent;

import android.app.Service;

import android.content.Intent;

import android.os.IBinder;

import android.os.SystemClock;

import android.widget.Toast;

import java.util.Calendar;

import java.util.TimeZone;

public class MyServiceextends Service {

public MyService() {

}

private AlarmManageram;

    private PendingIntentpi;

    @Override

    public IBinderonBind(Intent intent) {

// TODO: Return the communication channel to the service.

        //throw new UnsupportedOperationException("Not yet implemented");

        return null;

    }

@Override

    public int onStartCommand(Intent intent, int flags, int startId) {

getAlarmTime();

        return START_REDELIVER_INTENT; //这里为了提高优先级,选择START_REDELIVER_INTENT 没那么容易被内存清理时杀死

    }


    private void getAlarmTime() {

Intent startNotification =new Intent(this, RingReceived.class);  //这里启动的广播,下一步会教大家设置

        am = (AlarmManager) getSystemService(ALARM_SERVICE);  //这里是系统闹钟的对象

        pi = PendingIntent.getBroadcast(this, 0, startNotification, PendingIntent.FLAG_UPDATE_CURRENT);    //设置事件

        long firstTime = SystemClock.elapsedRealtime();    // 开机之后到现在的运行时间(包括睡眠时间)

        long systemTime = System.currentTimeMillis();

        Calendar calendar = Calendar.getInstance();

        calendar.setTimeInMillis(System.currentTimeMillis());

        // 这里时区需要设置一下,不然会有8个小时的时间差

        calendar.setTimeZone(TimeZone.getTimeZone("GMT+8"));

        calendar.set(Calendar.MINUTE, 11);

        calendar.set(Calendar.HOUR_OF_DAY, 56);

        calendar.set(Calendar.SECOND, 0);

        calendar.set(Calendar.MILLISECOND, 0);

        // 选择的定时时间

        long selectTime = calendar.getTimeInMillis();

        // 如果当前时间大于设置的时间,那么就从第二天的设定时间开始

        if(systemTime > selectTime) {

Toast.makeText(MyService.this,"设置的时间小于当前时间", Toast.LENGTH_SHORT).show();

            calendar.add(Calendar.DAY_OF_MONTH, 1);

            selectTime = calendar.getTimeInMillis();

        }

// 计算现在时间到设定时间的时间差

        long time = selectTime - systemTime;

        firstTime += time;

        am.setRepeating(AlarmManager.RTC_WAKEUP,firstTime,AlarmManager.INTERVAL_DAY, pi);

    }

}

第四步:

在RingReceived的onReceive()方法中设置状态栏的通知信息。

import android.app.Notification;

import android.app.NotificationChannel;

import android.app.NotificationManager;

import android.app.PendingIntent;

import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

import android.os.Build;

import android.util.Log;

import androidx.core.app.NotificationCompat;

public class RingReceivedextends BroadcastReceiver {

private int PUSH_NOTIFICATION_ID = (0x001);

    private StringPUSH_CHANNEL_ID ="PUSH_NOTIFY_ID";

    private  StringPUSH_CHANNEL_NAME ="PUSH_NOTIFY_NAME";

    NotificationCompat.BuildermBuilder;

    Notificationnotification;

    @Override

    public void onReceive(Context context, Intent intent) {

// TODO: This method is called when the BroadcastReceiver is receiving

        // an Intent broadcast.

//throw new UnsupportedOperationException("Not yet implemented");

        receive2(context);

        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        intent.setClass(context, MyService.class);

        context.startService(intent);  //回调Service,同一个Service只会启动一个,所以直接再次启动Service,会重置开启新的提醒,

    }

private void receive2(Context context) {

NotificationManager mNotifyManager=(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {

//当手机系统版本号大于8.0的时候就需要设置一个NotificationChannel属性

            mBuilder =new NotificationCompat.Builder(context,PUSH_CHANNEL_ID);

            NotificationChannel mChannel =new NotificationChannel(PUSH_CHANNEL_ID, PUSH_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);

            mNotifyManager.createNotificationChannel(mChannel);

            mBuilder.setContentTitle("闹钟提醒!")

.setContentText("不要忘记填写今日的工作日志!")

.setSmallIcon(R.drawable.alarmclock)

.setChannelId(PUSH_CHANNEL_ID);

        }else {

mBuilder =new NotificationCompat.Builder(context);

            mBuilder.setContentTitle("闹钟提醒!")

.setContentText("不要忘记填写今日的工作日志!")

.setSmallIcon(R.drawable.background1);

        }

//创建一个启动其他Acitivity的Intent

        Intent intent =new Intent(context, MainActivity.class);

        PendingIntent pi = PendingIntent.getActivity(context, 0 , intent, 0);

        //设置通知栏单击跳转

        mBuilder.setContentIntent(pi);

        //设置打开该通知,该通知自动消失

        mBuilder.setAutoCancel(true);

        notification =mBuilder.build();

        //在我们notify的时候,即更新通知栏的时候,同样也不能忘了这个PUSH_NOTIFICATION_ID

        mNotifyManager.notify(PUSH_NOTIFICATION_ID, notification);

    }

}

此处遇到了问题2和问题3。

问题2:开发过程中android studio项目的API过高导致的方法报错;

notification =mBuilder.build();

build()方法在项目SDK超过android4.1(API:16)时会被画红线。

因为由于 Notification.Builder 仅支持 Android 4.1及之后的版本,而android的SDK的特性是向上兼容。

注意:也可以不要求项目SDK为android4.1,只需要在build()标红线时,在红线部分:alt+enter添加通知类型。

问题3:安装APK的手机系统版本过高导致状态栏无法显示通知信息;

在手机系统版本号targetSdk高于Android8.0(API:26)的时候,系统不会添加默认的Channel。低于Android8.0的版本则会默认添加Channel。项目没有添加Channel,状态栏是不会显示通知信息的。

  if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {

//当手机系统版本号大于8.0的时候就需要设置一个NotificationChannel属性

            mBuilder =new NotificationCompat.Builder(context,PUSH_CHANNEL_ID);

          NotificationChannel mChannel =new

          NotificationChannel(PUSH_CHANNEL_ID, PUSH_CHANNEL_NAME,

          NotificationManager.IMPORTANCE_HIGH);

......

第五步:

测试程序。我不习惯用虚拟器进行测试,所以都是直接连接手机进行测试。在此处遇到了问题4。

问题4:高版本的手机对于软件的权限管理非常严格;

手机版本太高,对于软件的权限管理会非常严格。我在将.APK文件安装到手机上后,依然无法实现定时器功能,不论是在程序在后台运行过程中的定时提醒,还是程序已经关闭后的定时提醒,都实现不了。

后来发现需要手动去对软件进行权限管理。

操作:

设置  --> 应用 -----> 应用启动管理 ----> 找到相应软件,设置为手动管理(勾选:允许自启动,允许后台活动)。

写在最后:对于这一块的内容,也是第一次接触。如果大家发现有什么问题,欢迎一起探讨。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容