定时开关机(二):AlarmManager的使用及对定时不准问题的修改

定时开关机(二):AlarmManager的使用及对定时不准问题的修改

AlarmManager是系统提供的定时服务,通过AlarmManager发送一个定时广播,接收方收到广播后,启动关机活动,这样就实现了定时关机功能。
首先看一下如何发送定时广播。

AlarmManager发送定时广播

public void setShutDownAlarm(){
		//设好的定时关机时间--hour:minute
        int hour = 0;//设置的定时关机时间-hour
        int minute = 0;//设置的定时关机时间-minute
        try {
            hour = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.AUTO_SHUTDOWN_HOUR);               
        } catch (SettingNotFoundException snfe) {
        }

        try {
            minute = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.AUTO_SHUTDOWN_MINUTE);             
        } catch (SettingNotFoundException snfe) {
        }
        
        Calendar offCal = Calendar.getInstance();
        long currentTime = offCal.getTimeInMillis();
        Log.d(TAG, "current time :" + currentTime);
        offCal.set(Calendar.HOUR_OF_DAY,hour);  
        offCal.set(Calendar.MINUTE,minute);
        offCal.set(Calendar.SECOND, 0);
        long shutDownTime = offCal.getTimeInMillis();
		
		//如果设置的关机时间早于当前时间,则将关机时间延后一天。
		//因为如果设置的发送广播时间早于当前时间,会立即发送关机广播,导致立即关机,这不是我想要的结果
        if(currentTime>shutDownTime){
            offCal.add(Calendar.DAY_OF_MONTH, 1);
            shutDownTime = offCal.getTimeInMillis();
        }
        
		//注册定时广播
        Intent intent= new Intent(ACTION_AUTO_OFF);  
        PendingIntent shutPendingIntent = PendingIntent.getBroadcast(mContext,0,intent,PendingIntent.FLAG_CANCEL_CURRENT);  
        AlarmManager alarm = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);  
        alarm.set(AlarmManager.RTC_WAKEUP,shutDownTime, shutPendingIntent); //此处有坑,后面说
     
    }	

发送定时广播,主要是通过最后一段代码,前面的代码是为了获取发送广播的时间shutDownTime。要发送的广播是以PendingIntent来获取的,PendingIntent.getBroadcast(Context context, int requestCode, Intent intent, int flags)有四个参数,下面逐一说明:
context:上下文
requestCode:每次requestcode不同,就能产生多个Pendingintent,一般写0就可以
intent:PendingIntent是对Intent的封装,intent即为要封装的Intent
flags:不同操作的标识,有如下四种取值:
1.FLAG_CANCEL_CURRENT:如果AlarmManager管理的PendingIntent已经存在,那么将会取消当前的PendingIntent,从而创建一个新的PendingIntent.
2.FLAG_UPDATE_CURRENT:如果AlarmManager管理的PendingIntent已经存在,让新的Intent更新之前Intent对象数据,例如更新Intent中的Extras,另外,我们也可以在PendingIntent的原进程中调用PendingIntent的cancel ()把其从系统中移除掉
3.FLAG_NO_CREATE:如果AlarmManager管理的PendingIntent已经存在,那么将不进行任何操作,若描述的Intent不存直接返回NULL(空).
4.FLAG_ONE_SHOT:该PendingIntent只作用一次.在该PendingIntent对象通过send()方法触发过后,PendingIntent将自动调用cancel()进行销毁,那么如果你再调用send()方法的话将会失败,系统将会返回一个SendIntentException.

然后通过AlarmManager.set(int type, long triggerAtMillis, PendingIntent operation) 设定一个alarm。引用的set方法有三个参数,下面逐一说明:
type:闹钟类型,有五个可选值:
1.AlarmManager.ELAPSED_REALTIME: 闹钟在手机睡眠状态下不可用,该状态下闹钟使用相对时间(相对于系统启动开始),状态值为3;
2.AlarmManager.ELAPSED_REALTIME_WAKEUP 闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间,状态值为2;
3.AlarmManager.RTC 闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间,状态值为1;
4.AlarmManager.RTC_WAKEUP 表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间,状态值为0;
5.AlarmManager.POWER_OFF_WAKEUP 表示闹钟在手机关机状态下也能正常进行提示功能,所以是5个状态中用的最多的状态之一,该状态下闹钟也是用绝对时间,状态值为4;不过本状态好像受SDK版本影响,某些版本并不支持。
triggerAtMillis:触发时间,单位:ms。由于本文第一个参数使用的是RTC_WAKEUP,所以要使用绝对时间。
operation:即PendingIntent,绑定了闹钟的执行动作,本文是发送一个ACTION_AUTO_OFF广播。

至此,就可以发送一个定时广播了,接下来是广播接收方的操作。

触发关机操作

		IntentFilter alarm_filter = new IntentFilter(ACTION_AUTO_OFF);
        alarm_filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
        mContext.registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {

                if (DEBUG) Log.d(TAG, "shut down alarm received");                

                Toast.makeText(context, "system is shutting down",
                    Toast.LENGTH_SHORT).show();
                Intent newIntent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
                newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                mContext.startActivity(newIntent);
                     
            }
        }, alarm_filter);

可以看到,接收到广播后,启动了系统的关机Activity,系统进入关机流程。如果想添加加关机提示可以在加在此处。

闹钟不准

在测试过程中发现,使用AlarmManager设置短时间的闹钟时没有发现问题,但设置长时间的闹钟比如十个小时左右时,闹钟会延迟十几分钟(感谢同事测出)。经调查,问题出在AlarmManager.set()上。

public void set(int type, long triggerAtMillis, PendingIntent operation) {
        setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null,
                null, null, null);
    }

调用的是AlarmMangerService的setImple:

void setImpl(int type, long triggerAtTime, long windowLength, long interval,
            PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
            int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
            int callingUid, String callingPackage) 

setImpl第三个参数windowLength决定了闹钟是否精准。在AlarmManager.set方法中,传入的是legacyExactLength()。

 private long legacyExactLength() {
        return (mAlwaysExact ? WINDOW_EXACT : WINDOW_HEURISTIC);
    }

再来看一下mAlwaysExact 的值

mAlwaysExact = (mTargetSdkVersion < Build.VERSION_CODES.KITKAT);

也就是说,如果SDK的版本小于 KITKAT(19)时,mAlwaysExact 为true。本文使用的是android N,所以mAlwaysExact 为false。所以legacyExactLength() = WINDOW_HEURISTIC,即不精确。
将set方法改为setExactAndAllowWhileIdle,可以看到,传入的参数是WINDOW_EXACT。

public void setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) {
        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation,
                null, null, null, null, null);
    }

修改后测试,闹钟精准。

以上是AlarmManager使用过程中的经验总结,有不正确的地方望指正。

你可能感兴趣的:(定时开关机(二):AlarmManager的使用及对定时不准问题的修改)