Android多线程:HandlerThread使用&源码解析

时间:2021-03-24 17:32:48

Android多线程:HandlerThread使用&源码解析


前言

  • 多线程的应用在Android开发中是非常常见的,常用方法主要有:

    1. 继承Thread类
    2. 实现Runnable接口
    3. Handler
    4. AsyncTask
    5. HandlerThread
  • 今天,我将全面解析多线程其中一种常见用法:HandlerThread

由于本文涉及多线程知识和Handler源码解析,所以阅读本文前建议先看:
Android开发:Handler异步通信机制全面解析(包含Looper、Message Queue)


目录

Android多线程:HandlerThread使用&源码解析


1. 定义

  • Android API提供的一个封装类
  • 继承自Thread类

2. 作用

  • 通过继承Thread类,实现快速地创建一个带有Looper的新工作线程的功能
  • 通过对Handler类使用的封装,实现快速创建Handler并与其他线程进行通信的功能

总结

HandlerThread本质上是通过继承Thread类封装Handler类的使用,从而使得创建新线程和与其他线程进行通信变得更加方便易用


3. 使用对比

3.1 Thread+Handler用法

因为HandlerThread本质上是继承Thread类和对Handler类使用的封装,所以我们先来看下一般情况下使用Thread+Handler创建线程的用法:
1. 使用Thread类创建一个新的线程
2. 通过创建Handler与其他线程进行通信

如果你还没阅读过Handler的源码,强烈建议先看文章:
Android开发:Handler异步通信机制全面解析(包含Looper、Message Queue)

Thread+Handler的一般用法

Handler mHandler;
private void Thread_With_Handler() {
//步骤1:实例化Thread类对象
new Thread() {
@Override
public void run() {
super.run();
//步骤2:创建与当前线程绑定的Looper实例,并初始化MessageQueue
Looper.prepare();
//步骤3:生成Handler实例并与上面创建的Looper进行绑定
mHandler = new Handler(Looper.myLooper());
//步骤4:实现消息循环
Looper.loop();
}
}.start();
}

在注释里已经非常清晰了,接下来我将继续介绍更加方便易用的HandlerThread

3.2 HandlerThread用法

  • HandlerThread的本质:继承Thread类和对Handler类使用的封装。
  • 我们先来看HandlerThread的具体使用步骤
//步骤1:创建HandlerThread的实例对象=已经创建了一个新线程
//参数=线程名字,作用是标记该线程
HandlerThread mHandlerThread = new HandlerThread("handlerThread");

//步骤2:启动线程
mHandlerThread.start();

//步骤3:创建工作线程Handler,实现消息处理的操作,并与其他线程进行通信
Handler mHandler = new Handler( handlerThread.getLooper() ) {
@Override
public boolean handleMessage(Message msg) {
//消息处理
return true;
}
});

//步骤4:结束线程,即停止线程的消息循环
mHandlerThread.quit();
  • 是不是感觉 HandlerThread更加清晰、简单、易用?!
  • 接下来,我们开始对HandlerThread的用法进行源码分析,看一下HandlerThread是如何通过继承Thread类和封装Handler类用法从而使得多线程通信变得如此方便。

4. HandlerThread源码分析

结合HandlerThread上面的用法和本质来看,其实主要是分析:
- 如何创建新线程,即分析

HandlerThread mHandlerThread = new HandlerThread("handlerThread");
  • 如何创建Handler并进行通信,即分析
mHandlerThread.start();
Handler mHandler = new Handler( handlerThread.getLooper()) ;
  • 如何结束线程,即分析
mHandlerThread.quit();

以下是HandlerThread类的源码(注释标注得非常清晰)


public class HandlerThread extends Thread {
//线程优先级
int mPriority;
//当前线程id
int mTid = -1;
//当前线程持有的Looper对象
Looper mLooper;

/*分析1:如何创建新线程*/

//HandlerThread在进行实例化的同时,已经创建了一个新线程,这说明猫腻是在构造方法
//HandlerThread类有两个构造方法
//不同之处就是设置当前线程的优先级参数。你可以根据自己的情况来设置优先级,也可以使用默认优先级。

//方法1. 默认优先级
public HandlerThread(String name) {
//通过调用父类默认的方法创建线程
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}

//方法2. 根据需求设置优先级-构造方法带优先级参数
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}

/*分析2:如何创建Handler*/

//步骤1:通过run()方法作好创建Handler的准备
@Override
public void run() {
//获得当前线程的id
mTid = Process.myTid();
//创建了一个Looper对象并初始化了一个MessageQueue
Looper.prepare();
//持有锁机制来获得当前线程的Looper对象
synchronized (this) {
mLooper = Looper.myLooper();

//通知getLooper方法中的wait当前线程已经创建mLooper对象成功,让wait结束等待
notifyAll();
}
//设置当前线程的优先级
Process.setThreadPriority(mPriority);
//在线程循环之前做一些准备工作
//该方法实现体是空的,子类可以实现该方法,也可以不实现。
onLooperPrepared();
//进行消息循环,即不断从MessageQueue中取消息和派发消息
Looper.loop();
mTid = -1;
}
}

//线程循环前的准备工作
protected void onLooperPrepared() {
}

//步骤2:在创建Handler时调用了getLooper()
//Handler mHandler = new Handler( handlerThread.getLooper())
//getLooper()的作用主要是获得当前HandlerThread线程中的mLooper对象
public Looper getLooper() {
//如果线程不是存活的,则直接返回null
if (!isAlive()) {
return null;
}

synchronized (this) {
//首先判断当前线程是否存活
//如果不是存活的,这直接返回null。
//其次如果当前线程存活的,在判断线程的成员变量mLooper是否为null,如果为null,说明当前线程已经创建成功,但是还没来得及创建Looper对象
while (isAlive() && mLooper == null) {
try {
//因此,这里会调用wait方法去等待
wait();
} catch (InterruptedException e) {
}
}
}
//当run方法中的notifyAll方法调用之后通知当前线程的wait方法结束等待并跳出循环
//最终getLooper()返回的是我们在run方法中创建的mLooper
return mLooper;
}

/*分析3:结束新线程*/

//Handler有两种让当前线程退出循环的方法:quit() 和 quitSafely()
//第一种:效率高,但线程不安全
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
//第二种:效率低,但线程安全
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}

注意:

  • 在获得mLooper对象的时候存在一个同步的问题:只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值,才能将创建的Handler与该Looper绑定
  • 解决方案:在run()中成功创建Looper对象后,立即调用notifyAll()通知 getLooper()中的wait()结束等待,并返回run()中成功创建的Looper对象,使得Handler与该Looper对象绑定

5. 实例使用

5.1 实例说明:

  • 目的:点击按钮实现延迟操作并更新UI组件
  • 使用方法:HandlerThread类

在看下面例子之前我建议你们先去下载Demo,边下边读效果更佳哦!

源码地址:Demo for HandlerThread

5.2 具体代码

  1. 主布局文件
    activity_main.xml
<?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.example.carson_ho.handlerthread.MainActivity">

//用于显示结果
<TextView
android:id="@+id/text1"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="显示结果" />

//点击按钮1和2实现相应延时操作 & UI组件更新
<Button
android:id="@+id/button1"
android:layout_centerInParent="true"
android:layout_below="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="延迟3秒显示我爱学习"/>


<Button
android:id="@+id/button2"
android:layout_centerInParent="true"
android:layout_below="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="延迟10秒显示我不爱学习"/>

//点击按钮结束线程
<Button
android:id="@+id/button3"
android:layout_centerInParent="true"
android:layout_below="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="结束线程的消息循环"/>

</RelativeLayout>
  1. MainActivity文件

MainActivity.java(注释已经标注得非常清楚)

package com.example.carson_ho.handlerthread;

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;

public class MainActivity extends AppCompatActivity {

HandlerThread mHandlerThread;
Handler mainHandler,workHandler;
TextView text;
Button button1,button2,button3;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//显示文本
text = (TextView) findViewById(R.id.text1);

//创建与主线程关联的Handler
mainHandler = new Handler();

//创建后台线程
initBackground();

//点击Button1-通过SendMessage方法将0x121消息发送至MessageQueue
//在工作线程中,当消息循环时取出0x121消息并在工作线程执行相关操作
button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

workHandler.sendEmptyMessage(0x121);
}
});

//点击Button2-通过SendMessage方法将0x122消息发送至MessageQueue
//在工作线程中,当消息循环时取出0x122消息并在工作线程执行相关操作
button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

workHandler.sendEmptyMessage(0x122);
}
});


//点击Button3-退出消息循环
button3 = (Button) findViewById(R.id.button3);
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

mHandlerThread.quit();
}
});

}

private void initBackground(){
//通过实例化mHandlerThread从而创建新线程
mHandlerThread = new HandlerThread("handlerThread");
//启动新线程
mHandlerThread.start();

//创建与工作线程相关联的Handler,并与mHandlerThread所创建的Looper相关联
//实现了Handler与工作线程相关联
//下面HandlerMessage的方法体均会在mHandlerThread所创建的工作线程中执行
workHandler = new Handler(mHandlerThread.getLooper()){
@Override
//消息处理的操作
public void handleMessage(Message msg)
{
//设置了两种消息处理操作,通过msg来进行识别
switch(msg.what){
//标识1:0x121
case 0x121:
try {
//延时操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//通过主线程Handler.post方法进行在主线程的UI更新操作
//可能有人看到new了一个Runnable就以为是又开了一个新线程
//事实上并没有开启任何新线程,只是使run()方法体的代码抛到与mHandler相关联的线程中执行,我们知道mainHandler是与主线程关联的,所以更新TextView组件依然发生在主线程
mainHandler.post(new Runnable() {
@Override
public void run () {
text.setText("我爱学习");
}
});
break;

//标识2:0x122
case 0x122:
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mainHandler.post(new Runnable() {
@Override
public void run () {
text.setText("我不喜欢学习");
}
});
break;
default:
break;
}
}
};
}
}

5.3 运行结果

Android多线程:HandlerThread使用&源码解析

Android多线程:HandlerThread使用&源码解析

Android多线程:HandlerThread使用&源码解析

5.4 特别注意

从上面的例子中会出现两个细节问题:
5.4.1 细节问题1:内存泄露
在上面的例子中,你会发现出现了严重的警告:

In Android, Handler classes should be static or leaks might occur.

即造成了严重的内存泄漏,关于Handler的内存泄露请参考 Android开发:详解Handler的内存泄露

5.4.2 细节问题2:连续发送消息

  • 当你连续点击3下时,发现并没有马上按照最新点击的按钮的操作去显示,而是按顺序的一个个显示出来
  • 原因:使用HandlerThread时只是开了一个工作线程,当你点击了x下后,只是将x个消息发送到消息队列MessageQueue里排队,等候派发消息给Handler再进行对应的操作

5.5 Demo的源码地址

源码地址放在Github上:Demo for HandlerThread

6. 总结

  • 本文主要对多线程HandlerThread的用法和源码进行了全面介绍
  • 接下来,我会继续讲解Android开发中关于多线程的知识,包括继承Thread类、实现Runnable接口、Handler等等,有兴趣可以继续关注Carson_Ho的安卓开发笔记

请点赞!因为你的鼓励是我写作的最大动力!

相关文章阅读
1分钟全面了解“设计模式”
Android开发:最全面、最易懂的Android屏幕适配解决方案
Android开发:Handler异步通信机制全面解析(包含Looper、Message Queue)
Android开发:顶部Tab导航栏实现(TabLayout+ViewPager+Fragment)
Android开发:底部Tab菜单栏实现(FragmentTabHost+ViewPager)
Android开发:JSON简介及最全面解析方法!
Android开发:XML简介及DOM、SAX、PULL解析对比


欢迎关注Carson_Ho的简书!

不定期分享关于安卓开发的干货,追求短、平、快,但却不缺深度
Android多线程:HandlerThread使用&源码解析