文档章节

从源码角度看Handler原理

北ing
 北ing
发布于 2015/10/20 19:22
字数 2344
阅读 7
收藏 0
点赞 0
评论 0

在Android中,有一个规定就是除了主线程,其他线程不能操作UI视图,因为不这样做的话会出现线程不安全问题。但还有一个规定UI线程在执行一个操作如果5秒内没有响应就会包ANR错误。所以UI线程中不允许访问网络这样的耗时操作,那么问题来了,子线程执行耗时操作,比如从网络获取图片,但子线程不能更新UI,而主线程能更新UI,但不能去下载图片。这样handler消息处理机制就出现了。

1. Handler是什么?

handler是Android给我们提供用来更新UI的一套机制,也是一套消息处理的机制,我们可以发送消息,也可以通过它处理消息。

2. 为什么要使用Handler?

Android在设计时,就封装了一套消息消息创建、传递、处理机制,如果不遵循这样的机制就没办法更新UI,就会抛出异常。

3. Android中为什么要设计只能通过Handler机制更新UI?

解决多线程并发问题。如果允许多线程更新UI会导致界面错乱,如果非要使用多线程并使用加锁机制更新UI又会导致性能下降,所以更新UI的操作全部交给主线程。

4.那么handler机制是怎样的机制呢?

了解handler机制,首先需要清楚四个核心类:
Message:对发送的消息的封装
MessageQueue:消息队列,存放所有的消息
Looper:可以循环读取消息(从MessageQueue中读取)
Handler:处理消息,同时也是发送消息的

具体来看看handler是怎么实现的。

4.1使用1:主线程接收子线程发来的消息(下载图片为例)
1)在主线程中初始化handler对象:

private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //处理子线程发送过来的message
            Bitmap bitmap = (Bitmap)msg.obj;
            imageView.setImageBitmap(bitmap);
        }
    };

2)然后子线程中下载好图片后发送图片给主线程:

new Thread(new Runnable() {
            @Override
            public void run() {
                HttpGet get = new HttpGet(path);
                HttpClient client = new DefaultHttpClient();
                HttpResponse response = null;
                try {
                    response = client.execute(get);
                    if (response.getStatusLine().getStatusCode() == 200) {
                        byte[] arr = EntityUtils.toByteArray(response
                                .getEntity());
                        Bitmap bitmap = BitmapFactory.decodeByteArray(arr, 0,
                                arr.length);
                        // 下载完成时把图片发送给主线程
                        // 从MessageQueue中获取可用的Message对象,如果没有可用的Message对象则会创建一个新的Message对象
                        Message msg = Message.obtain();
                        // 把发送的图片封装到msg中
                        msg.obj = bitmap;
                        // 使用Handler发送msg
                        handler.sendMessage(msg);// 把msg发送给实例化handler的线程
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

消息的拦截:

/** * 拦截消息测试 * @author Administrator * */
public class ThirdActivity extends Activity {
    Handler handler = new Handler(new Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            Log.i("--", "消息都要经过我这里");
            /**返回false,消息会继续向下分发,返回true则拦截*/
            return true;
        }
    }) { 
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.i("--", "我才是处理消息的");
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                handler.sendEmptyMessage(1);
            }
        }).start();
    }
}

3)使用1有关handler机制的操作有:
Handler handler = new Handler();
handleMessage(Message msg);

Message msg = Message.obtain();
handler.sendMessage(msg);

那么现在来看一看它内部到底是怎样执行的。

5.从源码一步一步分析handler原理

5.1Handler handler = new Handler();

 public Handler() {
        this(null, false);
    }

这个无参构造方法会调用两个参数的构造方法,注意上面的参数null和false,如下:

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

那来看看这个两个参数的构造方法都做了些什么。
FIND_POTENTIAL_LEAKS这个变量初始化为false
private static final boolean FIND_POTENTIAL_LEAKS = false;
所以直接执行后面的,也就是这些:

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对象和一个MessageQueue对象,mLooper = Looper.myLooper();中Looper.myLooper()方法中是得到当前线程的Looper对象。那么当前线程是什么?在这里,因为我们是在主线程中实例化Handler对象,所以当前线程就是主线程,值得注意的是,主线程在创建时就会维护一个Looper对象和MessageQueue对象,所以这里得到的Looper对象和消息队列都是主线程的。

public static Looper myLooper() {
        return sThreadLocal.get();
    }

mQueue = mLooper.mQueue;这句说明handler内部的MessageQueue和Looper的MessageQueue是一个。
好,那么handler的初始化阶段先到这里。
5.2 Message msg = Message.obtain();
再来看看这句做了哪些事,
注意:有时候我们会直接这么写Message msg = new Message();

 public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

这里的意思就是如果sPool不为空就返回sPool,否则就new一个新的Message对象。(sPool是回收放在消息池中的对象)

public void recycle() {
        clearForRecycle();
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

5.3 handler.sendMessage(msg);
关键的一句终于到了,来看看这个又做了什么。
第一步:

public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

第二步:

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

第三步:

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

第四步:

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

到第四步算是找到能办事的了,看这里都办了哪些事。
1) msg.target = this;可以查看Message源码target就是Handler类型的,这里把msg的target指向this,也就是这个发送消息的handler。

2)queue.enqueueMessage(msg, uptimeMillis);调用queue的enqueueMessage方法,去MessageQueue类中看看。

final boolean enqueueMessage(Message msg, long when) {
        if (msg.isInUse()) {
            throw new AndroidRuntimeException(msg + " This message is already in use.");
        }
        if (msg.target == null) {
            throw new AndroidRuntimeException("Message must have a target.");
        }
        boolean needWake;
        synchronized (this) {
            if (mQuiting) {
                RuntimeException e = new RuntimeException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                return false;
            }
            msg.when = when;
            Message p = mMessages;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue. Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
        }
        if (needWake) {
            nativeWake(mPtr);
        }
        return true;
    }

很简单,就是把handler发送的msg加入到消息队列中。

5.4handleMessage(Message msg);
好了,最后只用处理msg了。这是个回调方法,所以必须要等到Looper去从消息队列中读取消息时才会执行。
那就去看看Looper.loop();这个循环读取消息的方法。

public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            msg.target.dispatchMessage(msg);
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }
            msg.recycle();
        }
    }

开头几句是得到当前线程的Looper对象,然后同构Looper对象得到queue对象,很明显,5.1已经说了,那么这里得到的就是主线程那个Looper和MessageQueue。然后开始了死循环,一直从消息队列中读取消息,读不到就返回,否则执行后面操作。注意看后面操作,真正处理消息的时候到了。

msg.target.dispatchMessage(msg);关键就是这句。 它调用了msg的target的dispatchMessage方法,之前说了,target就是发送消息的那个handler,好,再会Handler类看dispatchMessage方法。

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

很简单,这就是一个消息分发处理嘛。记得handler发送消息有两种方式,一种就是普通的handler.sendMessage(msg);方法,还有一种就是
handler.post(new Runnable(){
@Override
public void run() {
//主线程应该执行的操作
}
});
先来看第一种:直接回调hanleMessage(msg);方法,这样主程序中就执行该操作啦。
再来看第二种:

 private static void handleCallback(Message message) {
        message.callback.run();
}

调用message.callback的run方法。
从源码可以一步一步看出,如下,message的callback指向handler调用post方法传入的Runnable对象。

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

再回头看dispatchMessage方法,会调用下面这个方法:

private static void handleCallback(Message message) {
        message.callback.run();
    }

内部最后调用run方法。
handler原理基本就这样了。

总结:handler对象定义在需要接收本线程或其他线程发送消息的线程中,该线程会维护一个Looper对象和MessageQueue对象,在其他线程或本线程通过handler发送的消息会加入到handler所在线程中的消息队列中,同时Looper的loop()方法会一直读取消息队列,读到消息后,又让发送消息的handler处理这个消息。

源码下载

版权声明:本文为博主原创文章,未经博主允许不得转载。

© 著作权归作者所有

共有 人打赏支持
北ing
粉丝 0
博文 12
码字总数 17332
作品 0
海淀
Android异步消息处理机制完全解析-Handler详解

参考资料 - 官方介绍文档 - Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系 - Android异步消息处理机制完全解析,带你从源码的角度彻底理解 - 慕课网课程-Androi...

javen205 ⋅ 2017/03/25 ⋅ 0

IntentService-你可能需要知道这些

Service作为Android四大组件之一,在我们的日常开发中占着不可或缺的一席,而轻量级的IntentService作为对Service的一个补充,也越来越收到大家的推崇,本博文就带大家来学习和分析下IntentS...

24K男 ⋅ 2017/09/27 ⋅ 0

知识点

蓝厂: 1.事件分发流程 2.View的渲染机制 3.动画的原理,底层如何给上层信号 编译打包的过程 5.Android有多个资源文件夹,应用在不同分辨率下是如何查找对应文件夹下的资源的,描述整个过程 ...

咖喱配胡椒 ⋅ 2017/10/10 ⋅ 0

Android Handler异步通信:深入详解Handler机制源码

前言 在开发的多线程应用场景中,机制十分常用 今天,我将手把手带你深入分析 机制的源码,希望你们会喜欢 目录 1. Handler 机制简介 在多线程的应用场景中,将工作线程中需更新的操作信息 ...

carson_ho ⋅ 05/21 ⋅ 0

从源码的角度认识AsyncTask

一、为什么需要工作者线程 我们知道,Android应用的主线程(UI 线程)肩负着绘制用户界面和及时响应用户操作的重任,为了避免“用户点击按钮后没反应”这样的糟糕用户体验,我们就要确保主线...

xingjm8511 ⋅ 2016/06/23 ⋅ 0

Android 开发进阶指南

之前有人在朋友圈评论我说,现在学习Android 遇到了瓶颈,基本上一般的API,市面上大部分App UI 都能做出来,该怎么提高?然后我回复他了,我学习Android 也有一年左右,也是个菜鸟,也遇到这...

程序袁_绪龙 ⋅ 2016/03/15 ⋅ 0

HandlerThread线程间通信 源码解析

上一篇我们通过源码分析了Handler的消息流程原理,如果对的原理还不够明白的同学可以先学习上篇。我们今天的主角是。此乃我android大军一员猛将也。 目录 HandlerThread简单介绍 如果没有Han...

香脆的大鸡排 ⋅ 2017/07/18 ⋅ 0

Android消息机制源码分析

Android消息机制的概述 Handler 是Android消息机制的上层接口,通过它可以轻松的将一个任务切换到Handler所在的线程去执行。 Q&A 为什么Android需要通过Handler去切换任务执行的线程呢? 因为...

sun_____xin ⋅ 2017/09/01 ⋅ 0

Handler机制原理(五)总结

时光飞逝,不知不觉写完Handler系列文章已经用时一个月了。作为我开始分析Android Framework源码的敲门砖还是遇到了很多挫折,尤其是分析MessageQueue源码时那种百思不得其解的疑惑困扰着我很...

吴七禁 ⋅ 2017/12/06 ⋅ 0

【原创】遨游springmvc之HandlerMapping

1.前言 之前我们springmvc原理篇里已经介绍过,从springmvc核心处理器DispatcherServlet派遣请求的时候,首先会将请求包装,这就是我们这边介绍的HandlerMapping 在springmvc源码介绍中我们知...

开源中国首席脑科主任 ⋅ 2016/07/24 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

知乎Java数据结构

作者:匿名用户 链接:https://www.zhihu.com/question/35947829/answer/66113038 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 感觉知乎上嘲讽题主简...

颖伙虫 ⋅ 今天 ⋅ 0

Confluence 6 恢复一个站点有关使用站点导出为备份的说明

推荐使用生产备份策略。我们推荐你针对你的生产环境中使用的 Confluence 参考 Production Backup Strategy 页面中的内容进行备份和恢复(这个需要你备份你的数据库和 home 目录)。XML 导出备...

honeymose ⋅ 今天 ⋅ 0

JavaScript零基础入门——(九)JavaScript的函数

JavaScript零基础入门——(九)JavaScript的函数 欢迎回到我们的JavaScript零基础入门,上一节课我们了解了有关JS中数组的相关知识点,不知道大家有没有自己去敲一敲,消化一下?这一节课,...

JandenMa ⋅ 今天 ⋅ 0

火狐浏览器各版本下载及插件httprequest

各版本下载地址:http://ftp.mozilla.org/pub/mozilla.org//firefox/releases/ httprequest插件截至57版本可用

xiaoge2016 ⋅ 今天 ⋅ 0

Docker系列教程28-实战:使用Docker Compose运行ELK

原文:http://www.itmuch.com/docker/28-docker-compose-in-action-elk/,转载请说明出处。 ElasticSearch【存储】 Logtash【日志聚合器】 Kibana【界面】 答案: version: '2'services: ...

周立_ITMuch ⋅ 今天 ⋅ 0

使用快嘉sdkg极速搭建接口模拟系统

在具体项目研发过程中,一旦前后端双方约定好接口,前端和app同事就会希望后台同事可以尽快提供可供对接的接口方便调试,而对后台同事来说定好接口还仅是个开始、设计流程,实现业务逻辑,编...

fastjrun ⋅ 今天 ⋅ 0

PXE/KickStart 无人值守安装

导言 作为中小公司的运维,经常会遇到一些机械式的重复工作,例如:有时公司同时上线几十甚至上百台服务器,而且需要我们在短时间内完成系统安装。 常规的办法有什么? 光盘安装系统 ===> 一...

kangvcar ⋅ 昨天 ⋅ 0

使用Puppeteer撸一个爬虫

Puppeteer是什么 puppeteer是谷歌chrome团队官方开发的一个无界面(Headless)chrome工具。Chrome Headless将成为web应用自动化测试的行业标杆。所以我们很有必要来了解一下它。所谓的无头浏...

小草先森 ⋅ 昨天 ⋅ 0

Java Done Right

* 表示难度较大或理论性较强。 ** 表示难度更大或理论性更强。 【Java语言本身】 基础语法,面向对象,顺序编程,并发编程,网络编程,泛型,注解,lambda(Java8),module(Java9),var(...

风华神使 ⋅ 昨天 ⋅ 0

Linux系统日志

linux 系统日志 /var/log/messages /etc/logrotate.conf 日志切割配置文件 https://my.oschina.net/u/2000675/blog/908189 logrotate 使用详解 dmesg 命令 /var/log/dmesg 日志 last命令,调......

Linux学习笔记 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部