android计时与系统休眠
android计时与系统休眠
摘要:之前做项目的时候,修改一个倒计时秒表,本来以为比较简单,但是发现很多有趣的东西。我们项目里面用的是Timer计时的方法,但是,当系统休眠的时候,Timer也是处于休眠状态的。后来,我改进了几个方法,一个是handle+message的方法,还有一个是handle+runnable的方法,还有handle+Thread的方法。但是同样发现系统休眠的时候,这些同样是处于休眠状态的。后来上网查找了一下,这和android的架构有关:
原理
Android手机有两个处理器,一个叫Application Processor(AP),一个叫Baseband Processor(BP)。AP是ARM架构的处理器,用于运行Linux+Android系统;BP用于运行实时操作系统(RTOS),通讯协议栈运行于BP的RTOS之上。非通话时间,BP的能耗基本上在5mA左右,而AP只要处于非休眠状态,能耗至少在50mA以上,执行图形运算时会更高。另外LCD工作时功耗在100mA左右,WIFI也在100mA左右。一般手机待机时,AP、LCD、WIFI均进入休眠状态,这时Android中应用程序的代码也会停止执行。Android为了确保应用程序中关键代码的正确执行,提供了Wake Lock的API,AlarmManager这个类使用的是BP的芯片,使得应用程序有权限通过代码阻止AP进入休眠状态。但如果不领会Android设计者的意图而滥用Wake Lock API,为了自身程序在后台的正常工作而长时间阻止AP进入休眠状态,就会成为待机电池杀手。
首先,完全没必要担心AP休眠会导致收不到消息推送。通讯协议栈运行于BP,一旦收到数据包,BP会将AP唤醒,唤醒的时间足够AP执行代码完成对收到的数据包的处理过程。其它的如Connectivity事件触发时AP同样会被唤醒。那么唯一的问题就是程序如何执行向服务器发送心跳包的逻辑。你显然不能靠AP来做心跳计时。Android提供的Alarm Manager就是来解决这个问题的。Alarm应该是BP计时(或其它某个带石英钟的芯片,不太确定,但绝对不是AP),触发时唤醒AP执行程序代码。那么Wake Lock API有啥用呢?比如心跳包从请求到应答,比如断线重连重新登陆这些关键逻辑的执行过程,就需要Wake Lock来保护。而一旦一个关键逻辑执行成功,就应该立即释放掉Wake Lock了。两次心跳请求间隔5到10分钟,基本不会怎么耗电。除非网络不稳定,频繁断线重连,那种情况办法不多。
网上有说使用AlarmManager,因为AlarmManager 是Android 系统封装的用于管理 RTC 的模块,RTC (Real Time Clock) 是一个独立的硬件时钟,可以在 CPU 休眠时正常运行,在预设的时间到达时,通过中断唤醒 CPU。
实验
后来,本人使用AlarmManager,但是又碰到不准的问题,而且当系统繁忙的时候(比如刚开机前一分钟),更为明显,后来查看官方的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);
- public void onReceive(final Context context, Intent intent) {
-
- 需要的操作;}
targetSdkVersion
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保留了两个接口,这两个接口是准确的。本人于是使用了这其中一个。
方法如下 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);
- }
然后注册一个广播接收器接收
- 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);
-
需要的操作;}
/** * 2014-12-05 BenMin FEIXUN_DESKCLOCK_BENMIN_001 * modify PWEUN-4141 to remind the user when the timing is over. * 2014-12-17 BenMin FEIXUN_DESKCLOCK_BENMIN_002 * modify to ensure the fragment attached to Activity when using getResources() function. * 2014-12-26 BenMin FEIXUN_DESKCLOCK_BENMIN_003 * modify PLGN-489 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); } } }
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。