android計(jì)時與系統(tǒng)休眠
來源:程序員人生 發(fā)布時間:2015-03-17 08:41:26 閱讀次數(shù):3087次
android計(jì)時與系統(tǒng)休眠
TIP:可能寫的有點(diǎn)倉促,具體的可以聯(lián)系我(*^__^*)
1.摘要:
之前做項(xiàng)目的時候,修改1個倒計(jì)時秒表,本來以為比較簡單,但是發(fā)現(xiàn)很多有趣的東西。我們項(xiàng)目里面用的是Timer計(jì)時的方法,但是,當(dāng)系統(tǒng)休眠的時候,Timer也是處于休眠狀態(tài)的。后來,我改進(jìn)了幾個方法,1個是handle+message的方法,還有1個是handle+runnable的方法,還有handle+Thread的方法。但是一樣發(fā)現(xiàn)系統(tǒng)休眠的時候,這些一樣是處于休眠狀態(tài)的。后來上網(wǎng)查找了1下,這和android的架構(gòu)有關(guān):
2.原理
Android手機(jī)有兩個處理器,1個叫Application Processor(AP),1個叫Baseband Processor(BP)。AP是ARM架構(gòu)的處理器,用于運(yùn)行Linux+Android系統(tǒng);BP用于運(yùn)行實(shí)時操作系統(tǒng)(RTOS),通訊協(xié)議棧運(yùn)行于BP的RTOS之上。非通話時間,BP的能耗基本上在5mA左右,而AP只要處于非休眠狀態(tài),能耗最少在50mA以上,履行圖形運(yùn)算時會更高。另外LCD工作時功耗在100mA左右,WIFI也在100mA左右。1般手機(jī)待機(jī)時,AP、LCD、WIFI均進(jìn)入休眠狀態(tài),這時候Android中利用程序的代碼也會停止履行。Android為了確保利用程序中關(guān)鍵代碼的正確履行,提供了Wake
Lock的API,AlarmManager這個類使用的是BP的芯片,使得利用程序有權(quán)限通過代碼禁止AP進(jìn)入休眠狀態(tài)。但如果不領(lǐng)會Android設(shè)計(jì)者的意圖而濫用Wake Lock API,為了本身程序在后臺的正常工作而長時間禁止AP進(jìn)入休眠狀態(tài),就會成為待電機(jī)池殺手。
首先,完全沒必要擔(dān)心AP休眠會致使收不到消息推送。通訊協(xié)議棧運(yùn)行于BP,1旦收到數(shù)據(jù)包,BP會將AP喚醒,喚醒的時間足夠AP履行代碼完成對收到的數(shù)據(jù)包的處理進(jìn)程。其它的如Connectivity事件觸發(fā)時AP一樣會被喚醒。那末唯1的問題就是程序如何履行向服務(wù)器發(fā)送心跳包的邏輯。你明顯不能靠AP來做心跳計(jì)時。Android提供的Alarm Manager就是來解決這個問題的。Alarm應(yīng)當(dāng)是BP計(jì)時(或其它某個帶石英鐘的芯片,不太肯定,但絕對不是AP),觸發(fā)時喚醒AP履行程序代碼。那末Wake
Lock API有啥用呢?比如心跳包從要求到應(yīng)對,比如斷線重連重新登陸這些關(guān)鍵邏輯的履行進(jìn)程,就需要Wake Lock來保護(hù)。而1旦1個關(guān)鍵邏輯履行成功,就應(yīng)當(dāng)立即釋放掉Wake Lock了。兩次心跳要求間隔5到10分鐘,基本不會怎樣耗電。除非網(wǎng)絡(luò)不穩(wěn)定,頻繁斷線重連,那種情況辦法不多。
網(wǎng)上有說使用AlarmManager,由于AlarmManager 是Android 系統(tǒng)封裝的用于管理 RTC 的模塊,RTC (Real Time Clock) 是1個獨(dú)立的硬件時鐘,可以在 CPU 休眠時正常運(yùn)行,在預(yù)設(shè)的時間到達(dá)時,通過中斷喚醒 CPU。
3.實(shí)驗(yàn)
后來,本人使用AlarmManager,但是又碰到不準(zhǔn)的問題,而且當(dāng)系統(tǒng)繁忙的時候(比如剛開機(jī)前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以后,為了優(yōu)化電池,該計(jì)時操作不準(zhǔn)確了(可能在定義時間以后響應(yīng)),但是19之前的任然準(zhǔn)確。
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(包括)以后可能不準(zhǔn)確,但是android保存了兩個接口,這兩個接口是準(zhǔn)確的。本人因而使用了這其中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);
-
需要的操作;}
但是后來有發(fā)現(xiàn),即便使用了精確的時間計(jì)時,但是在系統(tǒng)剛開機(jī)的1分鐘內(nèi)計(jì)時,依然會有不及時響應(yīng)的情況,所以,后來自己想了1下,畢竟是兩塊芯片,多進(jìn)程多線程還要斟酌好多同步的問題,兩塊芯片也不可能配合的那末完美無缺,假設(shè)1塊特別繁忙的時候。所以,后來我參考源碼的方法:正常情況下使用線程或Timer計(jì)時,并且使用
AlarmManager設(shè)定1個計(jì)時結(jié)束的時間,當(dāng)履行Activity的Onpause的時候使用AlarmManager的計(jì)時響應(yīng)。當(dāng)Onresume的時候,根據(jù)走過的時間刷新界面。下面是我全部的代碼
/**
* 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);
}
}
}
生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈