【Android 异步操作】Timer 定时器 ( Timer 与 TimerTask 基本使用 | Timer 定时器常用用法 | Timer 源码分析 )

简介: 【Android 异步操作】Timer 定时器 ( Timer 与 TimerTask 基本使用 | Timer 定时器常用用法 | Timer 源码分析 )

文章目录

一、Timer 定时器基本使用

二、Timer 定时器常用用法

三、Timer 源码分析

四、Timer 部分源码注释

五、源码及资源下载



参考文档 :


Timer 定时器 API 文档

TimerTask 定时器任务 API 文档





一、Timer 定时器基本使用


Timer 可用于执行延迟任务或循环任务 ; 下面是定时器最基本用法 ;



1 . Timer 定时器基本使用 :


创建 Timer 定时器 : 调用构造函数创建定时器 Timer timer = new Timer() ;

分配 TimerTask 定时器任务 : 调用定时器的 schedule 方法 , 为 Timer 定时器分配 TimerTask 定时器任务 ;

 

timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // ...
            }
        }, 1_000);



2 . 定时器任务执行规则 : Timer 执行任务是 串行执行 的 , 同一时间只能执行一个任务 ;


在下面的示例中


任务 1 在 1 秒之后执行 , 在第 6 秒执行完毕 ;

任务 2 在第 6 秒 , 任务 1 执行完毕后 , 才开始执行 , 在第 11 秒执行完毕 ;


3 . 代码示例 :


 

private void timer(){
        // Timer 可用于执行延迟任务或循环任务
        Timer timer = new Timer();
        /*
            如果提交多个 TimerTask 定时器任务
            需要等待之前的 定时器任务 执行完成 , 才能执行后面的任务
            TimerTask 实现了 Runnable 接口
            延迟 1 秒执行任务 1 ( 任务 1 时长 5 秒 )
            延迟 2 秒执行任务 2 ( 任务 2 时长 5 秒 )
            Timer 执行任务是串行执行的 , 同一时间只能执行一个任务
            任务 1 在 1 秒之后执行 , 在第 6 秒执行完毕
            任务 2 在第 6 秒 , 任务 1 执行完毕后 , 才开始执行 , 在第 11 秒执行完毕
         */
        // 延迟 1 秒执行任务 1
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                Log.i(TAG, "延迟 1 秒执行 5 秒的任务 1 开始执行");
                try {
                    Thread.sleep(5_000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.i(TAG, "延迟 1 秒执行 5 秒的任务 1 执行完毕");
            }
        }, 1_000);
        // 延迟 2 秒执行任务 2
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                Log.i(TAG, "延迟 2 秒执行 5 秒的任务 2 开始执行");
                try {
                    Thread.sleep(5_000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.i(TAG, "延迟 2 秒执行 5 秒的任务 2执行完毕");
            }
        }, 2_000);
    }







二、Timer 定时器常用用法


1 . Timer 定时器构造函数 :



① 创建默认定时器 : 默认以 “Timer-序列号” 作为定时器线程名称 ;


public Timer() { this("Timer-" + serialNumber()); }


② 创建守护线程定时器 : 指定定时器是否作为守护线程来执行 , 定时器线程名称是默认名称 ;


public Timer(boolean isDaemon) { this("Timer-" + serialNumber(), isDaemon); }


③ 创建定时器并指定定时器名称 : 创建定时器 , 并 设置定时器线程名称 ;


public Timer(String name) {
        thread.setName(name);
        thread.start();
    }


④ 指定名称并设置守护线程 : 同时 设置定时器名称 , 并 设置定时器是否是守护线程 ;


 

public Timer(String name, boolean isDaemon) {
        thread.setName(name);
        thread.setDaemon(isDaemon);
        thread.start();
    }



2 . 定时器调度方法 :



① 在指定一段时间后执行定时器任务 : 在 delay 毫秒后 , 执行 TimerTask 定时器任务 ;


public void schedule(TimerTask task, long delay) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        sched(task, System.currentTimeMillis()+delay, 0);
    }


② 在指定的时间执行定时器任务 : 在某个指定的时间执行 TimerTask 定时器任务 ;


public void schedule(TimerTask task, Date time) { sched(task, time.getTime(), 0); }


③ 在指定的时间执行循环任务 : 在 firstTime 时间执行第一次 TimerTask 定时器任务 , 之后每隔 period 毫秒的周期时间 , 循环执行定时器任务 ; 循环周期是 period 毫秒 ; 如果因为某种原因导致某些操作出现了延迟 , 那么后续操作也会跟着延迟 ;


 

public void schedule(TimerTask task, Date firstTime, long period) {
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, firstTime.getTime(), -period);
    }


④ 延迟指定时间循环执行任务 : 延迟 delay 毫秒后 , 执行第一次定时器任务 , 然后每隔 period 毫秒 , 循环执行定时器任务 ; 循环周期是 period 毫秒 ; 如果因为某种原因导致某些操作出现了延迟 , 那么后续操作也会跟着延迟 ;


 

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);
    }


⑤ 在指定的时间执行循环任务 : 在 firstTime 时间执行第一次 TimerTask 定时器任务 , 之后每隔 period 毫秒的周期时间 , 循环执行定时器任务 ; 循环周期是 period 毫秒 ; 如果因为某种原因导致某些操作出现了延迟 , 那么后续操作需要补偿上述出现的延迟 ;


 

public void scheduleAtFixedRate(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 毫秒后 , 执行第一次定时器任务 , 然后每隔 period 毫秒 , 循环执行定时器任务 ; 循环周期是 period 毫秒 ; 如果因为某种原因导致某些操作出现了延迟 , 那么后续操作需要补偿上述出现的延迟 ;


public void scheduleAtFixedRate(TimerTask task, Date firstTime,
                                    long period) {
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, firstTime.getTime(), period);
    }




schedule 方法适用场景 : 适合用于平稳执行某种任务 ; 稳定性 > 准确率 ;


scheduleAtFixedRate 方法适用场景 : 适合用于对绝对时间敏感的任务 ; 准确率 > 稳定性 ;






三、Timer 源码分析


在 Timer 中定义了 TimerThread thread 成员变量 , 该成员对象在创建对象时会自动创建 ;


TimerThread 是定义在 Timer.java 文件中的类 , 是一个自定义线程类 ; 该帮助类实现了定时器任务的执行线程 , 该线程中的定时器队列等待任务到来 , 在合适的时间执行定时器任务 ; 调度需要重复执行的任务 ; 从任务队列中 , 移出被取消的任务 , 移出不需要循环执行的任务 ;


class TimerThread extends Thread{}


在构造函数 public Timer(String name) 中 , 调用了该线程的 start() 方法 , 启动了该线程 ;


// 省略了无关代码 
public class Timer {
    private final TimerThread thread = new TimerThread(queue);
    public Timer(String name) {
        // 启动 TimerThread 线程 
        thread.start();
    }
}



在 TimerThread 自定义线程中的 run() 方法中 , 主要是调用了 mainLoop() 方法 ; 该方法中是一个死循环 , 从循环队列中取出 TimerTask 定时器任务 , 然后执行 ; 必须等待前一个任务执行完毕 , 才能执行下一个任务 ;







四、Timer 部分源码注释


// 省略了无关代码 
public class Timer {
    /**
     * 定时器线程 
     * 该 TimerThread thread 对象在创建对象时会自动创建 
     */
    // Android-added: @ReachabilitySensitive
    @ReachabilitySensitive
    private final TimerThread thread = new TimerThread(queue);
  // 生成序列号 , 作为定时器线程的默认名称 
    private static int serialNumber() {
        return nextSerialNumber.getAndIncrement();
    }
    /**
     * 创建默认定时器 , TimerThread thread 成员变量不作为守护线程
     */
    public Timer() {
        this("Timer-" + serialNumber());
    }
    /**
     * 创建一个定时器 , 其关联的 TimerThread thread 成员可以设置是否作为守护线程 ;
     * 如果该定时器用于用于调度重复性的维护活动 , 其守护线程会被调用 , 
     * 应用运行期间必须调用守护线程 , 
     * 但是上述操作不能影响应用的生命周期 ; 
     *
     * @param isDaemon 如果设置成 true , TimerThread thread 需要被设置成守护线程 
     */
    public Timer(boolean isDaemon) {
        this("Timer-" + serialNumber(), isDaemon);
    }
    /**
     * 创建一个 Timer 定时器 , 并为其其关联的线程设置指定的名称 ; 
     *
     * @param name 为 TimerThread 成员变量设置名称
     */
    public Timer(String name) {
        thread.setName(name);
        // 启动 TimerThread 线程 
        thread.start();
    }
    /**
     * Creates a new timer whose associated thread has the specified name,
     * and may be specified to
     * {@linkplain Thread#setDaemon run as a daemon}.
     *
     * @param name 设置关联的定时器线程名称
     * @param isDaemon 如果设置成 true 定时器线程将被设置成守护线程
     */
    public Timer(String name, boolean isDaemon) {
        thread.setName(name);
        thread.setDaemon(isDaemon);
        thread.start();
    }
}
/**
 * TimerThread 是定义在 Timer.java 文件中的类 ; 
 * 
 * 该帮助类实现了定时器任务的执行线程 , 该线程中的定时器队列等待任务到来 , 在合适的时间执行定时器任务 ; 
 * 调度需要重复执行的任务 ;
 * 从任务队列中 , 移出被取消的任务 , 移出不需要循环执行的任务 ; 
 */
class TimerThread extends Thread {
    /**
     * 该标志为会 被 reaper  设置成 false , 用于通知我们定时器对象已经被销毁 ; 
     * 一旦该标志位设置成 true , 并且任务队列中没有任务时 , 没有任务去执行 , 这里就需要优雅的关闭定时器 ; 
     * 注意该字段由队列管理器维护 ; 
     */
    boolean newTasksMayBeScheduled = true;
    /**
     * 任务队列 , 调用 schedule 方法调度 TimerTask 定时器任务 , 就是将任务加入到该队列中 ; 
     * 定时器任务队列 , 在定时器线程中维护该队列 , 而不是在定时器中维护 , 这样避免循环引用 ; 
     * 否则定时器永远不会被回收 , 并且该线程永远不会消失 ; 
     */
    private TaskQueue queue;
    TimerThread(TaskQueue queue) {
        this.queue = queue;
    }
  // run 方法中主要是调用 mainLoop() 方法 ; 
    public void run() {
        try {
            mainLoop();
        } finally {
            // 如果定时器被取消 , 需要杀死该线程
            synchronized(queue) {
                newTasksMayBeScheduled = false;
                queue.clear();  // 消除过时的引用
            }
        }
    }
    /**
     * 定时器轮询 
     * 里面是一个死循环 , 循环从人物队列中取出 TimerTask 定时器任务 , 然后执行 ; 
     * 必须等待前一个任务执行完毕 , 才能执行下一个任务 
     */
    private void mainLoop() {
        while (true) {
            try {
                TimerTask task; // 定时器任务 
                boolean taskFired; 
                synchronized(queue) {
                    // 如果队列为空 , 一直阻塞 , 当有任务进来时 , 解除阻塞
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    if (queue.isEmpty())
                        break; // 如果队列为空 , 就会退出循环 , 终止该定时器 
                    // 队列不为空 , 查看第一个 evt , 并执行相应操作 
                    long currentTime, executionTime;
                    task = queue.getMin();
                    synchronized(task.lock) {
                        if (task.state == TimerTask.CANCELLED) {
                            queue.removeMin();
                            continue;  // 任务被取消 , 从队列中移除
                        }
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;
                        if (taskFired = (executionTime<=currentTime)) {
                            if (task.period == 0) { // 该任务不需要循环执行 , 从队列中移除 
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else { // 该任务需要循环执行 , 继续调度
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    if (!taskFired) // 等待前一个任务执行完毕 , 如果之前的任务没有执行完毕 , 一直阻塞
                        queue.wait(executionTime - currentTime);
                }
                if (taskFired)  // 任务执行完毕
                    task.run();
            } catch(InterruptedException e) {
            }
        }
    }
}






五、源码及资源下载


源码及资源下载地址 :


① GitHub 工程地址 : Android_Asynchronous


② MainActivity.java 主界面代码地址 : MainActivity.java , 这是上述示例代码位置 ; 仅做参考意义不大 ;


目录
相关文章
|
8月前
|
XML Android开发 数据格式
Android 中简单计时器的实现方法(Handler和TimerTask)
Android 中简单计时器的实现方法(Handler和TimerTask)
332 0
|
Java Android开发
Android计时器TimerTask,Timer,Handler
Android计时器TimerTask,Timer,若要在TimerTask中更新主线程UI,鉴于Android编程模型不允许在非主线程中更新主线程UI,因此需要结合Android的Handler实现在Java的TimerTask中更新主线程UI。
1359 0
|
3天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
24 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
26天前
|
Java Android开发
Android 开发获取通知栏权限时会出现两个应用图标
Android 开发获取通知栏权限时会出现两个应用图标
12 0
|
1月前
|
XML 缓存 Android开发
Android开发,使用kotlin学习多媒体功能(详细)
Android开发,使用kotlin学习多媒体功能(详细)
102 0
|
1月前
|
设计模式 人工智能 开发工具
安卓应用开发:构建未来移动体验
【2月更文挑战第17天】 随着智能手机的普及和移动互联网技术的不断进步,安卓应用开发已成为一个热门领域。本文将深入探讨安卓平台的应用开发流程、关键技术以及未来发展趋势。通过分析安卓系统的架构、开发工具和框架,本文旨在为开发者提供全面的技术指导,帮助他们构建高效、创新的移动应用,以满足不断变化的市场需求。
18 1
|
17小时前
|
数据库 Android开发 开发者
安卓应用开发:构建高效用户界面的策略
【4月更文挑战第24天】 在竞争激烈的移动应用市场中,一个流畅且响应迅速的用户界面(UI)是吸引和保留用户的关键。针对安卓平台,开发者面临着多样化的设备和系统版本,这增加了构建高效UI的复杂性。本文将深入分析安卓平台上构建高效用户界面的最佳实践,包括布局优化、资源管理和绘制性能的考量,旨在为开发者提供实用的技术指南,帮助他们创建更流畅的用户体验。
|
17天前
|
XML 开发工具 Android开发
构建高效的安卓应用:使用Jetpack Compose优化UI开发
【4月更文挑战第7天】 随着Android开发不断进化,开发者面临着提高应用性能与简化UI构建流程的双重挑战。本文将探讨如何使用Jetpack Compose这一现代UI工具包来优化安卓应用的开发流程,并提升用户界面的流畅性与一致性。通过介绍Jetpack Compose的核心概念、与传统方法的区别以及实际集成步骤,我们旨在提供一种高效且可靠的解决方案,以帮助开发者构建响应迅速且用户体验优良的安卓应用。