情景重现

        button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                try {                    Thread.sleep(5 * 1000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                textView.setText("after changed");            }        });

开头我们就看到了如上的一段简单的伪码。因为这里我试图去还原一种场景,一种可能我们不少人最初接触Android时可能都会遇到的错误场景。
这种场景的逻辑很简单:在程序运行中,某个控件的值会被动态的改变。这个值通过某种途径获取,但该途径是耗时的(例如访问网络,文件读写等)。
上面的伪码中的逻辑是:点击按钮将会改变文本框的值,且这个值是通过某种耗时的操作获取到,于是我们通过将线程休眠5秒来模拟这个耗时操作。

好的,现在我们通过编译正式开始运行类似的代码。那么,我们首先会收到熟悉的一个错误,即“Application No Response(ANR)”。
接着,通过查阅相关的资料,我们明白了:原来我们像上面这样做时,耗时操作直接就是存在于主线程,即所谓的UI线程当中的。
那么这就代表着:这个时候的UI线程会因为执行我们的耗时操作而被堵塞,也自然就无法响应用户其它的UI操作。于是,就会引起ANR这个错误了。
现在我们了解了ANR出现的原因,所以我们自然就会通过一些手段来避开这种错误。我们决定将耗时的操作从UI线程拿走,放到一个新开的子线程中:

        button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                new Thread(new Runnable() {                    @Override                    public void run() {                        try {                            Thread.sleep(5 * 1000);                            textView.setText("after changed");                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                }).start();            }        });

好的,现在我们模拟的耗时操作已经被我们放到了UI线程之外的线程。当我们信心十足的再次运行程序,确得到了如下的另一个异常信息:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

从异常信息中,我们看到系统似乎是在告诉我们一个信息,那就是:只有创建一个视图层次结构的原始线程才能触摸到它的视图
那么,我们似乎就能够理解这种异常出现的原因了:我们将耗时操作放在了我们自己创建的分线程中,显然它并非原始线程,自然就不能够去访问View。
这样设计的初衷实际上是不难猜想的,如果任何线程都能去访问UI,请联想一下并发编程中各种不可预知且操蛋的问题,可能我们的界面最终就热闹了。
但是,现在我们针对于这一异常的解决方案似乎也不难给出了。既然只有主线程能够访问View,那么我们只需要将更新UI的操作放到主线程就OK了。
那么,这里就顺带一提了。不知道有没有人和我曾经一样,想当然的写出过类似下面一样的代码:

        button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                new Thread(new Runnable() {                    @Override                    public void run() {                        try {                            Thread.sleep(5 * 1000);                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                }).start();                // 这是在主线程里执行的                textView.setText("after changed");            }        });

是的,如上的代码十分的,非常的“想当然”。这当然是因为对Java多线程机制理解不够所造成的。更新UI的操作确实是放到了主线程,但是!!!:
这并不代表着,UI更新一定会在分线程的耗时操作全部完成后才会执行,这自然是因为线程执行权是随机切换的。也就是说,很可能出现的情况是:
分线程中的耗时操作现在并没有执行完成,即我们还没有得到一个正确的结果,便切换到了主线程执行UI的更新,这个时候自然就会出现错误。

Handler粉墨登场

这个时候,作为菜鸟的我们有点不知所措。于是,赶紧上网查查资料,看看有没有现成的解决方案吧。这时,通常“Handler”就会进入我们的视线了:

    private Handler mHandler = new Handler(){        @Override        public void handleMessage(Message msg) {            switch (msg.what){                case 0x0001:                    textView.setText("after changed");            }        }    };    //===============================================    new Thread(new Runnable() {        @Override        public void run() {           try {              Thread.sleep(5 * 1000);              mHandler.sendEmptyMessage(0x0001);               } catch (InterruptedException e) {                  e.printStackTrace();               }             }     }).start();

我们发现关于Handler的使用似乎十分容易不过,容易到当我们认为自己掌握了它的时候似乎都没有成就感:

  • 首先,我们只需要建立一个Handler对象。
  • 接着,我们会在需要的地方,通过该Handler对象发送指定的Message。
  • 最后,该Handler对象通过handleMessage方法处理接收到的Message。

但我们沉下心来想一想:Handler为什么能够解决我们之前碰到的非原始线程不能更新UI的错误呢?它的实现原理如何?它能做的就只是更新UI吗?
掰扯了这么多,带着这些疑问,我们终于来到了我们这篇blog最终的目的,那就是搞清楚Android的消息机制(主要就是指Handler的运行机制)。

从构造函数切入

就像医生如果要弄清楚人体构造,方式当然是通过解剖来进行研究。而我们要研究一个对象的实现原理,最好的方式就是通过分析它的源码。
个人的习惯是,当我们没有一个十分明确的切入点的时候,选择构造函数切入通常是比较合适的,那我们现在就打开Handler的构造函数来看一下:

    // 1.    public Handler() {        this(null, false);    }    // 2.    public Handler(Callback callback) {        this(callback, false);    }    // 3.    public Handler(Looper looper) {        this(looper, null, false);    }    // 4.    public Handler(Looper looper, Callback callback) {        this(looper, callback, false);    }    // 5.    public Handler(boolean async) {        this(null, async);    }    // 6.    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;    }    // 7.    public Handler(Looper looper, Callback callback, boolean async) {        mLooper = looper;        mQueue = looper.mQueue;        mCallback = callback;        mAsynchronous = async;    }

好的,分析一下我们目前所看的,我觉得我们至少可以很容易的分析并掌握两点:

  • Handler自身提供了7种构造器,但实际上只有最后两种提供了具体实现。
  • 我们发现各种构造器最终围绕了另外两个类,即Callback与Looper。我们推测它们肯定不是做摆设的。

现在我们来分别看一下唯一两个提供了具体实现的构造器,我们发现:
除了 ”if (FIND_POTENTIAL_LEAKS) “这一段看上去和反射有关的if代码块之外,这两个构造器剩下的实现其实基本上是完全一致的,即:

        mLooper = looper;        mQueue = looper.mQueue;        mCallback = callback;        mAsynchronous = async;

唯一的不同在于mLooper这一实例变量的赋值方式:

        mLooper = Looper.myLooper();        if (mLooper == null) {            throw new RuntimeException(                "Can't create handler inside thread that has not called Looper.prepare()");        }        //==========================================        mLooper = looper;

“mLooper = Looper.myLooper();”这种方式究竟有何神奇,我们这里暂且不提。我们的注意力聚焦在以上看到的几个实例变量,打开源码看看:

    final MessageQueue mQueue;    final Looper mLooper;    final Callback mCallback;    final boolean mAsynchronous;

mAsynchronous是一个布尔型的变量,并且我们看到默认情况它的构造值是false,从命名我们就不难推测到,它多半与异步有关。除此之外:
其余类型分别是”MessageQueue,Looper,Callback“。一定记住它们!!!正是它们配合HandlerMessage完成了整个消息传递的架构。

OK,首先我们来看Callback这个东西,从命名来看绝逼与回调有关系,打开源码,果不其然正是定义在Handler内部的一个接口:

    public interface Callback {        public boolean handleMessage(Message msg);    }

我们看到了其中唯一声明的一个方法接口,看上去似乎有点眼熟。是的,那么它与Handler自身的handleMessage有何联系?我们暂且提不提。
现在,我们再接着看Looper和MessageQueue两个类型。很遗憾的是,这里我们发现:这是另外单独定义的两个全新的类。也就是说:
目前我们似乎无法在逻辑上将其与Handler联系起来。我们现在只知道从命名上来说,它们似乎分别代表着“循环”与“消息队列”的意思。

post()与sendMessage系列函数

那么,到了这一步似乎情况有点糟糕,因为似乎失去了下一步的切入点。没关系,这个时候我们回忆一下我们通常怎么样使用Handler:

  • mHandler.post();
  • mHandler.sendMessage();

没错,我们基本上就是通过以上两种方式去使用Handler。所以现在我们打开这两个方法相关的源码来看看:

    public final boolean post(Runnable r)    {       return  sendMessageDelayed(getPostMessage(r), 0);    }    private static Message getPostMessage(Runnable r) {        Message m = Message.obtain();        m.callback = r;        return m;    }    public final boolean sendMessage(Message msg)    {        return sendMessageDelayed(msg, 0);    }

由此我们发现的是:post函数最终调用的仍是send系列的函数;而sendMessage底部也依然是通过sendMessageDelayed调用的。
并且!查看一系列的send方法源码发现:它们最终都将通过sendMessageAtTime来完成整个调用。所以显然这将是我们下一个关注点。

先分析一下post方法的实现,我们看到其实Handler内部是通过getPostMessage对我们传入的Runnable对象进行了一次封装。
当我们看到getPostMessage方法的实现,我们会发现没什么大不了的,只是将传入Runnable对象赋值给了一个Message对象而已。
但我们也可能会观察到另外一点。就是我们可能会在使用Message时会通过构造器得到消息对象,而这里是通过静态方法obtain。

使用obtainMessage而非构造器

这二者有什么不同呢?我们先打开Message的构造器的方法来看一下:

    /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).    */    public Message() {    }

好的,我们发现构造器实际上没有任何实现内容。而注释告诉我们:更推荐使用obtain系列的方法来获取一个Message对象。
那么我们就好奇了?为什么更推荐使用obtain呢?我们以无参的obtain方法为例,打开源码瞧一瞧:

    /**     * Return a new Message instance from the global pool. Allows us to     * avoid allocating new objects in many cases.     */    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();    }

从以上代码我们可以看到的是:obtain最终的本质仍是产生Message对象。关键在于一个叫sPool的东西,这兄弟到底是个什么鬼呢?
实际上是Handler内部会通过这个叫做sPool的静态全局变量构建一个类似“池”的东西,而通过next属性我们不难推断”池”应该是以单链表来实现的。
再查看方法的注释:从全局池中返回一个新的消息实例。使我们能够避免在许多情况下分配新的对象。由此我们好像已经知道为何推荐obtain了。
包括网上很多打着类似“new message与obtainMessage之间区别”的资料里,一大段的文字之后,我们会发现最终实际有用的就类似一句话:
obtainMessage可以从池中获取Message对象,从而避免新的对象创建,达到节约内存的效果。但这样当然还是熄灭不了一颗好奇的心:
究竟为什么这个“池”能够避免新的对象创建呢?要解开这个疑问,我们还需要关注Handler类中的另一个方法“recycleUnchecked”的如下代码:

 void recycleUnchecked() {        // 第一部分        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++;            }        }    }

该方法顾名思义,主要的工作通常主要就是回收一个完成使命的Message对象。而这个回收动作发生的时机是什么呢?
通常来说,我们可以通过人为调用msg.recycle()来完成回收;另一种更常见的回收时机发生在MessageQuene当中,我们稍后会看到。
接下来我们该方法中的回收工作都做了什么,代码中注释为第一部分的代码做的工作很易懂,就是将回收的message对象的各个属性清空。
第二部分其实就是将回收的对象向“池”内添加的过程,而之前说到的obtain方法,其一旦判断sPoll不为null,就直接从池内获取闲置对象,不再创建。

到此实际上我们就已经分析了,为什么obtain能够节约内存开销的原理了。但如果你的数据结构和我一样渣,可能还会有点晕。没关系,看如下代码:

Message msg1 = Message.obtain();msg1.recycle();Message msg2 = Message.obtain();

我们对应于这三行简单的代码,来有代入感的分析一下它们运行的过程,相信就会有个比较清晰的理解了。

  • 首先获取msg1的时候,这个时候sPool肯定是为null的。所以它的工作实际与直接通过构造器创建对象没有区别。
  • 通过msg1对象调用recycle方法,最终进入之前所说的回收工作的第二部分执行。此时的结果为:msg1.next = sPoll(即null,没有next节点);sPoll = msg1;
  • 这时我们再通过obtain去获取对象msg2,进入方法后,判断sPoll不为null。于是, Message m = msg1;注意:
    这代表我们已经从池中取出了msg1,于是执行sPool = m.next时,我们说到msg1.next是null,所以sPool再次等于null,逻辑完全正确。
    与此同时,我们也可以想得到,假设m.next不等于null时:sPool = m.next的逻辑实际上就转换成了,将sPool指向next节点,即代表我们已经取走一个对象了,池将指向下一个节点,即为我们下次要获取的消息对象。

MessageQuene 消息队列的工作机制

好了,现在相信我们都清楚以上的概念了。我们的关注点将回到我们之前提到的关键位置,即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);    }

我们发现该方法的实现很简单,但最终会调用另一个方法“enqueueMessage”,赶紧打开这个方法看一看:

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

我们发现该方法显然使用了委托设计模式,将最终的方法实现委托了给了quene对象,即MessageQuene来实现。
对于MessageQuene中的enqueueMessage方法,该方法的源码个人觉得没必要全部关注。我们先看下面这小段代码:

            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.recycle方法,记得我们之前说过的回收工作吗?这里正是另一种发生时机,这个时机的标准如上所示,正是:
“mQuitting”为true,而在什么时候mQuitting会被设置为true,我们稍后将会看到,这里先暂且一提。接着看另一端更为关键的代码:

                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;

有了之前的基础,我们发现该方法所做的工作实际上很简单,它仍然是以单链表的形式,通过不断追加next节点达到向队列中添加Message的效果。
由此,我们发现:当我们通过handler对象post或send了一条消息,其实最终的工作很简单,就是向MessageQuene即消息队列中追加一条消息而已。
那么,接下来呢?自然的,消息追加到了队列当中。我们则需要从队列中依次取出消息对象,才能对其作出处理。苦苦寻觅一番之后:
我们发现了next()方法,该方法的实现归根结底是通过循环来不断的从队列中拉取消息,考虑到篇幅,我们不再贴出源码。唯一注意:

                    if (now < msg.when) {                        // Next message is not ready.  Set a timeout to wake up when it is ready.                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);                    } 

当没有新的消息来临之前,如上的代码将能够确保队列“阻塞”而一直等待新的消息对象来临。好了,我们总结一下:
MessageQuene将通过enqueueMessage方法向队列中插入消息,而通过next方法取出消息。但现在的关键点在:
关于enqueueMessage方法我们已经知道它在Handler当中被调用,而next方法目前我们只看到声明,还没看到调用的产生。

Looper - 消息的拉取者

以next()方法的调用为关键字按图索骥,我们最终发现它在我们之前提到的另一个关键的东西”Lopper”中产生了调用,具体是Lopper中的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;        ...        for (;;) {            Message msg = queue.next(); // might block            if (msg == null) {                // No message indicates that the message queue is quitting.                return;            }            ...            msg.target.dispatchMessage(msg);            ...            ...            msg.recycleUnchecked();        }    }

我们只保留了关于loop方法最为关键的部分,我们依次来分析一下,首先我们注意到的,肯定是一个名为“myLooper()”的方法调用:

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

ThreadLocal显神通

我们从代码中看到逻辑十分简单清晰,就是通过myLooper()来获取looper对象,而最终的方式则是通过sThreadLocal来获取。
这里,就不得不提到一个功能强大的东西ThreadLocal。我们来看一下Looper的源码当中关于sThreadLocal的定义:

static final ThreadLocal sThreadLocal = new ThreadLocal();

这个东西的作用究竟如何?简单来说,我们知道普通的定义一个实例变量,它将创建在“堆”上。
而“堆”并非线程私有的,所以实例变量也将被线程共享。而ThreadLocal则是将变量的作用域限制为线程私有。举例来说:

       static final ThreadLocal sThreadLocal = new ThreadLocal();       sThreadLocal.set("1");        new Thread(new Runnable() {            @Override            public void run() {                sThreadLocal.set("2");            }        }).start();

上面的代码通过sThreadLocal.get()来获取string,在主线程中和我们new的线程当中获取的值是独立的,分别是“1”和“2”。

接下来,我们看到的就是将会在一个无限循环中一直通过调用MessageQuene的next()方法来获取消息对象。
假设此次获取到了msg对象,则会通过msg.target调用dispatchMessage方法来分发消息。问题在于target是个什么东西?
在Message类中查看源码,我们可以知道target自身是一个Handler类型的对象。但通常我们都没有人为的去为这个变量赋值。
那么这个变量通常默认是什么呢?回到之前Handler类的enqueneMessage方法当中,看到如下代码:

 msg.target = this;

也就是说,如果我们没有明确的去为Message对象的target域赋值,它将被默认赋值为发送这条Message的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);        }    }

这个方法的实现逻辑也很清晰,它的具体分发过程如下:

  • 如果msg.callback不为null,那么将通过handleCallback方法来处理消息(实际上就是msg.callback.run());
  • 否则进一步判断mCallback是否为null,不为null则通过mCallback.handleMessage来处理消息。
  • 最后如果mCallback也为null,则会调用Handler自身的handleMessage方法来处理消息。

逻辑很简单,我们唯一注意的就是msg.callback和mCallback这两兄弟是指什么东西?很简单:
msg.callback是我们通过Message.obtain(Handler h, Runnable callback)或者通过Handler.post(Runnable r)传入的Runnable对象。
而mCallback就更熟悉了,回想我们之前查看Handler的构造器时看到的东西。
mCallback的本质就是Handler内部定义的接口Callback,所以通过它实际就是通过回调接口处理消息。

而这里,我觉得值得一说的是msg.callback这个东西。我们知道当它不为null,最终实际将通过message.callback.run()来处理消息。
也就是说最终实际上是调用了Runnable对象的run方法,但有Java的基础就会知道这样的调用实际与线程无关,只相当于普通的调用一个实例方法而已。
对于这点,我们一定要有清楚的认识,否则可能会因为使用不当造成一些想不到的错误。具体的例子我们暂且不说,放在最后的总结部分来看。

实际上到了这里,我们就已经把构建Android消息机制的四个关键,即Handler,Message,MessageQuene及Looper给联系起来了。简单总结一下:

  • 通过调用Handler的post或者send系列的方法来发送一条Message
  • 这一条Message最终会加入到链表结构的MessageQuene当中存放。
  • Looper会通过内部的loop方法不断调用MessageQuene的next()方法获取下一条Message
  • Looper获取到Message方法后,又会通过Handler的dispatchMessage来分发并处理消息。

Looper的正确创建

我相信到了这里,我们或多或少都会有些收获。但对于刚接触Andoid消息机制的朋友来说,还可能存在一个疑问,那就是:
通过之前我们的分析与理解,我们知道了对于Handler处理消息的机制来说,Lopper的参与是至关重要的。
但与此同时,我们发现之前我们似乎并没有创建Looper。我们不免会考虑,是系统帮助我们创建了吗?答案是肯定的。
回忆一下之前的代码,我们是通过无参的构造器来创建Handler对象的。我们也可以看到,该构造器最终会调用我们之前说到的第6个构造器。
然后我们发现在第6种构造器当中,是通过“mLooper = Looper.myLooper();”的方式来获取looper对象的。
这时我们就想起了之前的ThreadLocal,但即使是使用ThreadLocal,也起码得有一个ThreadLocal.set(Looper)的过程吧。
这个过程是在什么时候完成的呢?正常来说,我们推断这个过程很可能发生在Looper的构造器中。但一番查找我们发现Looper压根没提供公有的构造器。
经过苦苦的寻觅之后,最终我们会发现在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));    }

好了,现在我们回想一下,我们之前的代码中创建的Handler,是一个属于当前Activity的实例域。这代表它的创建是在主线程中完成的。
而关于Looper的创建的确也是在Android的主线程,即ActivityThread中完成创建的。具体的调用位于主线程的入口main方法中。
并且,主线程里的Looper,是通过调用Looper类专门为主线程创建Looper对象封装的方法“prepareMainLooper()”来创建的。

现在我们再看关于”Can’t create handler inside thread that has not called Looper.prepare()”的这个异常,我们就很容易找到原因了。
因为通过源码我们知道这个异常就是在第6个构造器中,当通过Looper.myLooper()获取结果为null时报告的。
同时,我们知道我们在主线程创建Handler的时候,没有问题是因为主线程默认就创建了Looper对象。那么:
当我们要在主线程以外的线程中创建Handler对象的时候,只要我们也为它创建对应的Looper对象就行了。示例如下:

        new Thread(new Runnable() {            @Override            public void run() {                Looper.prepare();                Handler handler = new Handler();                Looper.loop();            }        }).start();

是的,在创建完对象后,别忘了调用loop()方法,因为这才是开启循环从消息队列中获取消息的关键。

好了,现在我们正式接着看loop()方法中接下来的代码msg.recycleUnchecked();。是的,我们很熟悉的”回收”,由此我们可以知道:
回收Message的另一个时机实际就是,当Message对象从队列中取出处理完成之后,就会进行回收,放入池内。
如果具体阅读MessageQuene的源码,我们会发现还有多种不同的回收时机,我们简单总结几种常见的时机:

  • 人为调用message.recycle()来回收对象。
  • message从队列中取出被处理完成后会自动回收。
  • 调用Lopper.quit()/quitSafely(),该方法最终会调用MessageQuene的quit()方法。
  • MessageQuene调用quit在合适的时机将自身队列中的Message对象进行回收。
  • MessageQuene的quit方法还会将我们之前谈到的mQuitting设置为true,这代表着当调用了quit之后,再通过handler来send任何message,都将被直接回收。

总结

到这里,对于Android的消息机制我们也就研究的差不多了。虽然我总觉得在写的过程中,我本来还有个别想总结的点,但最后似乎忘了,也想不起了。

我们最后总结一个问题,那就是Handler为什么能够执行更新UI的操作!现在我们就来分析一下这个过程,回想一下:
通常我们在另开的线程中执行耗时操作,当耗时操作执行完毕后,则调用sendMessage()最终让handler更新UI。
现在我们知道了,这时的Handler我们一定会是定义在主线程,即UI线程当中的。当我们在分线程中sendMessage的时候:
经过我们之前说到的一系列调用,最终会通过Handler来进行最终处理。而Handler本身是在主线程中运行的,自然也就能够操作UI。
所以说,如果说Handler能够让我们更新UI,不如说其本质是将操作切换到该Handler所在的线程来执行。
我们可以试着发送消息给在分线程中创建的Handler对象,然后在handleMessage仍然试图去访问UI。会发现结果当然是行不通的。

这里就说到我们之前说到的handler.post()这样的使用方式了,因为参数类型是Runnable,所以我们很容易认为是通过handler执行一个线程任务。
但实际情况是,假设Handler对象是在主线程中创建的。那么,通过post()方法,我们仍然可以去更新UI。这是为什么?
这就回到了我们之前说的,当handler去dispatchMessage后,如果判断msg.callback不等于null,就会通过msg.callback.run()来处理消息。
这个时候实际本质上就是在切换到主线程去执行了Runnable对象的实例方法run()而已。所以当然能够更新UI。

而我们可能会有这样一种需求,那就是想要在指定的时间之后去执行一个耗时的线程任务,这个时候可能会想到Handler的postDelayed方法。
我想说的使用误区就是,这个时候的Handler一定不要是创建在主线程当中的,因为这样耗时操作最终还是在主线程执行,自然也就会引发ANR。
如果我们一定想要通过Handler实现我们这样的需求,其实很简单,当然就是要把Handler创建在分线程当中,就像下面这样:

              new Thread(new Runnable() {                    @Override                    public void run() {                        Looper.prepare();;                        final Handler handler = new Handler();                        Looper.loop();                        handler.post(new Runnable() {                            @Override                            public void run() {                                try {                                    Thread.sleep(5000);                                    handler.sendEmptyMessage(0);                                } catch (InterruptedException e) {                                    e.printStackTrace();                                }                            }                        });                    }                }).start();

更多相关文章

  1. Unable to start activity ComponentInfo 解决方法
  2. Android 安装apk的方法
  3. Android SQLite数据库解析并使用两种方法实现增删改查
  4. Android 读取XML的两种方法。
  5. android 主线程与分线程 做同步
  6. android window.requestWindowFeature()常用方法
  7. MediaExtractor的seekTo方法精确定位到指定帧
  8. android ImagView缩放方法之一(Bitmap)
  9. mono android 非UI线程操作UI线程

随机推荐

  1. 保留最新N份备份目录脚本
  2. Redis常见问题和解决办法梳理
  3. 小麦苗数据库巡检脚本,支持Oracle、MySQL
  4. Redis日常操作命令小结
  5. confluence与jira账号对接、查看到期时间
  6. zabbix监控交换机、防火墙等网络设备
  7. ELK基础架构解说-运维笔记
  8. 作用域与闭包;类与类的继承
  9. JavaScript:实例演示dom元素的增删改查操
  10. 商业智能与数据可视化的碰撞,能带来什么效