Android 多线程之HandlerThread 完全详解

简介:

关联文章: 
Android 多线程之HandlerThread 完全详解 
Android 多线程之IntentService 完全详解 
android多线程-AsyncTask之工作原理深入解析(上) 
android多线程-AsyncTask之工作原理深入解析(下)

  之前对线程也写过几篇文章,不过倒是没有针对android,因为java与android在线程方面大部分还是相同,不过本篇我们要介绍的是android的专属类HandlerThread,因为HandlerThread在设置思想上还是挺值得我们学习的,那么我们下面来就了解它吧,我们先来看看HandlerThread有那些特点:

  • HandlerThread本质上是一个线程类,它继承了Thread;
  • HandlerThread有自己的内部Looper对象,可以进行looper循环;
  • 通过获取HandlerThread的looper对象传递给Handler对象,可以在handleMessage方法中执行异步任务。
  • 创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。

一、HandlerThread常规使用步骤

了解完上面HandlerThread的一些特点后,我们先来看看HandlerThread使用步骤。 
1.创建实例对象

    1.  HandlerThread handlerThread = new HandlerThread("downloadImage");

 

传入参数的作用主要是标记当前线程的名字,可以任意字符串。 
2.启动HandlerThread线程

    1.  //必须先开启线程
    2.  handlerThread.start();

 

  到此,我们创建完HandlerThread并启动了线程。那么我们怎么将一个耗时的异步任务投放到HandlerThread线程中去执行呢?接下来看下面步骤: 
3.构建循环消息处理机制

  /**
     * 该callback运行于子线程
     */
    class ChildCallback implements Handler.Callback {
        @Override
        public boolean handleMessage(Message msg) { //在子线程中进行相应的网络请求 //通知主线程去更新UI mUIHandler.sendMessage(msg1); return false; } }

 

4.构建异步handler

//子线程Handler
Handler childHandler = new Handler(handlerThread.getLooper(),new ChildCallback());

 

  第3步和第4步是构建一个可以用于异步操作的handler,并将前面创建的HandlerThread的Looper对象以及Callback接口类作为参数传递给当前的handler,这样当前的异步handler就拥有了HandlerThread的Looper对象,由于HandlerThread本身是异步线程,因此Looper也与异步线程绑定,从而handlerMessage方法也就可以异步处理耗时任务了,这样我们的Looper+Handler+MessageQueue+Thread异步循环机制构建完成,来看看一个完整的使用案例。

二、HandlerThread的使用案例

主要代码如下: 
activity_handler_thread.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/image" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>

 

HandlerThreadActivity.java

package com.zejian.handlerlooper;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.widget.ImageView; import com.zejian.handlerlooper.model.ImageModel; import com.zejian.handlerlooper.util.LogUtils; import java.io.BufferedInputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; /** * Created by zejian on 16/9/2. */ public class HandlerThreadActivity extends Activity { /** * 图片地址集合 */ private String url[]={ "http://img.blog.csdn.net/20160903083245762", "http://img.blog.csdn.net/20160903083252184", "http://img.blog.csdn.net/20160903083257871", "http://img.blog.csdn.net/20160903083257871", "http://img.blog.csdn.net/20160903083311972", "http://img.blog.csdn.net/20160903083319668", "http://img.blog.csdn.net/20160903083326871" }; private ImageView imageView; private Handler mUIHandler = new Handler(){ @Override public void handleMessage(Message msg) { LogUtils.e("次数:"+msg.what); ImageModel model = (ImageModel) msg.obj; imageView.setImageBitmap(model.bitmap); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler_thread); imageView= (ImageView) findViewById(R.id.image); //创建异步HandlerThread HandlerThread handlerThread = new HandlerThread("downloadImage"); //必须先开启线程 handlerThread.start(); //子线程Handler Handler childHandler = new Handler(handlerThread.getLooper(),new ChildCallback()); for(int i=0;i<7;i++){ //每个1秒去更新图片 childHandler.sendEmptyMessageDelayed(i,1000*i); } } /** * 该callback运行于子线程 */ class ChildCallback implements Handler.Callback { @Override public boolean handleMessage(Message msg) { //在子线程中进行网络请求 Bitmap bitmap=downloadUrlBitmap(url[msg.what]); ImageModel imageModel=new ImageModel(); imageModel.bitmap=bitmap; imageModel.url=url[msg.what]; Message msg1 = new Message(); msg1.what = msg.what; msg1.obj =imageModel; //通知主线程去更新UI mUIHandler.sendMessage(msg1); return false; } } private Bitmap downloadUrlBitmap(String urlString) { HttpURLConnection urlConnection = null; BufferedInputStream in = null; Bitmap bitmap=null; try { final URL url = new URL(urlString); urlConnection = (HttpURLConnection) url.openConnection(); in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024); bitmap=BitmapFactory.decodeStream(in); } catch (final IOException e) { e.printStackTrace(); } finally { if (urlConnection != null) { urlConnection.disconnect(); } try { if (in != null) { in.close(); } } catch (final IOException e) { e.printStackTrace(); } } return bitmap; } }

 

  在这个案例中,我们创建了两个Handler,一个用于更新UI线程的mUIHandler和一个用于异步下载图片的childHandler。最终的结果是childHandler会每个隔1秒钟通过sendEmptyMessageDelayed方法去通知ChildCallback的回调函数handleMessage方法去下载图片并告诉mUIHandler去更新UI界面,以上便是HandlerThread常规使用,实际上在android比较典型的应用是IntentService,这个我们将放在下篇分析,这里就先不深入了,案例运行截图如下: 

三.HandlerThread源码解析

  HandlerThread的源码不多只有140多行,那就一步一步来分析吧,先来看看其构造函数


/**
 * 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.
 */
public class HandlerThread extends Thread { int mPriority;//线程优先级 int mTid = -1; Looper mLooper;//当前线程持有的Looper对象 public HandlerThread(String name) { super(name); mPriority = Process.THREAD_PRIORITY_DEFAULT; } /** * Constructs a HandlerThread. * @param name * @param priority The priority to run the thread at. The value supplied must be from * {@link android.os.Process} and not from java.lang.Thread. */ 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() { }

 

  从源码可以看出HandlerThread继续自Thread,构造函数的传递参数有两个,一个是name指的是线程的名称,一个是priority指的是线程优先级,我们根据需要调用即可。其中成员变量mLooper就是HandlerThread自己持有的Looper对象。onLooperPrepared()该方法是一个空实现,是留给我们必要时可以去重写的,但是注意重写时机是在Looper循环启动前,再看看run方法:

@Override
public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); //唤醒等待线程 } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }

 

  前面我们在HandlerThread的常规使用中分析过,在创建HandlerThread对象后必须调用其start()方法才能进行其他操作,而调用start()方法后相当于启动了线程,也就是run方法将会被调用,而我们从run源码中可以看出其执行了Looper.prepare()代码,这时Looper对象将被创建,当Looper对象被创建后将绑定在当前线程(也就是当前异步线程),这样我们才可以把Looper对象赋值给Handler对象,进而确保Handler对象中的handleMessage方法是在异步线程执行的。接着将执行代码:

synchronized (this) {
       mLooper = Looper.myLooper();
       notifyAll(); //唤醒等待线程
   }

 

  这里在Looper对象创建后将其赋值给HandlerThread的内部变量mLooper,并通过notifyAll()方法去唤醒等待线程,最后执行Looper.loop();代码,开启looper循环语句。那这里为什么要唤醒等待线程呢?我们来看看,getLooper方法

public Looper getLooper() {
 //先判断当前线程是否启动了
   if (!isAlive()) {
       return null; } // If the thread has been started, wait until the looper has been created. synchronized (this) { while (isAlive() && mLooper == null) { try { wait();//等待唤醒 } catch (InterruptedException e) { } } } return mLooper; }

  事实上可以看出外部在通过getLooper方法获取looper对象时会先先判断当前线程是否启动了,如果线程已经启动,那么将会进入同步语句并判断Looper是否为null,为null则代表Looper对象还没有被赋值,也就是还没被创建,此时当前调用线程进入等待阶段,直到Looper对象被创建并通过 notifyAll()方法唤醒等待线程,最后才返回Looper对象,之所以需要等待唤醒机制,是因为Looper的创建是在子线程中执行的,而调用getLooper方法则是在主线程进行的,这样我们就无法保障我们在调用getLooper方法时Looper已经被创建,到这里我们也就明白了在获取mLooper对象时会存在一个同步的问题,只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值,HandlerThread内部则通过等待唤醒机制解决了同步问题。

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; } 

 

  从源码可以看出当我们调用quit方法时,其内部实际上是调用Looper的quit方法而最终执行的则是MessageQueue中的removeAllMessagesLocked方法(Handler消息机制知识点),该方法主要是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送)还是非延迟消息。 
当调用quitSafely方法时,其内部调用的是Looper的quitSafely方法而最终执行的是MessageQueue中的removeAllFutureMessagesLocked方法,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理完成后才停止Looper循环,quitSafely相比于quit方法安全的原因在于清空消息之前会派发所有的非延迟消息。最后需要注意的是Looper的quit方法是基于API 1,而Looper的quitSafely方法则是基于API 18的。 
好~,到此对于HandlerThread的所有分析就到此完结。

Android 多线程之HandlerThread 完全详解 
Android 多线程之IntentService 完全详解



    本文转自 一点点征服   博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/8192210.html,如需转载请自行联系原作者


相关文章
|
4天前
|
API Android开发 iOS开发
深入探索Android与iOS的多线程编程差异
在移动应用开发领域,多线程编程是提高应用性能和响应性的关键。本文将对比分析Android和iOS两大平台在多线程处理上的不同实现机制,探讨它们各自的优势与局限性,并通过实例展示如何在这两个平台上进行有效的多线程编程。通过深入了解这些差异,开发者可以更好地选择适合自己项目需求的技术和策略,从而优化应用的性能和用户体验。
|
2月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
51 4
|
3月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
131 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
3月前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
在Android开发中,为应对复杂应用场景和繁重计算任务,多线程与异步编程成为保证UI流畅性的关键。本文将介绍Android中的多线程基础,包括Thread、Handler、Looper、AsyncTask及ExecutorService等,并通过示例代码展示其实用性。AsyncTask适用于简单后台操作,而ExecutorService则能更好地管理复杂并发任务。合理运用这些技术,可显著提升应用性能和用户体验,避免内存泄漏和线程安全问题,确保UI更新顺畅。
102 5
|
3月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
74 10
|
3月前
|
API Android开发 iOS开发
安卓与iOS开发中的线程管理对比
【9月更文挑战第12天】在移动应用的世界中,安卓和iOS平台各自拥有庞大的用户群体。开发者们在这两个平台上构建应用时,线程管理是他们必须面对的关键挑战之一。本文将深入探讨两大平台在线程管理方面的异同,通过直观的代码示例,揭示它们各自的设计理念和实现方式,帮助读者更好地理解如何在安卓与iOS开发中高效地处理多线程任务。
|
3月前
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。
|
4月前
|
调度 Android开发 开发者
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
62 4
|
4月前
|
安全 网络安全 数据安全/隐私保护
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享安卓与iOS开发中的线程管理比较
【8月更文挑战第30天】本文将探讨网络安全与信息安全的重要性,并分享关于网络安全漏洞、加密技术和安全意识的知识。我们将了解常见的网络攻击类型和防御策略,以及如何通过加密技术和提高安全意识来保护个人和组织的信息安全。
|
4月前
|
Java 测试技术 Android开发
Android项目架构设计问题之构造一个Android中的线程池如何解决
Android项目架构设计问题之构造一个Android中的线程池如何解决
31 0