android计时与系统休眠
TIP:可能写的有点仓促,具体的可以联系我(*^__^*)
1.摘要:
之前做项目的时候,修改1个倒计时秒表,本来以为比较简单,但是发现很多有趣的东西。我们项目里面用的是Timer计时的方法,但是,当系统休眠的时候,Timer也是处于休眠状态的。后来,我改进了几个方法,1个是handle+message的方法,还有1个是handle+runnable的方法,还有handle+Thread的方法。但是一样发现系统休眠的时候,这些一样是处于休眠状态的。后来上网查找了1下,这和android的架构有关:
2.原理
Android手机有两个处理器,1个叫Application Processor(AP),1个叫Baseband Processor(BP)。AP是ARM架构的处理器,用于运行Linux+Android系统;BP用于运行实时操作系统(RTOS),通讯协议栈运行于BP的RTOS之上。非通话时间,BP的能耗基本上在5mA左右,而AP只要处于非休眠状态,能耗最少在50mA以上,履行图形运算时会更高。另外LCD工作时功耗在100mA左右,WIFI也在100mA左右。1般手机待机时,AP、LCD、WIFI均进入休眠状态,这时候Android中利用程序的代码也会停止履行。Android为了确保利用程序中关键代码的正确履行,提供了Wake
Lock的API,AlarmManager这个类使用的是BP的芯片,使得利用程序有权限通过代码禁止AP进入休眠状态。但如果不领会Android设计者的意图而滥用Wake Lock API,为了本身程序在后台的正常工作而长时间禁止AP进入休眠状态,就会成为待电机池杀手。
首先,完全没必要担心AP休眠会致使收不到消息推送。通讯协议栈运行于BP,1旦收到数据包,BP会将AP唤醒,唤醒的时间足够AP履行代码完成对收到的数据包的处理进程。其它的如Connectivity事件触发时AP一样会被唤醒。那末唯1的问题就是程序如何履行向服务器发送心跳包的逻辑。你明显不能靠AP来做心跳计时。Android提供的Alarm Manager就是来解决这个问题的。Alarm应当是BP计时(或其它某个带石英钟的芯片,不太肯定,但绝对不是AP),触发时唤醒AP履行程序代码。那末Wake
Lock API有啥用呢?比如心跳包从要求到应对,比如断线重连重新登陆这些关键逻辑的履行进程,就需要Wake Lock来保护。而1旦1个关键逻辑履行成功,就应当立即释放掉Wake Lock了。两次心跳要求间隔5到10分钟,基本不会怎样耗电。除非网络不稳定,频繁断线重连,那种情况办法不多。
网上有说使用AlarmManager,由于AlarmManager 是Android 系统封装的用于管理 RTC 的模块,RTC (Real Time Clock) 是1个独立的硬件时钟,可以在 CPU 休眠时正常运行,在预设的时间到达时,通过中断唤醒 CPU。
3.实验
后来,本人使用AlarmManager,但是又碰到不准的问题,而且当系统繁忙的时候(比如刚开机前1分钟),更加明显,后来查看官方的API说明。由于我之前使用的方法大致是下面的原理:
-
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
-
Intent intent = new Intent("com.liu.alarm.ACTION_SEND");
-
PendingIntent sendIntent = PendingIntent.getBroadcast(this, 0, intent,PendingIntent.FLAG_UPDATE_CURRENT);
-
am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime() + 1000,1000, sendIntent);
然后注册1个广播接收器接收
-
public void onReceive(final Context context, Intent intent) {
-
-
需要的操作;}
我们看看官方给出的API说明:
Note: as
of API 19, all repeating alarms are inexact. If your application needs precise delivery times then it must use one-time exact alarms, rescheduling each time as described above. Legacy applications whosetargetSdkVersion
is
earlier than API 19 will continue to have all of their alarms, including repeating alarms, treated as exact.意思大概是说19或19以后,为了优化电池,该计时操作不准确了(可能在定义时间以后响应),但是19之前的任然准确。
If your application has strong ordering requirements there are other APIs
that you can use to get the necessary behavior; see setWindow(int,
long, long, PendingIntent)
andsetExact(int,
long, PendingIntent)
.意思是虽然19(包括)以后可能不准确,但是android保存了两个接口,这两个接口是准确的。本人因而使用了这其中1个。
方法以下 TIP:条件是确保你的API是19的或更高,否则做下判断
public static boolean isKitKatOrLater() {
return Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2;
}
-
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
-
Intent intent = new Intent("com.liu.alarm.ACTION_SEND");
-
PendingIntent sendIntent = PendingIntent.getBroadcast(this, 0, intent,PendingIntent.FLAG_UPDATE_CURRENT);</span>
-
if(isKitKatOrLater(){
-
am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime() + 1000, sendIntent);
-
}else{
-
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime() + 1000, sendIntent);
-
}
然后注册1个广播接收器接收
-
public void onReceive(final Context context, Intent intent) {
-
if(isKitKatOrLater(){
-
am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime() + 1000, sendIntent);
-
}else{
-
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime() + 1000, sendIntent);
-
}
-
-
if(isKitKatOrLater(){
-
am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime() + 1000, sendIntent);
-
需要的操作;}
但是后来有发现,即便使用了精确的时间计时,但是在系统刚开机的1分钟内计时,依然会有不及时响应的情况,所以,后来自己想了1下,毕竟是两块芯片,多进程多线程还要斟酌好多同步的问题,两块芯片也不可能配合的那末完美无缺,假设1块特别繁忙的时候。所以,后来我参考源码的方法:正常情况下使用线程或Timer计时,并且使用
AlarmManager设定1个计时结束的时间,当履行Activity的Onpause的时候使用AlarmManager的计时响应。当Onresume的时候,根据走过的时间刷新界面。下面是我全部的代码
/**
* 2014⑴2-05 BenMin FEIXUN_DESKCLOCK_BENMIN_001
* modify PWEUN⑷141 to remind the user when the timing is over.
* 2014⑴2⑴7 BenMin FEIXUN_DESKCLOCK_BENMIN_002
* modify to ensure the fragment attached to Activity when using getResources() function.
* 2014⑴2⑵6 BenMin FEIXUN_DESKCLOCK_BENMIN_003
* modify PLGN⑷89 to stop the ring when press the back menu.
*/
package com.phicomm.keyer;
import java.util.Timer;
import java.util.TimerTask;
import android.R.integer;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaPlayer;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.NumberPicker;
import android.widget.NumberPicker.Formatter;
import android.widget.NumberPicker.OnValueChangeListener;
import android.widget.TextView;
import com.phicomm.deskclock.DeskClockFragment;
import com.phicomm.deskclock.FxDeskClock;
import com.phicomm.deskclock.R;
public class FxKeyerFragment extends DeskClockFragment implements
OnClickListener, Formatter, OnValueChangeListener {
private NumberPicker keyerHour;
private NumberPicker keyerMinute;
private NumberPicker keyerSecond;
private Button kbtnStart;
private Button kbtnPause;
private Button kbtnReset;
private LinearLayout time;
private TextView hour;
private TextView minute;
private TextView second;
private int hourtime;
private int minutetime;
private int secondtime;
private Timer timer;
private TimerTask task;
private MediaPlayer alarmMusic;
private AlertDialog dialog = null;
private static final int originState = 0; //original state
private static final int timerState = 1; //timer state
private static final int pauseState = 2; //pause
private static final int resetState = 3; //reset
private static final int overState = 4; //over
private int stateNow = originState;
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what == 1){
if(secondtime > 0) {
secondtime --;
second.setText(format(secondtime));
} else if(minutetime > 0) {
minutetime --;
secondtime = 59;
second.setText(format(secondtime));
minute.setText(format(minutetime));
} else if(hourtime > 0) {
hourtime --;
secondtime = 59;
minutetime = 59;
second.setText(format(secondtime));
minute.setText(format(minutetime));
hour.setText(format(hourtime));
}
if (hourtime <= 0 && minutetime <= 0 && secondtime <= 0) {
startRing();
}
}
}
};
private AlarmManager mAlarmManager;
private PendingIntent sendIntent;
public static String ALARM_KEYER_ACTION = "com.phicomm.keyer.alarm_keyer_action";
private BroadcastReceiver mBroadcastReceiver;
private boolean isRing = false;
@Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
mAlarmManager = (AlarmManager) this.getActivity().getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent();
intent.setAction(ALARM_KEYER_ACTION);
sendIntent = PendingIntent.getBroadcast(getActivity(), 0, intent , PendingIntent.FLAG_UPDATE_CURRENT);
mBroadcastReceiver = new BroadcastReceiver(){
@Override
public void onReceive(Context arg0, Intent arg1) {
// TODO Auto-generated method stub
kbtnPause.setEnabled(false);
startRing();
}
};
IntentFilter filter = new IntentFilter(ALARM_KEYER_ACTION);
getActivity().registerReceiver(mBroadcastReceiver, filter );
}
private void startRing(){
if (timer != null) {
timer.cancel();
}
stateNow = overState;
if (isAdded() && isRing == false) {
kbtnPause.setTextColor(getResources().getColor(R.color.text_summery));
alarmMusic = MediaPlayer.create(getActivity(), R.raw.in_call_alarm);
alarmMusic.setLooping(true);
alarmMusic.start();
isRing = true;
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
.setTitle(R.string.tip)
.setMessage(R.string.tip_text)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface arg0, int arg1) {
// TODO Auto-generated method stub
alarmMusic.stop();
alarmMusic.release();
alarmMusic = null;
isRing = false;
}
});
dialog = builder.setCancelable(false).create();
dialog.show();
}
mAlarmManager.cancel(sendIntent);
startTime = 0;
leftTimeToRun = 0;
ringTime = 0;
adjustTime = 0;
setNumberPicker(0);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View v = inflater.inflate(R.layout.keyer, container, false);
keyerHour = (NumberPicker) v.findViewById(R.id.keyer_hour);
keyerHour.setMinValue(0);
keyerHour.setMaxValue(23);
keyerHour.setFormatter(this);
keyerHour.setOnValueChangedListener(this);
keyerHour.getChildAt(0).setFocusable(false);
keyerMinute = (NumberPicker) v.findViewById(R.id.keyer_minute);
keyerMinute.setMinValue(0);
keyerMinute.setMaxValue(59);
keyerMinute.setFormatter(this);
keyerMinute.setOnValueChangedListener(this);
keyerMinute.getChildAt(0).setFocusable(false);
keyerSecond = (NumberPicker) v.findViewById(R.id.keyer_second);
keyerSecond.setMinValue(0);
keyerSecond.setMaxValue(59);
keyerSecond.setFormatter(this);
keyerSecond.setOnValueChangedListener(this);
keyerSecond.getChildAt(0).setFocusable(false);
kbtnStart = (Button) v.findViewById(R.id.kbtnStart);
kbtnStart.setOnClickListener(this);
kbtnPause = (Button) v.findViewById(R.id.kbtnPause);
kbtnPause.setOnClickListener(this);
kbtnReset = (Button) v.findViewById(R.id.kbtnReset);
kbtnReset.setOnClickListener(this);
time = (LinearLayout) v.findViewById(R.id.time);
hour = (TextView) v.findViewById(R.id.hour);
minute = (TextView) v.findViewById(R.id.minute);
second = (TextView) v.findViewById(R.id.second);
TimeAllZero();
return v;
}
@Override
public void onResume() {
if (getActivity() instanceof FxDeskClock) {
((FxDeskClock) getActivity()).registerPageChangedListener(this);
}
long now = SystemClock.elapsedRealtime();
if(now <= ringTime && stateNow == timerState){
long leftTime = ringTime - now;
setNumberPicker(leftTime);
}
if(stateNow == overState) {
setNumberPicker(0);
}
super.onResume();
}
private void setNumberPicker(long leftTime){
long secs = leftTime/1000;
int hours = (int) (secs/3600);
int minutes = (int) ((secs%3600)/60);
int seconds = (int) ((secs%3600)%60);
secondtime = seconds;
minutetime = minutes;
hourtime = hours;
second.setText(format(seconds));
minute.setText(format(minutes));
hour.setText(format(hours));
}
@Override
public void onPause() {
if (getActivity() instanceof FxDeskClock) {
((FxDeskClock) getActivity()).unregisterPageChangedListener(this);
}
super.onPause();
}
public String format(int value) {
String tmpStr = String.valueOf(value);
if (value < 10) {
tmpStr = "0" + tmpStr;
}
return tmpStr;
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.kbtnStart:
TimeAllZero();
Start();
break;
case R.id.kbtnPause:
Pause();
break;
case R.id.kbtnReset:
Reset();
kbtnStart.setTextColor(getResources().getColor(R.color.text_summery));
break;
}
}
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
if (picker == keyerHour) {
hourtime = newVal;
}
if (picker == keyerMinute) {
minutetime = newVal;
}
if (picker == keyerSecond) {
secondtime = newVal;
}
TimeAllZero();
}
private long startTime = 0;
private long leftTimeToRun = 0;
private long ringTime = 0;
private long adjustTime = 0;
public void Start() {
if (kbtnStart.isEnabled()) {
kbtnStart.setVisibility(View.GONE);
time.setVisibility(View.VISIBLE);
hour.setText(format(hourtime));
minute.setText(format(minutetime));
second.setText(format(secondtime));
kbtnPause.setVisibility(View.VISIBLE);
kbtnPause.setText(R.string.kpause);
kbtnPause.setEnabled(true);
kbtnPause.setTextColor(getResources().getColor(R.color.text_gray));
kbtnReset.setText(R.string.kreset);
kbtnReset.setVisibility(View.VISIBLE);
keyerHour.setVisibility(View.GONE);
keyerMinute.setVisibility(View.GONE);
keyerSecond.setVisibility(View.GONE);
timer = null;
task = null;
timer = new Timer();
task = new TimerTask() {
@Override
public void run() {
// TODO Auto-generated method stub
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}
};
timer.schedule(task, 1000, 1000);
startTime = SystemClock.elapsedRealtime();
leftTimeToRun = (60 * 60 * hourtime + 60 * minutetime + secondtime) * 1000;
ringTime = startTime + leftTimeToRun;
mAlarmManager.cancel(sendIntent);
if (isKitKatOrLater()) {
mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, ringTime, sendIntent);
} else {
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, ringTime, sendIntent);
}
stateNow = timerState;
}
}
public static boolean isKitKatOrLater() {
return Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2;
}
public void Pause() {
if(kbtnPause.getText().toString().equals(getResources().getString(R.string.kpause))) {
kbtnPause.setText(R.string.kcontinue);
timer.cancel();
adjustTime = (leftTimeToRun - (SystemClock.elapsedRealtime() - startTime)) % 1000;
mAlarmManager.cancel(sendIntent);
stateNow = pauseState;
} else {
stateNow = timerState;
kbtnPause.setText(R.string.kpause);
secondtime = Integer.parseInt(second.getText().toString());
minutetime = Integer.parseInt(minute.getText().toString());
hourtime = Integer.parseInt(hour.getText().toString());
timer = new Timer();
task = new TimerTask() {
@Override
public void run() {
// TODO Auto-generated method stub
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}
};
timer.schedule(task, adjustTime, 1000);
startTime = SystemClock.elapsedRealtime();
leftTimeToRun = (60 * 60 * hourtime + 60 * minutetime + secondtime) * 1000 + adjustTime;
ringTime = startTime + leftTimeToRun;
mAlarmManager.cancel(sendIntent);
if (isKitKatOrLater()) {
mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, ringTime, sendIntent);
} else {
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, ringTime, sendIntent);
}
}
}
public void Reset() {
stateNow = resetState;
kbtnStart.setVisibility(View.VISIBLE);
kbtnPause.setVisibility(View.GONE);
kbtnReset.setVisibility(View.GONE);
keyerHour.setVisibility(View.VISIBLE);
keyerHour.setValue(0);
hourtime = 0;
keyerMinute.setVisibility(View.VISIBLE);
keyerMinute.setValue(0);
minutetime = 0;
keyerSecond.setVisibility(View.VISIBLE);
keyerSecond.setValue(0);
secondtime = 0;
time.setVisibility(View.GONE);
timer.cancel();
startTime = 0;
leftTimeToRun = 0;
ringTime = 0;
adjustTime = 0;
mAlarmManager.cancel(sendIntent);
}
public void TimeAllZero() {
if (keyerHour.getValue() == 0 && keyerMinute.getValue() == 0 && keyerSecond.getValue() == 0) {
kbtnStart.setEnabled(false);
kbtnStart.setTextColor(getResources().getColor(R.color.text_summery));
} else {
kbtnStart.setEnabled(true);
kbtnStart.setTextColor(getResources().getColor(R.color.text_gray));
}
}
@Override
public void onDestroyView() {
// TODO Auto-generated method stub
super.onDestroyView();
if (timer != null) {
timer.cancel();
timer = null;
}
if (task != null) {
task = null;
}
if (alarmMusic != null) {
alarmMusic.stop();
alarmMusic.release();
alarmMusic = null;
if(mBroadcastReceiver!= null){
getActivity().unregisterReceiver(mBroadcastReceiver);
}
}
}