做应用层开发,业务逻辑比较多,经常会碰到一些需要定时轮询操作,android api提供了一个 Timer ,可也满足条件,配合着 TimerTask 使用,比如答应log日志,延迟1秒执行,每隔5秒打印一次

    Timer mTimer = new Timer();
    TimerTask mTask = new TimerTask() {
        @Override
        public void run() {
            Log.e("TAG", "timerTest:   a "  );
        }
    };
    private void timerTest() {
        mTimer.schedule(mTask, 1000, 5000);
    }

如果想取消该轮询,可以调用 mTimer.cancel(); 方法, mTimer 中所有的task任务,都会被取消。 mTask.cancel() 是取消单独的task。我们看看这两个类的源码

我们先看一下 TimerTask ,发现它实现了 Runnable 接口,这里它仅仅是一个对象,不是线程,这点要谨记。它里面有几个常量,通过给 int state = VIRGIN; 赋值来标记状态, public abstract void run(); 这个是个抽象的方法,具体操作在它里面。
    public boolean cancel() {
        synchronized(lock) {
            boolean result = (state == SCHEDULED);
            state = CANCELLED;
            return result;
        }
    }
这个方法是取消task的方法,这里仅仅是针对task本身的方法,通过控制state的状态来改变。

看看 Timer 类源码,它里面最显眼是两个成员变量, TaskQueue queue = new TaskQueue(); TimerThread thread = new TimerThread(queue); 这两个类是干嘛的呢?点击 TaskQueue ,发现它其实就是一个即可容器, 默认 private TimerTask[] queue = new TimerTask[128]; 容量为128,如果元素个数达到128-1时,数组会扩容到原先的2倍;如果元素到达128*2-1时,会再次扩容,也是2倍增加,依次类推。

    private int size = 0;

    void add(TimerTask task) {
        // Grow backing store if necessary
        if (size + 1 == queue.length)
            queue = Arrays.copyOf(queue, 2*queue.length);

        queue[++size] = task;
        fixUp(size);
    }

    private void fixUp(int k) {
        while (k > 1) {
            int j = k >> 1;
            if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }

每次往里面添加 TimerTask 时,会检查是否要扩容,然后是从数组索引为1的位置开始添加元素,紧接着就是上滤的操作,这个明显是堆,用的是小堆,fixUp(int k) 操作,把 TimerTask 的 nextExecutionTime 值,排成小堆。fixUp 会对进行简单的排序,父元素的 nextExecutionTime 值一定比child的 nextExecutionTime 值小;

    void heapify() {
        for (int i = size/2; i >= 1; i--)
            fixDown(i);
    }

    private void fixDown(int k) {
        int j;
        while ((j = k << 1) <= size && j > 0) {
            if (j < size &&
                queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
                j++; // j indexes smallest kid
            if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }

这个是下沉,意思是通过比较,先判断元素是否有child,如果有两个child,先比较出小的,然后与该元素比较,如果该元素比child的值大,则交换位置,这是堆排序的原理之一。没有接触过 堆 数据结构的同学请自行百度一下相关资料。  

我们再看看 TimerThread 这个类,它继承了Thread ,是个线程。它的构造方法中有一个 TaskQueue ,我们看看

    boolean newTasksMayBeScheduled = true;
    TimerThread(TaskQueue queue) {
        this.queue = queue;
    }

    public void run() {
        try {
            mainLoop();
        } finally {
            synchronized(queue) {
                newTasksMayBeScheduled = false;
                queue.clear();  // Eliminate obsolete references
            }
        }
    }

我们知道,线程被调用时,是调用 start() 方法,最终会执行 run() 方法。 TimerThread 的 run() 方法中包裹了mainLoop()方法,直接分析 mainLoop() 可能有点懵,先看一下 timer添加task的方法, mTimer.schedule(mTask, 1000, 5000); 这行代码中,1000 是延迟执行的时间,5000是间隔时间。

    public void schedule(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, System.currentTimeMillis()+delay, -period);
    }
我们发现,延迟delay不能是负数,必须是大于或等于0;间隔period 必须大于0;然后获取当前本地时间System.currentTimeMillis(),与delay相加,计算出要执行该task的时间,同时对period取其负值,传入sched()方法中

    private void sched(TimerTask task, long time, long period) {
        if (time < 0)
            throw new IllegalArgumentException("Illegal execution time.");
        if (Math.abs(period) > (Long.MAX_VALUE >> 1))
            period >>= 1;
        synchronized(queue) {
            if (!thread.newTasksMayBeScheduled)
                throw new IllegalStateException("Timer already cancelled.");
            synchronized(task.lock) {
                if (task.state != TimerTask.VIRGIN)
                    throw new IllegalStateException(
                        "Task already scheduled or cancelled");
                task.nextExecutionTime = time;
                task.period = period;
                task.state = TimerTask.SCHEDULED;
            }
            queue.add(task);
            if (queue.getMin() == task)
                queue.notify();
        }
    }

这个方法,一开始是一些校验,可以忽略,从同步代码块开始看。对同一个task加锁,先判断task的state状态,它的默认值是 VIRGIN ,通过 sched() 方法添加task只能对同一个task添加一次,这里是做校验;然后把执行的时间点和间隔时间赋值给task,同时改变task的state状态值为 SCHEDULED ,意思是准备好了,但还没执行。task赋值完毕后,就把它添加到 queue中,if (queue.getMin() == task) 意思是queue中堆顶task就是添加的改task,马上唤醒queue所在的线程,执行相关操作。 schedule() 有重载几个方法,最终都会调用到 sched() 这个方法里,原理一样。

这时候再看 mainLoop() 方法,看看代码

    private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    if (queue.isEmpty())
                        break; // Queue is empty and will forever remain; die
                    long currentTime, executionTime;
                    task = queue.getMin();
                    synchronized(task.lock) {
                        if (task.state == TimerTask.CANCELLED) {
                            queue.removeMin();
                            continue;  // No action required, poll queue again
                        }
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;
                        if (taskFired = (executionTime<=currentTime)) {
                            if (task.period == 0) { // Non-repeating, remove
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else { // Repeating task, reschedule
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    if (!taskFired) // Task hasn't yet fired; wait
                        queue.wait(executionTime - currentTime);
                }
                if (taskFired)  // Task fired; run it, holding no locks
                    task.run();
            } catch(InterruptedException e) {
            }
        }
    }

mainLoop() 方法中,是一个死循环,不停的重复着同样的操作。对 queue 做了个同步,首先检查 queue 是否有 TimerTask 对象及 newTasksMayBeScheduled 属性是否为true,如果Timer 调用了 cancel() 方法, queue会被清空,newTasksMayBeScheduled 会赋值为 false;正常情况下,newTasksMayBeScheduled 为 true,即使 queue 中没有元素,该线程处于wait() 等待的状态,如果为false了,且queue中没有元素,则跳出死循环,该线程声明周期结束。继续往下看, task = queue.getMin(); 获取 queue 堆中顶部对象,判断task的state状态,如果 task.cancel(), 该task被取消了, TimerTask 中源码

    public boolean cancel() {
        synchronized(lock) {
            boolean result = (state == SCHEDULED);
            state = CANCELLED;
            return result;
        }
    }

state值变为 CANCELLED ,测试会把它从queue中删除掉。继续往下看,获取当前的时间及该task要执行的时间点,然后做比较, executionTime<=currentTime 比较的是当前时间是否比task要执行的时间点大或者相等,如果为true,则 taskFired 赋值为 true,这是后意思就是可以执行该task了。继续往下看,这时候会判断 period 值,如果 period 为0,说明执行一次,如果不为0,是多次执行,咱们看看代码是怎么实现的:period == 0 时,该task就是在堆顶,queue把它删除掉,然后state状态修改为 EXECUTED ,标记为执行;period != 0 时,我们计算出下一次要执行的时间点, long newTime = task.period<0 ? currentTime - task.period : executionTime + task.period;然后在queue中,重新进行下滤操作,重新形成堆;然后就是执行 task.run(); 这时候 task 是在子线程中操作的,非UI线程。 如果 taskFired 值为false,则queue.wait(),即线程进入等待状态,等待时间为 long millis =executionTime - currentTime; 如果没有时间, queue.wait(); 这种等待是被动的,无法自己唤醒自己,只能被动的 queue.notify(); 才能唤醒。

所以,一旦某个task被取消了,过了一会,又想使用它,则需要new新的task才能使用,比如上面的例子 mTask, 如果我们要取消,重新使用,那么需要

    private void cancelResetTest() {
        mTask.cancel();
        amTask= new TimerTask() {
            @Override
            public void run() {
                Log.e("TAG", "timerTest:   amTask1 "  );
            }
        };
        mTimer.schedule(mTask, 1000, 5000);
    }

更多相关文章

  1. android listview setselection 失效解决办法
  2. Android(安卓)View添加Listener小技巧
  3. android经典面试题集锦
  4. android启动之SystemServer启动
  5. android 睡眠和唤醒过程
  6. 从setContentView说起
  7. Android(安卓)scrollview嵌套listview出现listview无法滑动冲突
  8. InputManagerService
  9. IdleHandler类在android中的使用

随机推荐

  1. android 7.0及以上版本安装apk
  2. android FrameLayout响应了下层view的点
  3. android中listView单选
  4. android 文本走马灯代码
  5. android:inputType参数
  6. Android常见主题
  7. android 文本走马灯代码
  8. android:theme大全
  9. Android——四种基本布局
  10. Android下实现一个Activity的全屏显示