Android 定时任务的多种实现方式

2016年05月22日 20:51:41 痕迹天涯119 阅读数:63920

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014492609/article/details/51475254

定时任务实现总结

在Android中这算是一个常用的功能了,,有兴趣一起来探讨下可以

Android消息机制

首先来了解一下Android的消息处理机制

即Handlerd的运行机制,handler的运行需要底层的MessageQueue和Looper的支撑。MessageQueue(消息队列),它的内部存储了一些消息,以队列的形式对外提供插入和删除的操作(实际为单链表存储)。Looper(消息循环),配合MessageQueue实现实现消息的不断入队和出队工作。

一个关系图:

Android定时任务实现方式归纳总结_第1张图片

通过Handler可以很容易将任务切换到其他线程中执行,以减少主线程的负担,因此Handler常用来进行UI更新。这里只是简单的进行一些概述。对应handler还不清楚的强烈建议参考以下博客: 
Android 异步消息处理机制 
Android AsyncTask完全解析

当然,现在已经有更好的消息处理办法了,了解handler和Asynctask可以更好的理解Android内部消息的处理机制。 
推荐:EventBus,高度解耦,代码简洁明了,有兴趣的可以自行参考使用。


1.采用Handle与线程的sleep(long)方法

1) 定义一个Handler类,用于处理接受到的Message。

Handler handler = new Handler() {      public void handleMessage(Message msg) {          // 要做的事情          super.handleMessage(msg);      }  };  

2) 新建一个实现Runnable接口的线程类,如下:

public class MyThread implements Runnable {      @Override      public void run() {          // TODO Auto-generated method stub          while (true) {              try {                  Thread.sleep(10000);// 线程暂停10秒,单位毫秒                  Message message = new Message();                  message.what = 1;                  handler.sendMessage(message);// 发送消息              } catch (InterruptedException e) {                  // TODO Auto-generated catch block                  e.printStackTrace();              }          }      }  }  

3) 在需要启动线程的地方加入下面语句:

new Thread(new MyThread()).start();  

 

分析:纯正的java原生实现,在sleep结束后,并不能保证竞争到cpu资源,这也就导致了时间上必定>=10000的精度问题。

2.采用Handler的postDelayed(Runnable, long)方法

1)定义一个Handler类

Handler handler=new Handler();  Runnable runnable=new Runnable() {      @Override      public void run() {          // TODO Auto-generated method stub          //要做的事情          handler.postDelayed(this, 2000);      }  };  

2) 启动与关闭计时器

handler.postDelayed(runnable, 2000);//每两秒执行一次runnable.  
handler.removeCallbacks(runnable);

分析:嗯,看起蛮不错,实现上也简单了,和sleep想必还不会产生阻塞,注意等待和间隔的区别。

3.采用Handler与timer及TimerTask结合的方法

1) 定义定时器、定时器任务及Handler句柄

private final Timer timer = new Timer();  private TimerTask task;  Handler handler = new Handler() {      @Override      public void handleMessage(Message msg) {          // TODO Auto-generated method stub          // 要做的事情          super.handleMessage(msg);      }  };  

2) 初始化计时器任务

task = new TimerTask() {      @Override      public void run() {          // TODO Auto-generated method stub          Message message = new Message();          message.what = 1;          handler.sendMessage(message);      }  };   

3) 启动和关闭定时器

timer.schedule(task, 2000, 3000);   
timer.cancel();  

此外,Timer也可以配合runOnUiThread实现,如下

    private TimerTask mTimerTask = new TimerTask() {        @Override        public void run() {            runOnUiThread(new Runnable() {                @Override                public void run() {                    //处理延时任务                }            });        }    };

分析:timer.schedule(task, 2000, 3000);意思是在2秒后执行第一次,之后每3000秒在执行一次。timer不保证精确度且在无法唤醒cpu,不适合后台任务的定时。

采用AlarmManger实现长期精确的定时任务

AlarmManager的常用方法有三个:

  • set(int type,long startTime,PendingIntent pi);//一次性
  • setExact(int type, long triggerAtMillis, PendingIntent operation)//一次性的精确版
  • setRepeating(int type,long startTime,long intervalTime,PendingIntent 
    pi);//精确重复
  • setInexactRepeating(int type,long startTime,long 
    intervalTime,PendingIntent pi);//非精确,降低功耗

type表示闹钟类型,startTime表示闹钟第一次执行时间,long intervalTime表示间隔时间,PendingIntent表示闹钟响应动作


对以上各个参数的详细解释 
闹钟的类型:

  • AlarmManager.ELAPSED_REALTIME:休眠后停止,相对开机时间
  • AlarmManager.ELAPSED_REALTIME_WAKEUP:休眠状态仍可唤醒cpu继续工作,相对开机时间
  • AlarmManager.RTC:同1,但时间相对于绝对时间
  • AlarmManager.RTC_WAKEUP:同2,但时间相对于绝对时间
  • AlarmManager.POWER_OFF_WAKEUP:关机后依旧可用,相对于绝对时间

绝对时间:1970 年 1月 1 日 0 点

startTime: 
闹钟的第一次执行时间,以毫秒为单位,一般使用当前时间。

  • SystemClock.elapsedRealtime():系统开机至今所经历时间的毫秒数
  • System.currentTimeMillis():1970 年 1 月 1 日 0 点至今所经历时间的毫秒数

intervalTime:执行时间间隔。

PendingIntent : 
PendingIntent用于描述Intent及其最终的行为.,这里用于获取定时任务的执行动作。 
详细参考译文:PendingIntent

利用AlarmManger+Service+BarocastReceiver实现5s一次打印操作

服务类:

public class HorizonService extends Service {    @Override    public IBinder onBind(Intent intent) {        return null;    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        new Thread(new Runnable() {            @Override            public void run() {                Log.d("TAG", "打印时间: " + new Date().                        toString());            }        }).start();        AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);        int five = 5000; // 这是5s        long triggerAtTime = SystemClock.elapsedRealtime() + five;        Intent i = new Intent(this, AlarmReceiver.class);        PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);        manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);        return super.onStartCommand(intent, flags, startId);    }}

广播接受器

public class AlarmReceiver extends BroadcastReceiver {    @Override    public void onReceive(Context context, Intent intent) {        Intent i = new Intent(context, HorizonService.class);        context.startService(i);    }}

启动定时任务:

Intent intent = new Intent(this,HorizonService.class);startService(intent);

效果Demo: 
Android定时任务实现方式归纳总结_第2张图片

本例通过广播接收器和服务的循环调用实现了无限循环的效果,当然,你也可以直接利用setRepeating实现同样的效果。

注意:不要忘了在manifest文件中注册服务和广播接收器。

AlarmManager的取消方法:AlarmManger.cancel();

分析:该方式可唤醒cpu甚至实现精确定时,适用于配合service在后台执行一些长期的定时行为。


本文总结:不建议使用第一种方式,短期的定时任务推荐第二、三种方式实现,长期或者有精确要求的定时任务则可以配合Service在后台执行。

有其他疑问可下方提出或者直接链接官方文档AlarmService

--------------------- 

实现倒计时的方法

倒计时几种方法呢,这个不像正计时那样简单实现容易想到的那5种,甚至正计时通过总量减的方式全可以转化成倒计时。所以在此我想介绍另一个专门倒计时的类CountDownTimer来讲解。

构造方法:

         CountDownTimer (long millisInFuture, long countDownInterval)         //millisInFuture:设置倒计时的总时间(毫秒)         //countDownInterval:设置每次减去多少毫秒

验证码获取的示例代码如下:
MainActivity.java

public class MainActivity extends AppCompatActivity {    private static final int LEFT_TIME = 999;    private static final int TIME_OVER = 998;    private Button bt_verify;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        bt_verify = (Button) findViewById(R.id.bt_verify);        bt_verify.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                setTimer();            }        });    }    private void setTimer(){       timer.start();    }    private Handler mHandler = new Handler(){        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            switch (msg.what){                case LEFT_TIME:                    long left_time = (long) msg.obj;                    bt_verify.setEnabled(false);//禁止button点击                    bt_verify.setText((left_time / 1000) + " S");                    break;                case TIME_OVER:                    bt_verify.setEnabled(true);                    bt_verify.setText("点击重发");                    break;                default:                    break;            }        }    };    private void stopTimer(){        timer.cancel();    }    @Override    protected void onDestroy() {        super.onDestroy();        stopTimer();    }    long millisInFuture = 60*1000;    long countDownInterval =1000;    CountDownTimer timer = new CountDownTimer(millisInFuture,countDownInterval) {        @Override        public void onTick(long millisUntilFinished) {            //millisUntilFinished  剩余时间回调,这个是实时的(以countDownInterval为单位)            Message msg = Message.obtain();            msg.what = LEFT_TIME;            msg.obj = millisUntilFinished;            mHandler.sendMessage(msg);        }        @Override        public void onFinish() {            //结束时的回调            mHandler.sendEmptyMessage(TIME_OVER);        }    };}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>                    

需要注意的是:CountDownTimer如果使用不当,常常会报空指针异常,甚至造成严重的内存泄漏,不过解决办法也是有的:

一是在CountDownTimer的onTick方法中记得判空:
activity中:

    if(!activity.isFinishing()){        //doing something...    }

fragment中:

    if(getActivity()!=null){       //doing something...    }

二还需要在宿主Activity或fragment生命周期结束的时候,记得调用timer.cancle()方法:

    @Override    public void onDestroy() {        if(timer!=null){            timer.cancel();            timer = null;        }        super.onDestroy();    }

总结:

短期的定时任务推荐用最后两种方式方式实现,长期或者有精确要求的定时任务则选择AlarmManger + Service在后台执行。最后把开始的展示图去壳之后内部实现了一遍,内容比较简单,整篇文中最值得注意的OOM问题是开发中常常忽视的,减少OOM和ANR的情况,才可以将代码的细节把控好!


如果本文对你有所帮助,请点赞!你的鼓励是我写作的最大动力!

 

更多相关文章

  1. Android VideoView设置静音,Android 设置VideoView静音,Android
  2. ListView去掉分割线的几种方法
  3. SDK Platform Tools component is missing! Please use the SDK
  4. android handler消息机制
  5. Android Market google play store帐号注册方法流程 及发布应用
  6. Android 实现全屏显示的几种方法整理
  7. Android Handler 消息机制原理解析
  8. Android 选择器 PickerView实例,时间选择器、地址选择器、单项选
  9. Android 带有角标的imageview,类似于qq、微信未读消息提示效果

随机推荐

  1. PHP获取真实IP及IP模拟方法解析
  2. 使用python 中的socket包实现本地电脑与
  3. Centos系统部署nginx1.18.0
  4. 访问器属性与获取DOM元素的两种方法
  5. 如何开始学基础画画?0基础学画画教程!
  6. 深入聊一聊JS中new的原理与实现
  7. 在Ubuntu20.04上安装Kubernetes-Kubeadm
  8. 如何画好人物的眼睛?画二次元人物眼睛步骤
  9. 实战淘宝首页的基本整体架构:页眉,页脚,
  10. 如何利用JavaScript实现排序算法浅析