上一节我们讲解了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系列之原理分析的更多相关文章

  1. 【转载】Android 的 Handler 机制实现原理分析

    handler在安卓开发中是必须掌握的技术,但是很多人都是停留在使用阶段.使用起来很简单,就两个步骤,在主线程重写handler的handleMessage( )方法,在工作线程发送消息.但是,有没有 ...

  2. [转]Handler MessageQueue Looper消息循环原理分析

    Handler MessageQueue Looper消息循环原理分析   Handler概述 Handler在Android开发中非常重要,最常见的使用场景就是在子线程需要更新UI,用Handler ...

  3. Handler 原理分析和使用(二)

    在上篇 Handler 原理分析和使用(一)中,介绍了一个使用Handler的一个简单而又常见的例子,这里还有一个例子,当然和上一篇的例子截然不同,也是比较常见的,实例如下. import andro ...

  4. Handler 原理分析和使用(一)

    我为什么写Handler,原因主要还在于它在整个 Android 应用层面非常之关键,他是线程间相互通信的主要手段.最为常用的是其他线程通过Handler向主线程发送消息,更新主线程UI. 下面是一个 ...

  5. Handler 原理分析和使用之HandlerThread

    前面已经提到过Handler的原理以及Handler的三种用法.这里做一个非常简单的一个总结: Handler 是跨线程的Message处理.负责把Message推送到MessageQueue和处理. ...

  6. java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析

    java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...

  7. java基础解析系列(七)---ThreadLocal原理分析

    java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...

  8. Eureka 系列(05)消息广播(上):消息广播原理分析

    Eureka 系列(05)消息广播(上):消息广播原理分析 [TOC] 0. Spring Cloud 系列目录 - Eureka 篇 首先回顾一下客户端服务发现的流程,在上一篇 Eureka 系列( ...

  9. Java入门系列之线程池ThreadPoolExecutor原理分析思考(十五)

    前言 关于线程池原理分析请参看<http://objcoding.com/2019/04/25/threadpool-running/>,建议对原理不太了解的童鞋先看下此文然后再来看本文, ...

随机推荐

  1. 使用HTML5开发Kinect体感游戏

    一.简介 我们要做的是怎样一款游戏? 在前不久成都TGC2016展会上,我们开发了一款<火影忍者手游>的体感游戏,主要模拟手游章节<九尾袭来 >,用户化身四代,与九尾进行对决, ...

  2. C++内存对齐总结

    大家都知道,C++空类的内存大小为1字节,为了保证其对象拥有彼此独立的内存地址.非空类的大小与类中非静态成员变量和虚函数表的多少有关. 而值得注意的是,类中非静态成员变量的大小与编译器内存对齐的设置有 ...

  3. 百度MIP页规范详解 —— canonical标签

    百度MIP的规范要求必须添加强制性标签canonical,不然MIP校验工具会报错: 强制性标签<link rel="/^(canonical)$/"> 缺失或错误 这 ...

  4. ASP.NET内置对象的总结

    1. Response对象可形象的称之为响应对象,用于将数据从服务器发送回浏览器. 实例源码:链接: http://pan.baidu.com/s/1dDCKQ8x 密码: ihq0  2. Requ ...

  5. C语言 · 阶乘计算 · 基础练习

    问题描述 输入一个正整数n,输出n!的值. 其中n!=1*2*3*-*n. 算法描述 n!可能很大,而计算机能表示的整数范围有限,需要使用高精度计算的方法.使用一个数组A来表示一个大整数a,A[0]表 ...

  6. 关于全局ID,雪花(snowflake)算法的说明

    上次简单的说一下:http://www.cnblogs.com/dunitian/p/6041745.html#uid C#版本的国外朋友已经封装了,大家可以去看看:https://github.co ...

  7. 和我一起看API(一)你所不知道的LinearLayout补充

    楼主英语水平差,翻译的不好的话请多多指正,嘿嘿... A Layout that arranges its children in a single column or a single row. T ...

  8. windows下的命令行工具babun

    什么是babun babun是windows上的一个第三方shell,在这个shell上面你可以使用几乎所有linux,unix上面的命令,他几乎可以取代windows的shell.用官方的题目说就是 ...

  9. 客服小妹是如何泡到手的——C#定时提醒·语音录制·语音播放·文件转录Demo源码——倾情奉献!

    一.需求提出 客服小妹跟我说,每天要统计新加好友数,得先记下昨天的数目,然后查看今天的数目,还要相减,打字,记录——好麻烦! 又说,客户多的时候,忙起这头忘了那头,文字记录备忘又太费劲! 我说,赐你一 ...

  10. 基于Netty打造RPC服务器设计经验谈

    自从在园子里,发表了两篇如何基于Netty构建RPC服务器的文章:谈谈如何使用Netty开发实现高性能的RPC服务器.Netty实现高性能RPC服务器优化篇之消息序列化 之后,收到了很多同行.园友们热 ...