1.前言

我们知道在Android系统中,我们执行完耗时操作都要另外开启子线程来执行,执行完线程以后线程会自动销毁。

想象一下如果我们在项目中经常要执行耗时操作,如果经常要开启线程,接着又销毁线程,

这无疑是很消耗性能的?那有什么解决方法呢?

  1. 使用线程池
  2. 使用HandlerThread

本篇文章主要讲解一下问题

  1. HandlerThread的使用场景以及怎样使用HandlerThread?
  2. HandlerThread源码分析

2.HandlerThread的使用场景以及怎样使用HandlerThread?

使用场景

HandlerThread是Google帮我们封装好的,可以用来执行多个耗时操作,而不需要多次开启线程,

里面是采用handler和Looper实现的

Handy class for starting a new thread that has a looper.

The looper can then be used to create handler classes. Note that start() must still be called.

怎样使用HandlerThread?

  1. 创建HandlerThread的实例对象
HandlerThread handlerThread = new HandlerThread("myHandlerThread");

该参数表示线程的名字,可以随便选择。

  1. 启动我们创建的HandlerThread线程
handlerThread.start();

3.将我们的handlerThread与Handler绑定在一起。 还记得是怎样将Handler与线程对象绑定在一起的吗?

其实很简单,就是将线程的looper与Handler绑定在一起,代码如下:

mThreadHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
checkForUpdate();
if(isUpdate){
mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
}
}
};

注意必须按照以上三个步骤来,下面在讲解源码的时候会分析其原因

完整测试代码如下:

public class MainActivity extends AppCompatActivity {
private TextView mTv;
Handler mMainHandler = new Handler();
private Handler mThreadHandler;
private static final int MSG_UPDATE_INFO = 0x100;
private HandlerThread mHandlerThread;
private boolean isUpdate=true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTv = (TextView) findViewById(R.id.tv);
initHandlerThread();
}
private void initHandlerThread() {
mHandlerThread = new HandlerThread("xujun");
mHandlerThread.start();
mThreadHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
checkForUpdate();
if(isUpdate){
mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
}
}
};
}
/**
* 模拟从服务器解析数据
*/
private void checkForUpdate() {
try {
//模拟耗时
Thread.sleep(1200);
mMainHandler.post(new Runnable() {
@Override
public void run() {
String result = "实时更新中,当前股票行情:<font color='red'>%d</font>";
result = String.format(result, (int) (Math.random() * 5000 + 1000));
mTv.setText(Html.fromHtml(result));
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected void onResume() {
isUpdate=true;
super.onResume();
mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
}
@Override
protected void onPause() {
super.onPause();
isUpdate=false;
mThreadHandler.removeMessages(MSG_UPDATE_INFO);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandlerThread.quit();
mMainHandler.removeCallbacksAndMessages(null);
}
}

运行以上测试代码,将可以看到如下效果图(例子不太恰当,主要使用场景是在handleMessage中执行耗时操作)

3.HandlerThread源码分析

官方源代码如下,是基于sdk23的,可以看到,只有一百多行代码而已

public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
//持有锁机制来获得当前线程的Looper对象
synchronized (this) {
mLooper = Looper.myLooper();
//发出通知,当前线程已经创建mLooper对象成功,这里主要是通知getLooper方法中的wait
notifyAll();
}
//设置线程的优先级别
Process.setThreadPriority(mPriority);
//这里默认是空方法的实现,我们可以重写这个方法来做一些线程开始之前的准备,方便扩展
onLooperPrepared();
Looper.loop();
mTid = -1;
}
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// 直到线程创建完Looper之后才能获得Looper对象,Looper未创建成功,阻塞
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
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;
}
/**
* Returns the identifier of this thread. See Process.myTid().
*/
public int getThreadId() {
return mTid;
}
}

1)首先我们先来看一下它的构造方法

 public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}

有两个构造方法,一个参数的和两个参数的,name代表当前线程的名称,priority为线程的优先级别

2)接着我们来看一下run()方法,在run方法里面我们可以看到我们会初始化一个Looper,并设置线程的优先级别

public void run() {
mTid = Process.myTid();
Looper.prepare();
//持有锁机制来获得当前线程的Looper对象
synchronized (this) {
mLooper = Looper.myLooper();
//发出通知,当前线程已经创建mLooper对象成功,这里主要是通知getLooper方法中的wait
notifyAll();
}
//设置线程的优先级别
Process.setThreadPriority(mPriority);
//这里默认是空方法的实现,我们可以重写这个方法来做一些线程开始之前的准备,方便扩展
onLooperPrepared();
Looper.loop();
mTid = -1;
}
  • 还记得我们前面我们说到使用HandlerThread的时候必须调用start()方法,接着才可以将我们的HandlerThread和我们的handler绑定在一起吗?其实原因就是我们是在run()方法才开始初始化我们的looper,而我们调用HandlerThread的start()方法的时候,线程会交给虚拟机调度,由虚拟机自动调用run方法
mHandlerThread.start();
mThreadHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
checkForUpdate();
if(isUpdate){
mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
}
}
};

  • 这里我们为什么要使用锁机制和notifyAll();,原因我们可以从getLooper()方法中知道

public Looper getLooper() {
if (!isAlive()) {
return null;
}
// 直到线程创建完Looper之后才能获得Looper对象,Looper未创建成功,阻塞
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}

总结:在获得mLooper对象的时候存在一个同步的问题,

    只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值。

    这里等待方法wait和run方法中的notifyAll方法共同完成同步问题。

 

3)接着我们来看一下quit方法和quitSafe方法

//调用这个方法退出Looper消息循环,及退出线程
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
//调用这个方法安全地退出线程
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}

跟踪这两个方法容易知道只两个方法最终都会调用MessageQueue的quit(boolean safe)方法

void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
//安全退出调用这个方法
if (safe) {
removeAllFutureMessagesLocked();
} else {//不安全退出调用这个方法
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}

不安全的会调用removeAllMessagesLocked();这个方法,我们来看这个方法是怎样处理的,

其实就是遍历Message链表,移除所有信息的回调,并重置为null

private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}

安全地会调用removeAllFutureMessagesLocked();这个方法,它会根据Message.when这个属性,

判断我们当前消息队列是否正在处理消息,没有正在处理消息的话,直接移除所有回调,正在处理的话,

等待该消息处理处理完毕再退出该循环。因此说quitSafe()是安全的,而quit()方法是不安全的,

因为quit方法不管是否正在处理消息,直接移除所有回调。


private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if (p != null) {
//判断当前队列中的消息是否正在处理这个消息,》没有的话,直接移除所有回调
if (p.when > now) {
removeAllMessagesLocked();
} else {//正在处理的话,等待该消息处理处理完毕再退出该循环
Message n;
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}

4.参考文章

Android面试收集录8 HandlerThread详解的更多相关文章

  1. Android面试收集录9 IntentService详解

    一. 定义 IntentService是Android里面的一个封装类,继承自四大组件之一的Service. 二.作用 处理异步请求,实现多线程 三. 工作流程 注意:若启动IntentService ...

  2. Android面试收集录4 Fragment详解

    1.什么是Fragment? 你可以简单的理解为,Fragment是显示在Activity中的Activity. 它可以显示在Activity中,然后它也可以显示出一些内容. 因为它拥有自己的生命周期 ...

  3. Android面试收集录7 AsyncTask详解

    1.Android中的线程 在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制地产生, 并且 **线程的创建和销毁都会有相应的开销.**当系统中存在大量的线 ...

  4. Android面试收集录3 ContentProvider详解

    1.ContentProvider简单介绍 1.1.定义 ContentProvider,即内容提供者属于Android的四大组件之一. 1.2.作用 进程间进行数据交互&共享,即跨进程通信. ...

  5. Android面试收集录1 Activity+Service

    1.Activity的生命周期 1.1.首先查看一下Activity生命周期经典图片. 在正常情况下,一个Activity从启动到结束会以如下顺序经历整个生命周期: onCreate()->on ...

  6. Android面试收集录18 Android Context详解

    Activity mActivity =new Activity() 作为Android开发者,不知道你有没有思考过这个问题,Activity可以new吗?Android的应用程序开发采用JAVA语言 ...

  7. Android面试收集录2 Broadcast Receiver详解

    1.Broadcast Receiver广播接收器简单介绍 1.1.定义 Broadcast Receiver(广播接收器),属于Android四大组件之一 在Android开发中,Broadcast ...

  8. Android面试收集录6 事件分发机制

    转自:秋招面试宝典. 一. 基础认知 1.1 事件分发的对象是谁? 答:事件 当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件). Touch事件相关细节(发 ...

  9. Android面试收集录 Android系统的资源+其他

    1.Android应用程序的资源是如何存储的,如何使用? res文件夹或者assets文件夹 res目录中的资源在R类中生成一个int变量,然后再布局文件中可以直接使用,在代码中,要getResour ...

随机推荐

  1. Springboot中SpringMvc拦截器配置与应用(实战)

    一.什么是拦截器,及其作用 拦截器(Interceptor): 用于在某个方法被访问之前进行拦截,然后在方法执行之前或之后加入某些操作,其实就是AOP的一种实现策略.它通过动态拦截Action调用的对 ...

  2. 日志滚动与cron调度

    日志滚动与cron调度 1.日志滚动 nginx默认日志不能进行滚动,始终写入到一个文件中,即access.log.编写日志滚动的shell脚本,并使用linux的cron定时调度周期性进行日志文件滚 ...

  3. session的MaxInactiveInterval=0在tomcat6和tomcat8不同表现

    在tomcat6中调用 request.getSession().setMaxInactiveInterval(0); 这个session会立即过期. 而在tomcat8中,同样的调用,会导致这个se ...

  4. MySQL入门很简单: 8查询数据

    1. 查询语句语法 SELECT 属性列表 FROM 表名和视图列表 [WHERE 条件表达式1] [GROUP BY 属性名1 [HAVING t条件表达式2]] [ORDER BY 属性名2 [A ...

  5. Locust的官网及安装

    Locust官网: https://docs.locust.io/en/latest/installation.html for Python 3: $ python3 -m pip install ...

  6. Invalid MyEclipse License - Discontinuing this MyEclipse operation. 出现这个错误怎么改正?

    Invalid MyEclipse License - Discontinuing this MyEclipse operation这句话的意思是无效的许可证-停用此MyEclipse操作入门就是你的 ...

  7. vsftpd 启动 vsftpd:500 OOPS: bad bool value in config file for: guest_enable

    不然启动时会涌现毛病,举个例子 guest_enable=YES  后面出现空格,就会出现 为 vsftpd 启动 vsftpd:500 OOPS: bad bool value in config ...

  8. Dijkstra单源最短路径,POJ(2387)

    题目链接:http://poj.org/problem?id=2387 Dijkstra算法: //求某一点(源点)到另一点的最短路,算法其实也和源点到所有点的时间复杂度一样,O(n^2); 图G(V ...

  9. removing vmware debugger from visual studio

    removing vmware debugger from visual studio by Ross on 十月 14, 2010 at 5:30 下午 under Visual Studio |  ...

  10. linq 查询的两种方法 (在EF model中实现)

    众所周知:linq查询有两种方式 1.通过linq表达式查询 2.是通过linq方法查询 代码中 每一步都有注释