2019独角兽企业重金招聘Python工程师标准>>>

博客:

https://www.jianshu.com/u/e9cb5fd3aaa8

声明:本文由作者 zho007 授权发布,未经原作者允许请勿转载

看到上面蓝色字了吗,点下吧

前言

在安卓当中提供了异步消息处理机制的两种方式来解决线程之间的通信,一种是是AsynchTask,另外一种就是现在我们主要分析的Handler。

Handler是Android类库提供的用于接受、传递和处理消息或Runnable对象的处理类,它结合Message、MessageQueue和Looper类以及当前线程实现了一个消息循环机制,用于实现任务的异步加载和处理。

简单使用分析

总所周知,安卓中子线程是不能更新UI的,如果在子线程更新,那么程序就会崩溃,那么这时候我们就使用到了handler,子线程操作完成通知主线程更新UI。我们先看下handler机制的分析图,和架构图:


  • Looper有一个MessageQueue消息队列;

  • MessageQueue有一组待处理的Message;

  • Message中有一个用于处理消息的Handler;

  • Handler中有Looper和MessageQueue。

一个Handler对应一个Looper对象,一个Looper对应一个MessageQueue对象,使用Handler生成Message,所生成的Message对象的Target属性,就是该Handler对象。而一个Handler可以生成多个Message,所以说,Handler和Message是一对多的关系。

Android中主线程向子线程发送消息

1. 创建Handler

在安卓的ui线程中创建一个Handler

 Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);Log.e("TAG", "当前线程: " + Thread.currentThread().getName() + " 收到消息:" + msg.obj);}};

2. 开启子线程向主线程的handler发送消息

bt.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {new Thread(new Runnable() {@Overridepublic void run() {Message obtain = Message.obtain();obtain.obj = Thread.currentThread().getName() + ": 发送消息";handler.sendMessage(obtain);}}).start();}
});

3. 结果分析

结果:

当前线程: main  消息:Thread-4: 发送消息

结果说明我们在主线程中创建handler,然后点击按钮子线程向主线程发送消息成功

分析:

我们按照刚才的流程图来 分析一遍。

首先我们在主线程创建了一个Handler,那么在主线程中也会对应有一个Looper对象在轮询消息,一个Looper对象有一个MessageQueue,于是主线程中也有一个MessageQueue。我们的流程就是主线程中looper对象在一直轮询消息,如果消息队列中没有任何消息的话,那么当前线程暂时阻塞,直到子线程中获取handler对象发送消息,这时候,handler会对主线程中的MessageQueue中添加消息,当消息添加成功时,将阻塞的线程唤醒。于是looper轮询到有新消息,将新消息返回给handler对象,因为handler对象是在主线程中创建,所以消息将会在主线程中显示。

这个流程和生产者消费者模型有一点相似,一个线程生成消息,一个线程消费消息。所以在MessageQueue中的添加消息,和消费消息都会有一把锁。将这两个方法锁住;首先避免多个线程同时操作消息列队,和避免再写入消息的时候读取消息,导致消息错乱的问题。如下图,MessageQueue源码中锁住当前对象:

  • 也许这里有些难懂,但是没关系,我们继续向下分析

子线程向主线程发送消息

上面我们操作了子线程向主线线程发送消息,接下来我们使用handler主线程向子线程发送消息。

1. 子线程中创建handler对象。

2. 为当前子线程创建一个looper对象。(这里我们使用ThreadLocal来保存Looper副本)

3. 开启子线程looper轮询消息

    new Thread(new Runnable() {@Overridepublic void run() {//对当前线程创建一个looper副本Looper.prepare();handler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);Log.e("TAG", "当前线程: " + Thread.currentThread().getName() + " 收到消息:" + msg.obj);}};//开启轮询消息Looper.loop();}}).start();

4. 主线程向子线程发送消息

 Message obtain = Message.obtain();
obtain.obj = Thread.currentThread().getName() + "线程发送的消息";
handler.sendMessage(obtain);

5. 结果分析

结果如下:

当前线程: Thread-4  收到消息:main线程发送的消息

这时候我们就成功重主线程发送了一条消息给子线程

分析:

我们重上面代码注意到相比子线程发送消息给主线程我们主线程发送消息给子线程多了两行代码:

1.Looper.prepare();

我们翻阅源码如下:

private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}sThreadLocal.set(new Looper(quitAllowed));
}

这里我们使用了一个ThreadLocal来保存每一个接收线程中的Looper对象副本。由于子线程是我们手动开启的线程,所以我们要初始化一个looper副本。由于安卓主线程中,安卓系统自动维护了一个安卓主线程的looper对象副本并让looper轮询着消息。

2.Looper.loop();

我们翻阅源码如下:

 public static void loop() {//获取looper对象final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}//获取messageQueue对象final MessageQueue queue = me.mQueue;...............//轮询消息for (;;) {//轮询messageQueue中的消息,没有消息就再这里阻塞。Message msg = queue.next();if (msg == null) {// No message indicates that the message queue isreturn;}...........try {//发送消息msg.target.dispatchMessage(msg);............} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}............}}

这里安卓系统也是在主线程中轮询着消息。

手写一套handler。

我们经过上面的使用和简单分析了以后,也许还是有一些懵逼。所以下面我们自己通过生产者/消费者模型来模仿安卓Handler手写一套。代码如下:

Handler

public class Handler {private Looper mLooper;private MessageQueue mQueue;public Handler() {//获取当前线程的loopermLooper = Looper.myLooper();//获取当前线程的消息列队mQueue = mLooper.messageQuene;}/*** 发送消息* @param message*/public void sendMessage(Message message) {message.target = this;mQueue.enqueueMessage(message);}/*** 处理消息* @param message*/public void handleMessage(Message message) {}/*** 分发消息* @param message*/public void dispatchMessage(Message message) {handleMessage(message);}
}

Looper

public class Looper {final MessageQueue messageQuene;private static ThreadLocal<Looper> threadLocal = new ThreadLocal<>();public Looper() {messageQuene = new MessageQueue();}/*** 为当前线程初始化一个looper副本对象*/public static void prepare() {if (threadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}threadLocal.set(new Looper());System.out.println("looper初始化");}/*** 获取当前线程的looper副本对象** @return*/public static Looper myLooper() {return threadLocal.get();}/*** 轮询消息*/public static void loop() {//获取当前线程的looper对象Looper me = myLooper();Message msg;//开始轮询消息for (; ; ) {//轮询消息,没有消息就阻塞msg = me.messageQuene.next();if (msg == null || msg.target == null) {System.out.println("Looper:" + "空消息");continue;}System.out.println("Looper:" + "looper轮询到了消息,发送消息");//轮询到了消息分发消息msg.target.dispatchMessage(msg);}}
}

Message

public class Message {//发送的消息public Object obj;//目标Handlerpublic Handler target;@Overridepublic String toString() {return obj.toString();}
}

MessageQueue

要实现生产者/消费者模型,首先的有锁,这里使用ReentrantLock 
主要考虑的重写入,它可以根据设定的变量来唤醒不同类型的锁,也就是说当我们队列有数据时,我们需要唤醒read锁;当队列有空间时,我们需要唤醒写锁。

public class MessageQueue {Message[] mItems;int mPutIndex;//队列中消息数private int mCount;private int mTakeIndex;//锁Lock mLock;//唤醒,沉睡某个线程操作Condition getCondition;//可取Condition addCondition;//可添加public MessageQueue() {mItems = new Message[50];mLock = new ReentrantLock();getCondition = mLock.newCondition();addCondition = mLock.newCondition();}/*** 消息队列取消息 出队** @return*/Message next() {Message msg = null;try {mLock.lock();//检查队列是否空了while (mCount <= 0) {//阻塞System.out.println("MessageQueue:" + "队列空了,读锁阻塞");getCondition.await();}msg = mItems[mTakeIndex];//可能空//消息被处理后,置空数组中该项mItems[mTakeIndex] = null;//处理越界,index大于数组容量时,取第一个itemmTakeIndex = (++mTakeIndex >= mItems.length) ? 0 : mTakeIndex;mCount--;//通知生产者生产addCondition.signalAll();} catch (InterruptedException e) {e.printStackTrace();} finally {mLock.unlock();}return msg;}/*** 添加消息进队列** @param message*/public void enqueueMessage(Message message) {try {mLock.lock();//检查队列是否满了while (mCount >= mItems.length) {//阻塞System.out.println("MessageQueue:" + "队列空了,写锁阻塞");addCondition.await();}mItems[mPutIndex] = message;//处理越界,index大于数组容量时,替换第一个itemmPutIndex = (++mPutIndex >= mItems.length) ? 0 : mPutIndex;mCount++;//通知消费者消费getCondition.signalAll();} catch (InterruptedException e) {e.printStackTrace();} finally {mLock.unlock();}}
}

测试:

public class Test {public static Handler handler;public static void main(String[] args) throws InterruptedException {new Thread(new Runnable() {@Overridepublic void run() {Looper.prepare();handler = new Handler() {@Overridepublic void handleMessage(Message message) {super.handleMessage(message);System.out.println("Test:" + Thread.currentThread().getName() + "线程接收到:" + message.obj);}};Looper.loop();}}).start();//睡0.5s,保证上面的线程中looper初始化好了Thread.sleep(500);new Thread(() -> {Message message = new Message();message.obj = Thread.currentThread().getName() + "发送的消息 ";handler.sendMessage(message);}).start();new Thread(() -> {Message message = new Message();message.obj = Thread.currentThread().getName() + "发送的消息 ";handler.sendMessage(message);}).start();}
}

结果分析

结果:

looper初始化
MessageQueue:队列空了,读锁阻塞
Looper:looper轮询到了消息,发送消息
Test:Thread-0线程接收到:Thread-1发送的消息
Looper:looper轮询到了消息,发送消息
Test:Thread-0线程接收到:Thread-2发送的消息
MessageQueue:队列空了,读锁阻塞

分析:

到这里我们的手写的一套Handler就完成了。自己手写一次handler消息处理机制,再回过头来看看handler是不是很简单了,再也不怕面试中被问到。当然android源码中的handler处理机制移值到C层处理了.

demo地址:https://gitee.com/zhongjiabao/Demo.git

转载于:https://my.oschina.net/u/1177694/blog/1635167

Handler深入(分析源码,手写一套Handler)相关推荐

  1. 了解mybatis源码手写mybatis

    一:mybatis概述 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis ...

  2. Pytorch二维卷积 conv2d 使用/源码/手写实现conv2d/手写向量内积实现conv2d/转置卷积实现——学习笔记

    这里是一份学习笔记- 学习视频点这里

  3. bytebuddy实现原理分析 源码分析 (三)- advice 详解

    advice详解 八.advice 8.1 AsmVisitorWrapper 8.1.1 ForDeclareFields 8.1.1.1 Entry 8.1.1.2 DispatchingVisi ...

  4. easyloader [easyui_1.4.2] 分析源码,妙手偶得之

    用easyui很久了,但是很少去看源码. 有解决不了的问题就去百度... 今日发现,easyui的源码不难懂. 而且结合 easyloader 可以非常方便的逐个研究easyui的组件. 但是, ea ...

  5. python 抓取解析接口数据_[干货]用python抓取摩拜单车API数据并做可视化分析(源码)...

    原标题:[干货]用python抓取摩拜单车API数据并做可视化分析(源码) 在APP中能看到很多单车,但走到那里的时候,才发现车并不在那里.有些车不知道藏到了哪里:有些车或许是在高楼的后面,由于有GP ...

  6. Flink源码分析 - 源码构建

    本篇文章首发于头条号Flink源码分析 - 源码构建,欢迎关注我的头条号和微信公众号"大数据技术和人工智能"(微信搜索bigdata_ai_tech)获取更多干货,也欢迎关注我的C ...

  7. android 自定义相机源码,Android 自定义相机及分析源码

    Android 自定义相机及分析源码 使用Android 系统相机的方法: 要想让应用有相机的action,咱们就必须在清单文件中做一些声明,好让系统知道,如下 action的作用就是声明action ...

  8. 帝国CMS7.5仿金色财经整站源码+手机端+会员中心+投稿-财经综合门户

    简介: 帝国CMS7.5仿<金色财经>2020新版整站源码+手机端+会员中心+投稿-财经综合门户 内无安装说明,和后台密码,真正懂帝国cms的下载去安装. 网盘下载地址: http://k ...

  9. c# MODBUS协议源码 上/下位机源码烧写Flash工具

    c# MODBUS协议源码 上/下位机源码烧写Flash工具 包含: 1.C#上位机源码 2.上位机源码包含MODBUS协议源码 3.下位机源码 下位机源码采用STM32F10x芯片 的uC/OS-I ...

  10. bytebuddy实现原理分析 源码分析 (二)

    bytebuddy实现原理分析 &源码分析 四.字节码的操作(implementation ) pkg 4.1 bytecode :pkg 4.1.1 StackManipulation :c ...

最新文章

  1. centos7 安装apache+php+memcache
  2. 【实用】CTS请求号传输报错处理
  3. 云炬60s看世界20211122
  4. sql 倒数第二个_小白初探SQL(一)
  5. ESL:我们如何使用首云混合云产品实现提效降本
  6. Jeecg-Boot上传附件异常问题处理
  7. python牛顿法解非线性方程组_萌新请教牛顿法求解三元非线性方程组
  8. 华为管理学案例分析_管理学论文5000字如何高质量写作
  9. [码海拾贝 之TC] 使用View 定义动态的Class
  10. 《量子计算机简史》--摘
  11. python新手常犯的17个错误
  12. web基础知识梳理(笔记)
  13. Java SPI机制简介
  14. 在eclipse环境中配置hadoop开发环境遇到的问题
  15. HUAWEI HiCar让华为手机用户中的宝马车主Hi起来!
  16. 战神笔记本电脑自带access吗_战神GX9系列超享windows10搭载微软office2016
  17. html页面拼接,表格数据
  18. 南昌治疗糖尿病的专科医院
  19. hexo博客设置域名
  20. JPEG2000压缩DICOM文件的解压(三)

热门文章

  1. 数仓系列 | Flink 窗口的应用与实现
  2. 技术要扎扎实实的做,业余功夫也要修炼
  3. bpython3 推送_python3对接聊天机器人API
  4. win10删除开机密码_教你电脑如何设置开机密码_win10教程
  5. 第一周练习代码以及备注
  6. linux shell脚本读取配置文件 val=1,shell脚本
  7. python中bool怎么用_python函数之bool([x])用法详解
  8. Linux笔记 -- Pyhton虚拟环境Linux系统基本操作Vim编辑器基本操作
  9. Ajax_jquery库中Ajax方法的使用
  10. Confluence 6 配置草稿保存的时间