前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android多线程:手把手带你源码解析HandlerThread

Android多线程:手把手带你源码解析HandlerThread

作者头像
Carson.Ho
发布2022-03-25 09:21:00
3320
发布2022-03-25 09:21:00
举报
文章被收录于专栏:Android知识分享Android知识分享

前言

  • 多线程的应用在Android开发中是非常常见的,常用方法主要有:
    1. 继承Thread类
    2. 实现Runnable接口
    3. Handler
    4. AsyncTask
    5. HandlerThread
    6. IntentService
  • 今天,我将全面解析多线程中 HandlerThread的源码

Carson带你学多线程系列 基础汇总 Android多线程:你必须要了解的多线程基础知识汇总 基础使用 Android多线程:继承Thread类 使用解析(含实例教程) Android多线程:实现Runnable接口 使用解析(含实例教程) 复合使用 Android多线程:AsyncTask使用教程(含实例讲解) Android多线程:深入AsyncTask的原理、源码分析 Android多线程:手把手教你使用HandlerThread Android多线程:这是一份详细的HandlerThread源码分析攻略 Android多线程:IntentService使用教程(含实例讲解) Android多线程:这是一份全面 & 详细的IntentService源码分析指南 Android多线程:线程池ThreadPool全方位教学 相关使用 Android异步通信:这是一份全面&详细的Handler机制学习攻略 Carson带你学Java:手把手教你全面学习神秘的Synchronized关键字 Carson带你学Java:带你了解神秘的线程变量 ThreadLocal

目录

1. 简介

2. 工作原理

内部原理 = Thread类 + Handler类机制,即:

  • 通过继承Thread类,快速地创建1个带有**Looper**对象的新工作线程
  • 通过封装Handler类,快速创建**Handler** & 与其他线程进行通信

3. 源码分析

  • 本次源码分析将根据 HandlerThread的使用步骤讲解

若不熟悉,请务必看文章Android多线程:手把手教你使用HandlerThread

  • HandlerThread的使用步骤有5个:
代码语言:javascript
复制
// 步骤1:创建HandlerThread实例对象
// 传入参数 = 线程名字,作用 = 标记该线程
   HandlerThread mHandlerThread = new HandlerThread("handlerThread");

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

// 步骤3:创建工作线程Handler & 复写handleMessage()
// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
  Handler workHandler = new Handler( handlerThread.getLooper() ) {
            @Override
            public boolean handleMessage(Message msg) {
                ...//消息处理
                return true;
            }
        });

// 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
// 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
  // a. 定义要发送的消息
  Message msg = Message.obtain();
  msg.what = 2; //消息的标识
  msg.obj = "B"; // 消息的存放
  // b. 通过Handler发送消息到其绑定的消息队列
  workHandler.sendMessage(msg);

// 步骤5:结束线程,即停止线程的消息循环
  mHandlerThread.quit();
  • 下面,我将根据上述使用步骤进行源码分析

步骤1:创建HandlerThread的实例对象

代码语言:javascript
复制
/**
  * 具体使用
  * 传入参数 = 线程名字,作用 = 标记该线程
  */ 
   HandlerThread mHandlerThread = new HandlerThread("handlerThread");

/**
  * 源码分析:HandlerThread类的构造方法
  */ 
	public class HandlerThread extends Thread {
	// 继承自Thread类
	    
	    int mPriority; // 线程优先级
	    int mTid = -1; // 当前线程id
	    Looper mLooper; // 当前线程持有的Looper对象

	   // HandlerThread类有2个构造方法
	   // 区别在于:设置当前线程的优先级参数,即可自定义设置 or 使用默认优先级

		    // 方式1. 默认优先级
		    public HandlerThread(String name) {
		        // 通过调用父类默认的方法创建线程
		        super(name);
		        mPriority = Process.THREAD_PRIORITY_DEFAULT;
		    }
		  
		    // 方法2. 自定义设置优先级
		    public HandlerThread(String name, int priority) {
		        super(name);
		        mPriority = priority;
		    }
		    ...
     }

总结

  • HandlerThread类继承自Thread
  • 创建HandlerThread类对象 = 创建Thread类对象 + 设置线程优先级 = 新开1个工作线程 + 设置线程优先级

步骤2:启动线程

代码语言:javascript
复制
/**
  * 具体使用
  */ 
   mHandlerThread.start();

/**
  * 源码分析:此处调用的是父类(Thread类)的start(),最终回调HandlerThread的run()
  */ 
  @Override
    public void run() {
        // 1. 获得当前线程的id
        mTid = Process.myTid();

        // 2. 创建1个Looper对象 & MessageQueue对象
        Looper.prepare();

        // 3. 通过持有锁机制来获得当前线程的Looper对象
        synchronized (this) {
            mLooper = Looper.myLooper();
           
            // 发出通知:当前线程已经创建mLooper对象成功
            // 此处主要是通知getLooper()中的wait()
            notifyAll();
            
            // 此处使用持有锁机制 + notifyAll() 是为了保证后面获得Looper对象前就已创建好Looper对象
        }

        // 4. 设置当前线程的优先级
        Process.setThreadPriority(mPriority);

        // 5. 在线程循环前做一些准备工作 ->>分析1
        // 该方法实现体是空的,子类可实现 / 不实现该方法
        onLooperPrepared();

        // 6. 进行消息循环,即不断从MessageQueue中取消息 & 派发消息
        Looper.loop();

        mTid = -1;
    }
}

/**
  * 分析1:onLooperPrepared();
  * 说明:该方法实现体是空的,子类可实现 / 不实现该方法
  */ 
    protected void onLooperPrepared() {

    }

总结

  1. 为当前工作线程(即步骤1创建的线程)创建1个Looper对象 & MessageQueue对象
  2. 通过持有锁机制来获得当前线程的Looper对象
  3. 发出通知:当前线程已经创建mLooper对象成功
  4. 工作线程进行消息循环,即不断从MessageQueue中取消息 & 派发消息

步骤3:创建工作线程Handler & 复写handleMessage()

代码语言:javascript
复制
/**
  * 具体使用
  * 作用:将Handler关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
  * 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
  */ 
   Handler workHandler = new Handler( handlerThread.getLooper() ) {
            @Override
            public boolean handleMessage(Message msg) {
                ...//消息处理
                return true;
            }
        });

/**
  * 源码分析:handlerThread.getLooper()
  * 作用:获得当前HandlerThread线程中的Looper对象
  */ 
    public Looper getLooper() {
        // 若线程不是存活的,则直接返回null
        if (!isAlive()) {
            return null;
        } 
        // 若当前线程存活,再判断线程的成员变量mLooper是否为null
        // 直到线程创建完Looper对象后才能获得Looper对象,若Looper对象未创建成功,则阻塞
        synchronized (this) {
  
      
            while (isAlive() && mLooper == null) {
                try {
     				// 此处会调用wait方法去等待
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        // 上述步骤run()使用 持有锁机制 + notifyAll()  获得Looper对象后
        // 则通知当前线程的wait()结束等待 & 跳出循环
        // 最终getLooper()返回的是在run()中创建的mLooper对象
        return mLooper;
    }

总结

  • 在获得HandlerThread工作线程的Looper对象时存在一个同步的问题:只有当线程创建成功 & 其对应的Looper对象也创建成功后才能获得Looper的值,才能将创建的Handler 与 工作线程的Looper对象绑定,从而将Handler绑定工作线程
  • 解决方案:即保证同步的解决方案 = 同步锁、wait()notifyAll(),即 在run()中成功创建Looper对象后,立即调用notifyAll()通知 getLooper()中的wait()结束等待 & 返回run()中成功创建的Looper对象,使得Handler与该Looper对象绑定

步骤4:使用工作线程Handler向工作线程的消息队列发送消息

代码语言:javascript
复制
/**
  * 具体使用
  * 作用:在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
  * 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
  */ 
  // a. 定义要发送的消息
  Message msg = Message.obtain();
  msg.what = 2; //消息的标识
  msg.obj = "B"; // 消息的存放
  // b. 通过Handler发送消息到其绑定的消息队列
  workHandler.sendMessage(msg);

/**
  * 源码分析:workHandler.sendMessage(msg)
  * 此处的源码即Handler的源码,故不作过多描述
  */ 

步骤5:结束线程,即停止线程的消息循环

代码语言:javascript
复制
/**
  * 具体使用
  */ 
  mHandlerThread.quit();

/**
  * 源码分析:mHandlerThread.quit()
  * 说明:
  *     a. 该方法属于HandlerThread类
  *     b. HandlerThread有2种让当前线程退出消息循环的方法:quit() 、quitSafely()
  */ 
    
  // 方式1:quit() 
  // 特点:效率高,但线程不安全
  public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit(); 
            return true;
        }
        return false;
    }

  // 方式2:quitSafely()
  // 特点:效率低,但线程安全
  public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

  // 注:上述2个方法最终都会调用MessageQueue.quit(boolean safe)->>分析1

/**
  * 分析1: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(); // 方式1(不安全)会调用该方法 ->>分析2
	        } else {
	            removeAllMessagesLocked(); // 方式2(安全)会调用该方法 ->>分析3
	        }
	        // We can assume mPtr != 0 because mQuitting was previously false.
	        nativeWake(mPtr);
	    }
	}
/**
  * 分析2:removeAllMessagesLocked()
  * 原理:遍历Message链表、移除所有信息的回调 & 重置为null
  */ 
  private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}
/**
  * 分析3:removeAllFutureMessagesLocked() 
  * 原理:先判断当前消息队列是否正在处理消息
  *      a. 若不是,则类似分析2移除消息
  *      b. 若是,则等待该消息处理处理完毕再使用分析2中的方式移除消息退出循环
  * 结论:退出方法安全与否(quitSafe() 或 quit()),在于该方法移除消息、退出循环时是否在意当前队列是否正在处理消息
  */ 
  private void removeAllFutureMessagesLocked() {

    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;

    if (p != null) {
        // 判断当前消息队列是否正在处理消息
        // a. 若不是,则直接移除所有回调
        if (p.when > now) {
            removeAllMessagesLocked();
        } else {
        // b. 若是正在处理,则等待该消息处理处理完毕再退出该循环
            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);
        }
    }
}

至此,关于HandlerThread源码的分析完毕。

4. 总结

  • 本文全面分析了多线程中HandlerThread的源码,总结如下
  • Carson带你学多线程系列

基础汇总

Android多线程:多线程基础知识汇总

基础使用

Android多线程:继承Thread类使用(含实例教程)

Android多线程:实现Runnable接口使用(含实例教程)

复合使用

Android多线程:AsyncTask使用教程(含实例讲解)

Android多线程:AsyncTask的原理及源码分析

Android多线程:HandlerThread使用教程(含实例讲解)

Android多线程:HandlerThread的原理及源码分析

Android多线程:IntentService使用教程(含实例讲解)

Android多线程:IntentService的原理及源码分析

Android多线程:线程池ThreadPool全方位教学

相关使用

Android异步通信:这是一份全面&详细的Handler机制学习攻略

Android多线程:手把手教你全面学习神秘的Synchronized关键字

Android多线程:带你了解神秘的线程变量 ThreadLocal

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 目录
  • 1. 简介
  • 2. 工作原理
  • 3. 源码分析
    • 步骤1:创建HandlerThread的实例对象
      • 步骤2:启动线程
        • 步骤3:创建工作线程Handler & 复写handleMessage()
          • 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
            • 步骤5:结束线程,即停止线程的消息循环
            • 4. 总结
            相关产品与服务
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档