HandlerThread实现数字时钟
Posted big明
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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.参考文章
以上是关于HandlerThread实现数字时钟的主要内容,如果未能解决你的问题,请参考以下文章