Android定时任务实现方式归纳总结

Android定时任务的应用及实现

https://www.jianshu.com/p/9304c8faf79f

 冯裴添 关注

 0.2 2018.03.19 22:48 字数 1131 阅读 7201评论 1喜欢 8

定时任务在Android中这算是一个常用的功能了。比如注册获取验证码时的倒计时,或者支付时候也会有倒计时。正计时大多也都用在定时唤醒或者延时做一些操作的情况。本文我会先整理一下定时任务的几种方法以及CountDownTimer这个专门用来倒计时的类,后面我们以最常用应用场景来演示一下验证码倒计时原理的实现,包装效果后如下图:

实现正定时的五种方法

方法1:通过Handler + Thread 的方式。代码如下。

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private static final int TIMER = 999;
    private static boolean flag = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        setTimer();
    }

    private void setTimer(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (flag){
                    try {
                        Thread.sleep(1000); //休眠一秒
                        mHanler.sendEmptyMessage(TIMER);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    private Handler mHanler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case TIMER:
                    //去执行定时操作逻辑
                    break;
                default:
                    break;
            }
        }
    };

     private void stopTimer(){
         flag = false;
     }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopTimer();
    } 
}

这是比较容易想到的方法,值得注意的是:如果在此Activity关闭之前,必须要终止线程内的循环,否则就会造成内存泄露

方法2:通过Handler + Message的方式。代码如下。

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private static final int TIMER = 999;
    private static boolean flag = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        setTimer();
    }

    private void setTimer(){
        Message message = mHandler.obtainMessage(TIMER);     // Message
        mHandler.sendMessageDelayed(message, 1000);
    }

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case TIMER:
                    //在这里去执行定时操作逻辑
                    
                    if (flag) {
                        Message message = mHandler.obtainMessage(TIMER);
                        mHandler.sendMessageDelayed(message, 1000);
                    }
                    break;
                default:
                    break;
            }
        }
    };

    private void stopTimer(){
         flag = false;
    }
}

这个方法来实现定时其实还是有些缺陷的,主要的问题不是看起来像是死循环,而是在执行定时操作之后才可以进行下一次定时启动,如果此操作是耗时操作了,必定会延后下一秒的启动。所以这个方法定时严格来说不精确,不推荐。

扫描二维码关注公众号,回复: 5365683 查看本文章

方法3:通过Handler + Runnable的方式。代码如下。

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private static boolean flag = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        setTimer();
    }

    private void setTimer(){
        mHandler.postDelayed(runnable, 1000);
    }

    private Handler mHandler = new Handler();

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            //在这里执行定时需要的操作
            
            if (flag) {
                mHandler.postDelayed(this, 1000);
            }
        }
    };

    private void stopTimer(){
        flag = false;
    }

}

此方法和方法2很相似,缺点也相似,优点嘛~~简单!

方法4:通过Handler + TimerTask的方式。代码如下。

MainActivity.java

private static final int TIMER = 999;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        setTimer();
    }

    private void setTimer(){
        timer.schedule(task, 1000, 1000);       // timeTask
    }

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case TIMER:
                //在此执行定时操作
                    
                    break;
                default:
                    break;
            }
        }
    };
    
    Timer timer = new Timer();

    TimerTask task = new TimerTask() {
        @Override
        public void run() {
            mHandler.sendEmptyMessage(TIMER);
        }
    };

    private void stopTimer(){
        timer.cancel();
        task.cancel();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopTimer();
    }
}

这里引用了timer和task两个新的类,配合handler使用是目前比较多的方式,but我也不推荐,因为下面这个是我推荐的封装方法。
MyTimeTask.java

public class MyTimeTask {
    private Timer timer;
    private TimerTask task;
    private long time;

    public MyTimeTask(long time, TimerTask task) {
        this.task = task;
        this.time = time;
        if (timer == null){
            timer=new Timer();
        }
    }
    
    public void start(){
        timer.schedule(task, 0, time);//每隔time时间段就执行一次
    }

    public void stop(){
        if (timer != null) {
            timer.cancel();
            if (task != null) {
                task.cancel();  //将原任务从队列中移除
            }
        }
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private static final int TIMER = 999;
    private MyTimeTask task;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        setTimer();
    }

    private void setTimer(){
        task =new MyTimeTask(1000, new TimerTask() {
            @Override
            public void run() {
                mHandler.sendEmptyMessage(TIMER);
                //或者发广播,启动服务都是可以的
            }
        });
        task.start();
    }

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case TIMER:
                //在此执行定时操作

                    break;
                default:
                    break;
            }
        }
    };

    private void stopTimer(){
        task.stop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopTimer();
    }
}

单独封装出来的定时任务工具,引入方式简单,方便取消停止定时任务。简单来说优点就是:把工具类直接复制粘贴,然后简单调用就能用!

方法5:采用AlarmManger实现长期精确的定时任务。

此方法设计需要细说的点有很多,不再列代码,最好是各位亲自去写一写,对此方法印象会深一些,可以给出一个非常常见的应用场景:每隔6个小时,检查一遍数据库内容,查看数据是否有变化,如果有则弹出显示框显示,若没有则无变化。

AlarmManger常用方法有三个:

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

各个参数含义:type表示闹钟类型,startTime表示闹钟第一次执行时间,long intervalTime表示间隔时间,PendingIntent表示闹钟响应动作。

type闹钟类型分为:

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

startTime:闹钟的第一次执行时间,以毫秒为单位,一般使用当前时间。
intervalTime:执行时间间隔。
PendingIntent :PendingIntent用于描述Intent及其最终的行为.,这里用于获取定时任务的执行动作。

常见使用方式:利用AlarmManger+Service+BarocastReceiver来实现可唤醒cpu,甚至实现精确定时,适用于配合service在后台执行一些长期的定时行为

实现倒计时的方法

倒计时几种方法呢,这个不像正计时那样简单实现容易想到的那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"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@+id/bt_verify"
            android:layout_marginRight="5dp"/>

        <Button
            android:id="@+id/bt_verify"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="获取验证码"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"/>
    </RelativeLayout>
</RelativeLayout>

需要注意的是: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的情况,才可以将代码的细节把控好!


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

猜你喜欢

转载自blog.csdn.net/paladinzh/article/details/88035408