android计时与系统休眠

android计时与系统休眠


TIP:可能写的有点仓促,具体的可以联系我(*^__^*)

摘要:之前做项目的时候,修改一个倒计时秒表,本来以为比较简单,但是发现很多有趣的东西。我们项目里面用的是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说明。因为我之前使用的方法大致是下面的原理:

  1. AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);  
  2. Intent intent = new Intent("com.liu.alarm.ACTION_SEND");  
  3. PendingIntent sendIntent = PendingIntent.getBroadcast(this0, intent,PendingIntent.FLAG_UPDATE_CURRENT);  
  4. am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime() + 1000,1000, sendIntent);
然后注册一个广播接收器接收
  1. public void onReceive(final Context context, Intent intent) {  

  2. 需要的操作;}
      我们看看官方给出的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保留了两个接口,这两个接口是准确的。本人于是使用了这其中一个。

方法如下    TIP:前提是确保你的API是19的或更高,否则做下判断

public static boolean isKitKatOrLater() {

 return Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2;

}


  1. AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);  
  2. Intent intent = new Intent("com.liu.alarm.ACTION_SEND");  
  3. PendingIntent sendIntent = PendingIntent.getBroadcast(this0, intent,PendingIntent.FLAG_UPDATE_CURRENT);</span>  
  1. if(isKitKatOrLater(){
  2. am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime() + 1000, sendIntent);
  3. }else{
  4. am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime() + 1000, sendIntent);

然后注册一个广播接收器接收

  1. public void onReceive(final Context context, Intent intent) {
  2. if(isKitKatOrLater(){
  3. am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime() + 1000, sendIntent); 
  4. }else{
  5. am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime() + 1000, sendIntent);
  6. }  

  1. if(isKitKatOrLater(){
  2. am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime() + 1000, sendIntent);   
  3. 需要的操作;}

       但是后来有发现,即使使用了精确的时间计时,但是在系统刚开机的一分钟内计时,仍然会有不及时响应的情况,所以,后来自己想了一下,毕竟是两块芯片,多进程多线程还要考虑好多同步的问题,两块芯片也不可能配合的那么天衣无缝,假如一块特别繁忙的时候。所以,后来我参考源码的方法:正常情况下使用线程或者Timer计时,并且使用AlarmManager设定一个计时结束的时间,当执行Activity的Onpause的时候使用AlarmManager的计时响应。当Onresume的时候,根据走过的时间刷新界面。下面是我全部的代码
/**
 * 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);
        }
    }

    
}



郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。