1.描述

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

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

2.代码实现

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

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.download.app.timerdemo.MainActivity"> <TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="40dp"
android:textSize="50sp"
android:padding="10dp"
android:background="#bebebe"
android:text="@string/_00_00_00" /> <Button
android:id="@+id/btn_play"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="100dp"
android:background="@drawable/icon_start_bg"/>
</RelativeLayout>

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

 package com.download.app.timerdemo;

 import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView; import java.text.SimpleDateFormat; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private TextView textView;
private Button button;
private boolean isStart = false; //标识
private static final int MSG_START = 0; //消息(what) private HandlerThread mHandlerThread ;
private Handler mHandler; private Handler UIHandler = new Handler(); //线程Handler,用于更新UI @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化控件
init();
//创建后台线程
createBackThread();
} @Override
protected void onDestroy() {
super.onDestroy();
//释放资源
mHandlerThread.quit();
} private void init() {
textView = (TextView) findViewById(R.id.textView);
button = (Button) findViewById(R.id.btn_play);
button.setOnClickListener(this);
} private void createBackThread() {
//创建HandlerThread,名字为"gettime"
mHandlerThread = new HandlerThread("gettime");
//开启HandlerThread
mHandlerThread.start();
//在该Handler中创建一个Handler对象
mHandler = new Handler(mHandlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
            //在这里可以进行耗时操作,是在线程中运行的//
if(isStart) { //isStart是ture的时间,进行以下操作
//获取时间
SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
final String time = (sdf.format(System.currentTimeMillis())).split(" ")[1];
//向UIHandler发送消息,更新UI
UIHandler.post(new Runnable() {
@Override
public void run() {
textView.setText(time);
}
});
//向mHandler发送延时消息
mHandler.sendEmptyMessageDelayed(MSG_START,1000);
}
}
}; } @Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_play:
if (!isStart) {
//开始计时
view.setBackground(getDrawable(R.drawable.icon_pause_bg));
isStart = true;
mHandler.sendEmptyMessage(MSG_START); } else {
//停止计时
isStart = false;
view.setBackground(getDrawable(R.drawable.icon_start_bg));
}
break;
default:
break;
}
} }

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 = -; // 线程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]之间
}

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

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

当你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.参考文章

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

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

    Demo2\clock_demo\src\main\res\layout\activity_main.xml <LinearLayout xmlns:android="http://s ...

  2. C#开发漂亮的数字时钟

    今天用C#做了一个漂亮的数字时钟.界面如下. 实现技术:主要是通过Graphics类的DrawImage方法来绘制数字时钟中所有的数字,这些数字是从网上找的一些图片文件.时钟使用DateTime中No ...

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

    这个数字时钟的源码可以在Qt Demo中找到,风格是仿Android的,不过该Demo中含有三种动画效果(鉴于本人未曾用过Android的系统,因此不知道Android的数字时钟是否也含有这三种效果) ...

  4. 数字时钟DigClock

    首先建立数字显示类: using System; using System.Drawing; namespace CsDev { class SevenSegmentDispay { Graphics ...

  5. android脚步---数字时钟和模拟时钟

    时钟UI组件是两个非常简单的组件,分为Digitalclock  和Analogclock, main.xml文件,书中程序有问题,加了两个组件,一个Button和一个<Chronometer ...

  6. 基于Verilog HDL 的数字时钟设计

    基于Verilog HDL的数字时钟设计 一.实验内容:     利用FPGA实现数字时钟设计,附带秒表功能及时间设置功能.时间设置由开关S1和S2控制,分别是增和减.开关S3是模式选择:0是正常时钟 ...

  7. js动态数字时钟

    js动态数字时钟 主要用到知识点: 主要是通过数组的一些方法,如:Array.from() Array.reduce() Array.find() 时间的处理和渲染 js用到面向对象的写法 实现的功能 ...

  8. 简单酷炫的Canvas数字时钟

    声明:本文为原创文章,如需转载,请注明来源WAxes,谢谢! 我记得很早之前就看过这个DEMO,是岑安大大博客里看到的: 就是这个数字时钟,当时觉得这个创意不错,但是也没去折腾.直到昨天同事又在网上看 ...

  9. 使用jQuery和CSS3实现一个数字时钟

    点击进入更详细教程及源码下载     在线演示 我们经常会在网站中看见一个时钟的效果.今天向大家分享一个使用jQuery和CSS3实现一个数字时钟教程. http://www.html5cn.org/ ...

随机推荐

  1. TransactionTemplate编程式事务管理方式的进阶使用---自定义拓展模板类

    1, 前面一篇的文章介绍了TransactionTemplate的基本使用方法. 同事在其基础上又做了一层封装,这样更贴合本公司的业务与规范. 2, 首先定义了两个接口: ServiceTemplat ...

  2. spring aop 基于schema的aop

    AOP的基本概念: 连接点(Jointpoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化.方法执行.方法调用.字段调用或处理异常等等,Spring只支持方法执行连接点,在AOP ...

  3. Qt5.5.1和Qt5.3.2编译OCI驱动教程及验证方法

    我们都知道oracle数据库的强大,并且好多企业或者教学用到数据库时都会推荐使用.但是Qt因为版权问题没有封装oracle数据库专用驱动,网上也有一大堆说法和教程,但是或多或少的都有问题.下面废话不多 ...

  4. java_弹球小游戏

    弹球游戏实现原理: 隔一定时间(小于1秒)重新绘制图像,因为Graphics类是一个抽象类,创建子类的时候需要把所有涉及的方法都得重写,所以这里使用的是创建Canvas的子类,只需要重写它的paint ...

  5. HTML中表格

    HTML表格 [表格table] 表格用table表示,表格中的每一行tr表示,一行中的每一列用td表示 th表示的是:表头.表头中的文字,默认为加粗居中.th要放在tr中,用于替换掉td. [tab ...

  6. [C#] out vs ref

    当需要从一个方法中有多个返回值时可以考虑使用out和ref这两个关键字.下面通过代码的方式来说明两者的用法和不同之处. 例如现在有一个如下的Add方法,Add方法只有一个返回值. static int ...

  7. [Leetcode]50. Pow(x, n)

    Implement pow(x, n). 我的做法就比较傻了.排除了所有的特殊情况(而且double一般不可以直接判断==),然后常规情况用循环来做.- -||| 直接用循环,时间复杂度就比较大.应该 ...

  8. Leetcode Pasacl'sTriangle

    对于Vector的用法,实在是知道的太少,算法思想比较简单,核心也就一行代码,但是实现错误就显示平时代码的不熟悉. Given numRows, generate the first numRows ...

  9. 桥接模式二(Bridge)

    昨天写到了桥接模式的代码实现,今天我们就继续来讲完桥接模式.      有认真看的会发现,昨天的代码实现只是两个维度的变化:哪么有人可能就会问了哪如果我要加多一个维度呢?我要具体到企业的哪个部门,哪我 ...

  10. SpringMVC入门--编写一个SpringMVC小程序

    一.SpringMVC的优势 Spring 为展现层提供的基于 MVC 设计理念的优秀的Web 框架,是目前最主流的 MVC 框架之一.Spring3.0 后全面超越 Struts2,成为最优秀的 M ...