Handler系列之原理分析
上一节我们讲解了Handler的基本使用方法,也是平时大家用到的最多的使用方式。那么本节让我们来学习一下Handler的工作原理吧!!!
我们知道Android中我们只能在ui线程(主线程)更新ui信息,那么你们知道为什么只能通过Handler机制更新ui吗?其实最根本的目的就是解决多线程并发的问题。
假设在一个Activity中有多个线程去更新ui,并且都没有加锁,那么会是什么样子?
导致的结果就是更新界面错乱。
如果对更新ui的操作都进行加锁处理的话又产生什么问题哪?
性能下降。
处于对以上问题的考虑,Android给我们提供了一套更新ui的机制,我们只需要遵守这样的机制就可以了。根本不用去关心多线程问题,因为所有更新ui的操作,都是在主线程的消息队列当中通过轮训处理的。
<一>Handler机制的角色和职责
1 MessageQueue 消息队列
存储消息的容器,可以向其中添加、取出消息。遵循先进先出的原则。
2 Handler
负责将消息发向消息容器即MessageQueue中。
3 Looper 轮训器
通过调用自身的loop方法,不断的从消息队列当中取出消息并发送给target(即handler)处理消息。当消息队列当中没有轮训消息时,它就处于堵塞状态。
来个实际图来看一下Handler的工作原理:

<二>Handler机制工作原理分析
Handler机制要想起作用有三个步骤:
1 创建Looper
2 创建Handler
3 调用Looper的loop方法,循环消息
下面让我们来看看android中,如何去遵循这三点的,在那之前,先普及一下一个知识:
默认整个应用程序,都是通过ActivityThread类启动的,在ActivityThread类当中,负责创建我们所有的Activity,并回调每个Activity中的生命周期方法。在ActivityThread类中,默认会去创建一个线程,这个线程叫做main线程(主线程)。所有的应用程序,更新ui的操作,都是在这个main线程中进行的。
创建Looper和调用loop方法的工作,Android SDK 已经为我们做好了,所以我们在平时使用的时候,只需要创建Handler并发送消息即可。下面我们跟随Android源码看看它是怎么做的。入口是ActivityThread的main方法。

跟进Looper的prepareMainLooper方法

跟进prepare方法

这里我们需要对ThreadLocal类进行一下解释,ThreadLocal在我们的线程当中用于去保存一些变量信息,默认情况下,创建一个与线程相关的一个对象,是通过threadLocal存储的,threadLocal有set和get方法,set是把变量设置到threadLocal当中 ,get方法是获取出来。因为当前线程是ui线程,默认情况下threadLocal是没有存储的,所以为null,所以不走if而是new Looper对象之后在存储,下面我们在看看初始化Looper的时候做了哪些事情!

我们看到,在创建Looper轮训器的时候,自动的创建了消息队列MessageQuene。也就是说默认的情况下,android为我们自动创建了主线程的Looper和MessageQuene。
那么Handler怎么和我们的MessageQuene消息队列联系在一起的那?因为之前不是说handler发出的消息是发送到消息队列中了吗?
原因还要看我们在创建Handler的时候做了那些事情,跟进Handler初始化源码发现最终调用的是下面这个构造器创建实例的。
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
跟进Looper的myLooper方法
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
看到了什么?sThreadLocal是不是很熟悉,没错它就是ThreadLocal对象。默认情况下android为我们创建了主线程Looper对象并存储在sThreadLocal中,所以此处返回的就是主线程的Looper对象,也就是说我们在创建Handler的时候,它就和消息队列关联起来了。
那么当我们使用handler发送消息的时候,不管使用哪一种方法,一步一步跟进源码发现最终调用的都是Handler的sendMessageAtTime方法
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
看代码可知,在发送消息的时候消息队列不能为null,继续跟进enqueueMessage方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到,消息最终发送到了消息队列当中。那么消息是怎么轮训的那?前面已经提过,是通过Looper的loop方法,那么来看看吧!!!

可以看到loop方法里面的机制就是一个死循环,不断的从消息队列中取出消息,然后发送给target(handler对象)的dispatchMessage方法,跟进去!!!

一般情况下我们发送消息的时候没有给Message的callback赋值,所以第一个if条件不满足。下面的mCallback是在我们初始化Handler的时候才被初始化,Handler初始化有一种方法Handler(Callback callback),此处的参数就是给mCallback赋值的。我们一般初始化Handler的时候使用的是空参数的构造器,所以导致mCallback未被初始化,所以会直接走handleMessage(msg)方法,也就是我们初始化Handler时重写的handleMessage方法。至此,Handler工作的机制就开始工作了,你、了解了吗?
下面让我们看看如果我们选择的是带Callback参数的初始化方式逻辑又会是什么样那,请看初始化代码:
Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.d(TAG,"callback参数-------handleMessage");
return true;//此处的返回值会影响下面的handleMessage方法是否调用
//false 调用
//true 不调用
} }){
@Override
public void handleMessage(Message msg) {
Log.d(TAG,"重写handler的-------handleMessage方法");
super.handleMessage(msg);
}
};
根据上面的源码分析我们知道此处Callback参数中的handleMessage方法的返回值会影响到下面第二个handleMessage方法是否调用。经过验证,return true 则不调用 ,return false则调用。
最会通过一张图,看一下Handler的原理:

更形象一点,可以看下图:

好了,Android中的Handler机制工作原理我已经介绍完毕!!!参考了幕课网中《Android面试常客Handler详解》,大家如果没有明白可以去该网站自行学习。下篇我将介绍如何在子线程创建Handler。
Handler系列之原理分析的更多相关文章
- 【转载】Android 的 Handler 机制实现原理分析
handler在安卓开发中是必须掌握的技术,但是很多人都是停留在使用阶段.使用起来很简单,就两个步骤,在主线程重写handler的handleMessage( )方法,在工作线程发送消息.但是,有没有 ...
- [转]Handler MessageQueue Looper消息循环原理分析
Handler MessageQueue Looper消息循环原理分析 Handler概述 Handler在Android开发中非常重要,最常见的使用场景就是在子线程需要更新UI,用Handler ...
- Handler 原理分析和使用(二)
在上篇 Handler 原理分析和使用(一)中,介绍了一个使用Handler的一个简单而又常见的例子,这里还有一个例子,当然和上一篇的例子截然不同,也是比较常见的,实例如下. import andro ...
- Handler 原理分析和使用(一)
我为什么写Handler,原因主要还在于它在整个 Android 应用层面非常之关键,他是线程间相互通信的主要手段.最为常用的是其他线程通过Handler向主线程发送消息,更新主线程UI. 下面是一个 ...
- Handler 原理分析和使用之HandlerThread
前面已经提到过Handler的原理以及Handler的三种用法.这里做一个非常简单的一个总结: Handler 是跨线程的Message处理.负责把Message推送到MessageQueue和处理. ...
- java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析
java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...
- java基础解析系列(七)---ThreadLocal原理分析
java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...
- Eureka 系列(05)消息广播(上):消息广播原理分析
Eureka 系列(05)消息广播(上):消息广播原理分析 [TOC] 0. Spring Cloud 系列目录 - Eureka 篇 首先回顾一下客户端服务发现的流程,在上一篇 Eureka 系列( ...
- Java入门系列之线程池ThreadPoolExecutor原理分析思考(十五)
前言 关于线程池原理分析请参看<http://objcoding.com/2019/04/25/threadpool-running/>,建议对原理不太了解的童鞋先看下此文然后再来看本文, ...
随机推荐
- 启动了VSAN服务的主机不在vCenter集群中
背景: 这个问题的来源是,某用户将该ESXi主机直接夺取到另一个vCenterA的管辖中,而这个vCenterA中集群A开启了VSAN功能,导致再次反向夺取到vCenterB中的时候带有了来自于集群A ...
- WPF 有用博客地址
增加智能感知的RichTextBox扩展控件(WPF) WPF自定义控件与样式(3)-TextBox & RichTextBox & PasswordBox样式.水印.Label标签. ...
- 开发者接入 基本配置 服务器配置 out.aspx
页面代码: 前段为默认的,什么都不用写,后台如下: 即可 来自为知笔记(Wiz)
- webapp应用--模拟电子书翻页效果
前言: 现在移动互联网发展火热,手机上网的用户越来越多,甚至大有超过pc访问的趋势.所以,用web程序做出仿原生效果的移动应用,也变得越来越流行了.这种程序也就是我们常说的单页应用程序,它也有一个英文 ...
- jQuery的属性
The Write Less , Do More ! jQuery的属性 1. attr(name|properties|key,value|fn) : 设置或返回被选元素的属性值 ①获取属性 < ...
- 代码的坏味道(14)——重复代码(Duplicate Code)
坏味道--重复代码(Duplicate Code) 重复代码堪称为代码坏味道之首.消除重复代码总是有利无害的. 特征 两个代码片段看上去几乎一样. 问题原因 重复代码通常发生在多个程序员同时在同一程序 ...
- css样式之border
border用法详解: 1.border-width 属性设置边框的宽度 可能的值:像素 2.border-style 属性设置边框的样式 可能的值:solid(直线),dashed(虚线),dott ...
- Android 解析XML文件和生成XML文件
解析XML文件 public static void initXML(Context context) { //can't create in /data/media/0 because permis ...
- bootstrap
访问Bootstrap中文网,下载bootstrap中文文档,选择用于生产环境的bootstrap. 在官网使用ctrl+f查找想要的内容. 这里记一下Visual Studio Code软件的用法: ...
- ASP.NET Aries JSAPI 文档说明:AR.Utility
AR.Utility 文档 1:方法: 名称 说明 queryString function (key) *模拟.NET的Request对象 stringFormat function (str, a ...