博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
最全Handler解读,持续补充...
阅读量:5889 次
发布时间:2019-06-19

本文共 15236 字,大约阅读时间需要 50 分钟。

Handler


 本文主要详细去解读Android开发中最常使用的Handler,以及使用过程中遇到的各种各样的疑问。

 在Android开发的过程中,我们常常会将耗时的一些操作放在子线程(work thread)中去执行,然后将执行的结果告诉UI线程(main thread),熟悉Android的朋友都知道,UI的更新只能通过Main thread来进行。那么这里就涉及到了如何将
子线程的数据传递给main thread呢?
 Android已经为我们提供了一个消息传递的机制——Handler,来帮助我们将子线程的数据传递给主线程,其实,当熟悉了Handler的原理之后我们知道,Handler不仅仅能将子线程的数据传递给主线程,它能实现任意两个线程的数据传递。
 接下来,我们便详细的了解下Handler的原理及其使用。
 首先看一下Handler最常规的使用方式:

private Handler mHandler = new Handler(){        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            switch (msg.what) {                case MESSAGE_WHAT:                    Log.d(TAG, "main thread receiver message: " + ((String) msg.obj));                    break;            }        }    };        private void sendMessageToMainThreadByWorkThread() {        new Thread(){            @Override            public void run() {                Message message = mHandler.obtainMessage(MESSAGE_WHAT);                message.obj = "I am message from work thread";                mHandler.sendMessage(message);            }        }.start();    }    /*    * 通常我们在主线程中创建一个Handler,    * 然后重写该Handler的handlerMessage方法,可以看到该方法传入了一个参数Message,    * 该参数就是我们从其他线程传递过来的信息。    *    * 我们在来看下子线程中如何传递的信息,子线程通过Handler的obtainMessage()方法获取到一个Message实例,    * 我们来看看Message的几个属性:    * Message.what------------------>用来标识信息的int值,通过该值主线程能判断出来自不同地方的信息来源    * Message.arg1/Message.arg2----->Message初始定义的用来传递int类型值的两个变量    * Message.obj------------------->用来传递任何实例化对象    * 最后通过sendMessage将Message发送出去。    *    * Handler所在的线程通过handlerMessage方法就能收到具体的信息了,如何判断信息的来源呢?当然是通过what值啦。    * 怎么样很简单吧    */

 文章的开头说过,Handler不仅仅是能过将子线程的数据发送给主线程,它适用于任意两个线程之间的通信。

 下面我们来看下两个子线程之间如何进行通信的。
 很简单啊,在一个线程创建Handler,另外一个线程通过持有该Handler的引用调用sendMessage发送消息啊!
 写程序可不能关说不练啊,我们把代码敲出来看一下!

private Handler handler;    private void handlerDemoByTwoWorkThread() {        Thread hanMeiMeiThread = new Thread() {            @Override            public void run() {//                Looper.prepare();                handler = new Handler() {                    @Override                    public void handleMessage(Message msg) {                        Log.d(TAG, "hanMeiMei receiver message: " + ((String) msg.obj));                        Toast.makeText(MainActivity.this, ((String) msg.obj), Toast.LENGTH_SHORT).show();                    }                };//                Looper.loop();            }        };        Thread liLeiThread = new Thread() {            @Override            public void run() {                try {                    Thread.sleep(2000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                Message message = handler.obtainMessage();                message.obj = "Hi MeiMei";                handler.sendMessage(message);            }        };        hanMeiMeiThread.setName("韩梅梅 Thread");        hanMeiMeiThread.start();        liLeiThread.setName("李雷 Thread");        liLeiThread.start();        /*        * 搞定,我们创建了两个Thread,liLeiThread和hanMeiMeiThread两个线程,很熟悉的名字啊!        * 跟之前的代码没太大区别hanMeiMeiThread创建了Handler,liLeiThread通过Handler发送了消息。        * 只不过此处我们只发送一个消息,所以没有使用what来进行标记        * 运行看看,我们的李雷能拨通梅梅吗?        * 啊哦,出错了        * 05-13 17:08:17.709 20673-20739/? E/AndroidRuntime: FATAL EXCEPTION: 韩梅梅 Thread                                                   Process: design.wang.com.designpatterns, PID: 20673                                                   java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()                                                       at android.os.Handler.
(Handler.java:200) at android.os.Handler.
(Handler.java:114) *Can't create handler inside thread that has not called Looper.prepare() * -----------》它说我们创建的handler没有调用Looper.prepare(); * 好的,我们在实例化Handler之前调用下该方法,看一下。加上是不是没有报错了呢。 * 等等,虽然没有报错,但是hanMeiMeiThread也没有接到消息啊,消息呢?别急。 * 我们在Handler实例化之后加上Looper.loop();看一看,运行一下,是不是收到消息了呢。 * 只是为什么呢? * 接下来我们就去看看Handler是怎么实现的发消息呢,弄清楚了原理,这里的原因也就明了了。 */ }

 好了,卖了半天的关子,终于要开始真正的主题了。

 首先我们来看下,为什么在子线程里实例化的时候不调用Looper.prepare()就会报错呢?

//我们先来看看new Handler();时出错的原因。后续讲解源码分析只贴出关键部分。//如下是Handler构造函数里抛出上文异常的地方,可以看到,由于mLooper对象为空才抛出的该异常。mLooper = Looper.myLooper();        if (mLooper == null) {            throw new RuntimeException(                "Can't create handler inside thread that has not called Looper.prepare()");        }/*  异常的原因看到了,接下来我们看看Looper.prepare()方法都干了些什么?*/public static void prepare() {    prepare(true);}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));}/* 可以看到,该方法在当前thread创建了一个Looper(), ThreadLocal主要用于维护线程的本地变量,  */ private Looper(boolean quitAllowed) {    mQueue = new MessageQueue(quitAllowed);    mThread = Thread.currentThread();}//而Looper的构造函数里面又为我们创建了一个MessageQueue()对象。

 了解到此,我们已经成功引出了Handler机制几个关键的对象了,Looper、MessageQueue、Message。

 那么,肯定也有人又产生新的疑问了——为什么在主线程中创建Handler不需要要用Looper.prepare()和Looper.loop()方法呢?
 其实不是这样的,App初始化的时候都会执行ActivityThread的main方法,我们可以看看ActivityThread的main()方法都做了什么?

Looper.prepareMainLooper();        ActivityThread thread = new ActivityThread();        thread.attach(false);        if (sMainThreadHandler == null) {            sMainThreadHandler = thread.getHandler();        }        if (false) {            Looper.myLooper().setMessageLogging(new                    LogPrinter(Log.DEBUG, "ActivityThread"));        }        // End of event ActivityThreadMain.        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);        Looper.loop();/* 真相只有一个,是的在创建主线程的时候Android已经帮我们调用了Looper.prepareMainLooper() 和Looper.loop()方法,所以我们在主线程能直接创建Handler使用。*/

 我们接着来看Handler发送消息的过程:

//调用Handler不同参数方法发送Message最终都会调用到该方法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);    }

 sendMessage的关键在于enqueueMessage(),其内部调用了messageQueue的enqueueMessage方法

boolean enqueueMessage(Message msg, long when) {        ...        synchronized (this) {            if (mQuitting) {                IllegalStateException e = new IllegalStateException(                        msg.target + " sending message to a Handler on a dead thread");                Log.w(TAG, e.getMessage(), e);                msg.recycle();                return false;            }            msg.markInUse();            msg.when = when;            Message p = mMessages;            boolean needWake;            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 {                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;            }            // We can assume mPtr != 0 because mQuitting is false.            if (needWake) {                nativeWake(mPtr);            }        }        return true;    }    /*从代码可以看出Message被存入MessageQueue时是将Message存到了上一个Message.next上,       形成了一个链式的列表,同时也保证了Message列表的时序性。    */

 Message的发送实际是放入到了Handler对应线程的MessageQueue中,那么,Message又是如何被取出来的呢?

 细心的朋友可能早早就发现了,之前抛出异常的地方讲解了半天的Loop.prepare()方法,一直没有说到Loop.loop()方法。同时,在之前的例子中也看到了,如果不调用Looper.loop()方法,Handler是接受不到消息的,所以我们可以大胆的猜测,消息的获取肯定和它脱不了关系!当然关怀疑还不行,我们还必须找出真相来证明我们的猜想?那还等什么,先看看loop()方法吧。

public static void loop() {//可以看到,在调用Looper.prepare()之前是不能调用该方法的,不然又得抛出异常了        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            final Printer logging = me.mLogging;            if (logging != null) {                logging.println(">>>>> Dispatching to " + msg.target + " " +                        msg.callback + ": " + msg.what);            }            final long traceTag = me.mTraceTag;            if (traceTag != 0) {                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));            }            try {                msg.target.dispatchMessage(msg);            } finally {                if (traceTag != 0) {                    Trace.traceEnd(traceTag);                }            }            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.recycleUnchecked();        }    }/*这里我们看到,mLooper()方法里我们取出了,当前线程的looper对象,然后从looper对象开启了一个死循环 不断地从looper内的MessageQueue中取出Message,只要有Message对象,就会通过Message的target调用dispatchMessage去分发消息,通过代码可以看出target就是我们创建的handler。我们在继续往下分析Message的分发*/public void dispatchMessage(Message msg) {    if (msg.callback != null) {        handleCallback(msg);    } else {        if (mCallback != null) {            if (mCallback.handleMessage(msg)) {                return;            }        }        handleMessage(msg);    }}/*好了,到这里已经能看清晰了可以看到,如果我们设置了callback(Runnable对象)的话,则会直接调用handleCallback方法*/private static void handleCallback(Message message) {        message.callback.run();    }//即,如果我们在初始化Handler的时候设置了callback(Runnable)对象,则直接调用run方法。比如我们经常写的runOnUiThread方法:runOnUiThread(new Runnable() {            @Override            public void run() {                            }        });public final void runOnUiThread(Runnable action) {      if (Thread.currentThread() != mUiThread) {          mHandler.post(action);      } else {          action.run();      }  }  /*而如果msg.callback为空的话,会直接调用我们的mCallback.handleMessage(msg),即handler的handlerMessage方法。由于Handler对象是在主线程中创建的,所以handler的handlerMessage方法的执行也会在主线程中。  */

 到这里,想必你应该清楚如何在不同的线程之间来使用Handler了吧。

最后总结一下:

  1. 在使用handler的时候,在handler所创建的线程需要维护一个唯一的Looper对象,
    Looper对象的内部又维护有唯一的一个MessageQueue,所以一个线程可以有多个handler,
    但是只能有一个Looper和一个MessageQueue。
  2. Message在MessageQueue不是通过一个列表来存储的,而是将传入的Message存入到了上一个
    Message的next中,在取出的时候通过顶部的Message就能按放入的顺序依次取出Message。
  3. Looper对象通过loop()方法开启了一个死循环,不断地从looper内的MessageQueue中取出Message,
    然后通过handler将消息分发传回handler所在的线程。

最后附上一张自己理解画出来的流程图:

img_933b8a49ef26b05cabfe29caf6c15bc6.png
20180513192006823.png


Handler补充:

1. Handler在使用过程中,需要注意的问题之一便是内存泄漏问题。

为什么会出现内存泄漏问题呢?

首先Handler使用是用来进行线程间通信的,所以新开启的线程是会持有Handler引用的,
如果在Activity等中创建Handler,并且是非静态内部类的形式,就有可能造成内存泄漏。

  1. 首先,非静态内部类是会隐式持有外部类的引用,所以当其他线程持有了该Handler,线程没有被销毁,则意味着Activity会一直被Handler持有引用而无法导致回收。
  2. 同时,MessageQueue中如果存在未处理完的Message,Message的target也是对Activity等的持有引用,也会造成内存泄漏。
解决的办法:

 (1). 使用静态内部类+弱引用的方式:

  静态内部类不会持有外部类的的引用。

private Handler sHandler = new TestHandler(this);static class TestHandler extends Handler {    private WeakReference
mActivity; TestHandler(Activity activity) { mActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); Activity activity = mActivity.get(); if (activity != null) { //TODO: } }}

 (2). 在外部类对象被销毁时,将MessageQueue中的消息清空。例如,在Activity的onDestroy时将消息清空。

@Overrideprotected void onDestroy() {    handler.removeCallbacksAndMessages(null);    super.onDestroy();}

2. 在使用Handler时,通常是通过Handler.obtainMessage()来获取Message对象的,而其内部调用的是Message.obtain()方法,那么问题来了,为什么不直接new一个Message,而是通过Message的静态方法obtain()来得到的呢?

下面就通过代码来一探究竟

public static Message obtain() {        synchronized (sPoolSync) {            if (sPool != null) {                Message m = sPool;                sPool = m.next;                m.next = null;                m.flags = 0; // clear in-use flag                sPoolSize--;                return m;            }        }        return new Message();    }

 其实在在Message中有一个static Message变量sPool,这个变量是用于缓存Message对象的,在obtain中可以看到当需要一个Message对象时,如果sPool不为空则会返回当前sPool(Message),而将sPool指向了之前sPool的next对象,(之前讲MessageQueue时讲过Message的存储是以链式的形式存储的,通过Message的next指向下一个Message,这里就是返回了sPool当前这个Message,然后sPool重新指向了其下一个Message),然后将返回的Message的next指向置为空(断开链表),sPoolSize记录了当前缓存的Message的数量,如果sPool为空,则没有缓存的Message,则需要创建一个新的Message(new Message)。

img_583028266632ce0f1bdfa4e98618f75c.jpe
20180608180422985.jpg

 接着看下sPool中缓存的Message是哪里来的?

public void recycle() {        if (isInUse()) {            if (gCheckRecycle) {                throw new IllegalStateException("This message cannot be recycled because it "                        + "is still in use.");            }            return;        }        recycleUnchecked();    }void recycleUnchecked() {        // Mark the message as in use while it remains in the recycled object pool.        // Clear out all other details.        flags = FLAG_IN_USE;        what = 0;        arg1 = 0;        arg2 = 0;        obj = null;        replyTo = null;        sendingUid = -1;        when = 0;        target = null;        callback = null;        data = null;        synchronized (sPoolSync) {            if (sPoolSize < MAX_POOL_SIZE) {                next = sPool;                sPool = this;                sPoolSize++;            }        }    }

 recycle()是回收Message的方法,在Message处理完或者清空Message等时会调用。

recycleUnchecked()方法中可以看到,将what、arg1、arg2、object等都重置了值,如果当前sPool(Message缓存池)的大小小于允许缓存的Message最大数量时,将要回收的Message的next指向sPool,将sPool指向了回收的Message对象(即将Message放到了sPool缓存池的头部)

img_e8067c15ec93b889705c9413504dbbfc.jpe
20180608180812257.jpg
总结:

由此可见,使用obtain获取Message对象是因为Message内部维护了一个数据缓存池,回收的Message不会被立马销毁,而是放入了缓存池,

在获取Message时会先从缓存池中去获取,缓存池为null才会去创建新的Message。


3. Handler sendMessage原理解读。

 引入问题!

  1. sendMessageDelayed是如何实现延时发送消息的?
  2. sendMessageDelayed是通过阻塞来达到了延时发送消息的结果,那么会不会阻塞新添加的Message?

详细分析请移步下篇文章:

转载地址:http://wzwsx.baihongyu.com/

你可能感兴趣的文章
Python数据结构1-----基本数据结构和collections系列
查看>>
SQL Denali-FileTable
查看>>
C# 图像处理:复制屏幕到内存中,拷屏操作
查看>>
PHP微信支付流程
查看>>
UDP收不到报文,调试了一晚上,终于发现问题所在
查看>>
Tornado简介
查看>>
Heron and His Triangle 2017 沈阳区域赛
查看>>
CF989B A Tide of Riverscape 思维 第七题
查看>>
unix高级环境编程-读书笔记(1)
查看>>
MongoDB学习教程(1)
查看>>
基于adt-bundle-windows-x86-20140321的android环境搭建
查看>>
Jquery遮罩ShowLoading组件
查看>>
pivot 使用
查看>>
hdu 1180 诡异的楼梯 BFS + 优先队列
查看>>
quartz
查看>>
hadoop中mapreduce的mapper抽象类和reduce抽象类
查看>>
【HAOI2006】【BZOJ1051】【p1233】最受欢迎的牛
查看>>
dedecms的安装,request_order的问题
查看>>
PHP遍历文件夹下的文件和获取到input name的值
查看>>
JavaScript对象属性(二)
查看>>