android計時與系統休眠
來源:程序員人生 發布時間:2015-03-17 08:41:26 閱讀次數:3093次
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);
}
}
}
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈