HandlerThread实现数字时钟

1.描述

刚看完Android多线程编程,对HandlerThread比较感兴趣,趁热巩固练习,实现一个了数字时钟,希望对学习HandlerThread有所帮助。如下:

  • 启动一个HandlerThread不断获取时间
  • 每隔一秒钟通过Handler通知UI线程更新界面的显示
  • 界面上有按钮可以暂停、继续的计时

2.代码实现

创建一个TimerDemo工程,内容很简单,主要是两个文件:布局文件activity_main.xml和Activity文件MainActivity.java。先看activity_main.xml文件,里面只有一个TextView用于显示时间,一个Button用于开始\停止显示时间。

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <RelativeLayout
 3     xmlns:android="http://schemas.android.com/apk/res/android"
 4     xmlns:tools="http://schemas.android.com/tools"
 5     android:layout_width="match_parent"
 6     android:layout_height="match_parent"
 7     tools:context="com.download.app.timerdemo.MainActivity">
 8
 9
10     <TextView
11         android:id="@+id/textView"
12         android:layout_width="wrap_content"
13         android:layout_height="wrap_content"
14         android:layout_centerHorizontal="true"
15         android:layout_marginTop="40dp"
16         android:textSize="50sp"
17         android:padding="10dp"
18         android:background="#bebebe"
19         android:text="@string/_00_00_00" />
20
21     <Button
22         android:id="@+id/btn_play"
23         android:layout_width="80dp"
24         android:layout_height="80dp"
25         android:layout_alignParentBottom="true"
26         android:layout_centerHorizontal="true"
27         android:layout_marginBottom="100dp"
28         android:background="@drawable/icon_start_bg"/>
29 </RelativeLayout>

接着是核心部分的MainActivity.java,主要流程是初次点击按钮,向由HandlerThread创建的Handler(mHandler)中发送开始计时的消息,mHandler收到消息就获取时间并发UIHandler发送消息更新Textview,结束之后mHandler再给自己发送一个延时消息(延时1s),这样就可以每秒云更新时间。当然为了模拟时钟坏了,停止更新时间,增加一个标识(isStart),初次点击设为ture,再次点击设为false,这样反复。因此在mHandler向自身发送延时消息时判断该标识(isStart),就可以实现暂停\继续计时功能。更通俗的描述是点击按钮时钟修好了开始工作,再次点点击按钮,时钟坏了,停止工作。代码如下:

  1 package com.download.app.timerdemo;
  2
  3 import android.os.Bundle;
  4 import android.os.Handler;
  5 import android.os.HandlerThread;
  6 import android.os.Message;
  7 import android.support.v7.app.AppCompatActivity;
  8 import android.view.View;
  9 import android.widget.Button;
 10 import android.widget.TextView;
 11
 12 import java.text.SimpleDateFormat;
 13
 14 public class MainActivity extends AppCompatActivity implements View.OnClickListener {
 15
 16     private TextView textView;
 17     private Button button;
 18     private boolean isStart = false;            //标识
 19     private static final  int MSG_START = 0;    //消息(what)
 20
 21     private HandlerThread mHandlerThread ;
 22     private Handler mHandler;
 23
 24     private Handler UIHandler = new Handler();  //线程Handler,用于更新UI
 25
 26     @Override
 27     protected void onCreate(Bundle savedInstanceState) {
 28         super.onCreate(savedInstanceState);
 29         setContentView(R.layout.activity_main);
 30         //初始化控件
 31         init();
 32         //创建后台线程
 33         createBackThread();
 34     }
 35
 36     @Override
 37     protected void onDestroy() {
 38         super.onDestroy();
 39         //释放资源
 40         mHandlerThread.quit();
 41     }
 42
 43     private void init() {
 44         textView = (TextView) findViewById(R.id.textView);
 45         button = (Button) findViewById(R.id.btn_play);
 46         button.setOnClickListener(this);
 47     }
 48
 49     private void createBackThread() {
 50         //创建HandlerThread,名字为"gettime"
 51         mHandlerThread = new HandlerThread("gettime");
 52         //开启HandlerThread
 53         mHandlerThread.start();
 54         //在该Handler中创建一个Handler对象
 55         mHandler = new Handler(mHandlerThread.getLooper()){
 56             @Override
 57             public void handleMessage(Message msg) {
 58                 super.handleMessage(msg);            //在这里可以进行耗时操作,是在线程中运行的//
 59                 if(isStart) {   //isStart是ture的时间,进行以下操作
 60                     //获取时间
 61                     SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
 62                     final String time = (sdf.format(System.currentTimeMillis())).split(" ")[1];
 63                     //向UIHandler发送消息,更新UI
 64                     UIHandler.post(new Runnable() {
 65                         @Override
 66                         public void run() {
 67                             textView.setText(time);
 68                         }
 69                     });
 70                     //向mHandler发送延时消息
 71                     mHandler.sendEmptyMessageDelayed(MSG_START,1000);
 72                 }
 73             }
 74         };
 75
 76     }
 77
 78     @Override
 79     public void onClick(View view) {
 80         switch (view.getId()) {
 81             case R.id.btn_play:
 82                 if (!isStart) {
 83                     //开始计时
 84                     view.setBackground(getDrawable(R.drawable.icon_pause_bg));
 85                     isStart = true;
 86                     mHandler.sendEmptyMessage(MSG_START);
 87
 88                 } else {
 89                     //停止计时
 90                     isStart = false;
 91                     view.setBackground(getDrawable(R.drawable.icon_start_bg));
 92                 }
 93                 break;
 94             default:
 95                 break;
 96         }
 97     }
 98
 99
100 }

3.效果展示

4.总结

本例子主要使用的HandlerThread,下面对HandlerThread进行剖析。

HandlerThread本质是就是一个普通的Thread,只不过内部建立了Looper(对Handler、Message、Looper、MessageQueue不熟悉的可以去看这篇博客:从Handler.post(Runnable r)再一次梳理Android的消息机制(以及handler的内存泄露))。我们知道Handler是用于异步更新UI,更详细的说就是子线程与UI线程之间的通信,但是如果要想子线程与子线程之间的通信怎么办呢?当然可以用Handler + Thread实现,但是要自己操作Looper,很麻烦,Google官方很贴心的帮我们封装好一个类HandlerThread,类似的还有AsyncTask。不多说,下面直接上HandlerThread源码:

首先是字段和构造函数:

int mPriority; // 线程优先级
    int mTid = -1; // 线程id
    Looper mLooper; // 与线程关联的Looper

    public HandlerThread(String name) { // 提供个名字,方便debug
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT; // 没提供,则使用默认优先级
    }

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority; // 使用用户提供的优先级,基于linux优先级,取值在[-20,19]之间
    }

然后再看看关键的三个方法:

 1  protected void onLooperPrepared() { // callback方法,如果你愿意可以Override放自己的逻辑;其在loop开始前执行
 2     }
 3
 4     @Override
 5     public void run() {
 6         mTid = Process.myTid();
 7         Looper.prepare(); //当前线程创建一个消息循环,调用loop() 方法使之处理信息,直到循环结束。
 8         synchronized (this) { // 进入同步块,当mLooper变的可用的使用,调用notifyAll通知其他可能block在当前对象上的线程
 9             mLooper = Looper.myLooper();
10             notifyAll();
11         }
12         Process.setThreadPriority(mPriority); // 设置线程优先级
13         onLooperPrepared(); // 调用回调函数,这是空方法,可以自已重写逻辑
14         Looper.loop(); // 开始loop
15         mTid = -1; // reset为invalid值
16     }
17
18     public Looper getLooper() {
19         if (!isAlive()) { // 如果线程不是在alive状态则直接返回null,有可能是你忘记调start方法了。。。
20             return null;
21         }
22
23         // 如何这个线程已经启动了,那么将一直等待,直到mlooper被创建。
24         synchronized (this) {
25             while (isAlive() && mLooper == null) { // 进入同步块,当条件不满足时无限等待,
26                 try {                              // 直到mLooper被设置成有效值了才退出while(当然也可能是线程状态不满足);
27                     wait();                        // run方法里的notifyAll就是用来唤醒这里的
28                 } catch (InterruptedException e) { // 忽略InterruptedException
29                 }
30             }
31         }
32         return mLooper; // 最后返回mLooper,此时可以保证是有效值了。
33     }

当你new一个HandlerThread的对象时记得调用其start()方法,然后你可以接着调用其getLooper()方法来new一个Handler对象,最后你就可以利用此Handler对象来往HandlerThread发送消息来让它为你干活了。

最后,看看两个退出方法:

    public boolean quit() {
        Looper looper = getLooper(); // 注意这里是调用getLooper而不是直接使用mLooper,
        if (looper != null) {        // 因为mLooper可能还没初始化完成,而调用方法可以
            looper.quit();           // 等待初始化完成。
            return true;
        }
        return false;
    }

    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

这两个方法都是使HandlerThread不接受新的消息事件加入消息队列。但quit()是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。而quitSafely()是只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。

最后总结一下HandlerThread的特点

  • HandlerThread将loop转到子线程中处理,说白了就是将分担MainLooper的工作量,降低了主线程的压力,使主界面更流畅。
  • 开启一个线程起到多个线程的作用。处理任务是串行执行,按消息发送顺序进行处理。HandlerThread本质是一个线程,在线程内部,代码是串行处理的。
  • 但是由于每一个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
  • HandlerThread拥有自己的消息队列,它不会干扰或阻塞UI线程。
  • 对于网络IO操作,HandlerThread并不适合,因为它只有一个线程,还得排队一个一个等着。


5.参考文章

时间: 2024-10-16 14:01:23

HandlerThread实现数字时钟的相关文章

[Java项目01][数字时钟]

我从2016年3月份开始学Java,看的是某宝得来的视频,目前Java SE还没学完. 前几天手痒在网上搜Java小项目,看到Youtube上的歪果仁们做的Java数字时钟,非常简单,适合我这样的新手,于是也做了一份. 效果大概是是这样的: 源代码传到了GitHub上: https://github.com/jpch89/Digital-Clock 另外还录了份视频,免得以后自己忘了怎么做,传到了Vimeo上: https://vimeo.com/159116313 参考资料如下: 1. Cre

Qt仿Android带特效的数字时钟源码分析(滑动,翻页,旋转效果)

这个数字时钟的源码可以在Qt Demo中找到,风格是仿Android的,不过该Demo中含有三种动画效果(鉴于本人未曾用过Android的系统,因此不知道Android的数字时钟是否也含有这三种效果),其分别为滑动.翻页和旋转. 由于本人的Qt Creator输入中文后显示的都是乱码,因而在此只能使用英文进行注释,后期如果有时间再进行中文的相关整理.可能有些地方理解并不是很正确.希望大家多多指正! 以下为源码: [cpp] view plaincopy #include <QtCore> #i

MFC数字时钟在VS2013的简易制作

首先,新建一个项目:文件--->新建-->项目.选择MFC应用程序.命名为Clock 下一步后选择基于对话框的应用程序,单击完成. 二,先将对话框中的确定和取消等按钮删除,在工具栏中添加两个Static Text的静态文本框,在俩个文本框的属性设置中,将文字(Align Text)设置居中,边界(Border)设置False,名字(Caption)中的Static删除,分别将两个文本框框的ID设置为IDC_STATIC1和IDC_STATIC2.                        

中国MOOC_面向对象程序设计——Java语言_第2周 对象交互_1有秒计时的数字时钟

第2周编程题 查看帮助 返回 第2周编程题,在课程所给的时钟程序的基础上修改 依照学术诚信条款,我保证此作业是本人独立完成的. 温馨提示: 1.本次作业属于Online Judge题目,提交后由系统即时判分. 2.学生可以在作业截止时间之前不限次数提交答案,系统将取其中的最高分作为最终成绩. 1 有秒计时的数字时钟(10分) 题目内容: 这一周的编程题是需要你在课程所给的时钟程序的基础上修改而成.但是我们并不直接给你时钟程序的代码,请根据视频自己输入时钟程序的Display和Clock类的代码,

基于FPGA的简易数字时钟

基于FPGA的可显示数字时钟,设计思路为自底向上,包含三个子模块:时钟模块,进制转换模块.led显示模块.所用到的FPGA晶振频率为50Mhz,首先利用它得到1hz的时钟然后然后得到时钟模块.把时钟模块输出的时.分.秒输入到进制转换模块后得到十进制的值再输入到led显示模块,该project已经在FPGA开发板上亲測可用. 下图为模块示意图(实际project中并没有採用原理图的输入方法.这里仅作示意). 以下分模块说明: clk1:  时钟模块,设计思路为首先依据50M晶振得到1hz的时钟,然

模拟时钟(AnalogClock)和数字时钟(DigitalClock)

Demo2\clock_demo\src\main\res\layout\activity_main.xml 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 and

js之数字时钟

以下是我用js写的数字时钟年月日,日期时间的源码,请多指教: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <

前端作品一之——网页数字时钟制作

1.制作前准备 在素材网上挑选你喜欢的0~9的数字图片,下载下来,用PS将这些图片切成合适在网页上显示的的大小(我切成50px*60px). 2.布局 在HTML将这几张图片引用进去,并排显示,并在每两个中间加上“:”,将包含图片标签的DIV设置成居中. 3.功能实现 将初始的图片显示都设置成数字0,这时在静态页面上会显示“00:00:00”:因为之前已经给图片命名为0~9.png通过getHours().getMinutes().getSeconds()分别获取时分秒这六个数值(需要注意的是当

数字时钟小插件

学习js中----今天试着写了下数字时钟. html代码如下: <body style="font-size:50px;background:black;color:#fff;"> <img src="img/1.png"> <img src="img/4.png"> : <img src="img/0.png"> <img src="img/6.png"