从源码一次彻底理解Android的消息机制

安卓中的消息循环机制Handler及Looper详解 我们知道安卓中的UI线程不是线程安全的,我们不能在UI线程中进行耗时操作,通常我们的做法是开启一个子线程在子线程中处理耗时操作,但是安卓规定不允许在子线程中进行UI的更新操作,通常我们会通过Handler机制来完成该功能,即当子线程中耗时操作完成后,在子线程中通过Handler向主线程发送消息,在主线程中的Handler的handleMessage方法中处理接受到的消息。这就是安卓中的消息机制,安 阅读详情

情景重现

        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“。一定记住它们!!!正是它们配合Handler及Message完成了整个消息传递的架构。

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<Looper> sThreadLocal = new ThreadLocal<Looper>();

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

       static final ThreadLocal<String> sThreadLocal = new ThreadLocal<String>();

       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();
Android异步消息机制及源码分析 【yian_ http://blog.youkuaiyun.com/ 转载烦请注明出处,尊重分享成果】一直对Android中的消息处理机制比较模糊,恰好昨天做了一次Team知识分享,也就借此机会配合源码了解下消息处理机制,仅以博客记录。1 前言一:在分析别人的源码,一直都在workThread配合looper ,handler与uiThread交互互通Message。 二:对于很多初学者老说,大都是知其然而 阅读详情

相关推荐

Android消息机制源码解析(Handler)

Android消息机制,其实也就是Handler机制,主要用于UI线程和子线程之间交互。众所周知,一般情况下,出于安全的考虑,所有与UI控件的操作都要放在主线程及UI线程,而一些耗时操作应当放在子线程中。当在子线程中完成耗时操作并要对UI控件进行操作时,就要用Handler来控制。另外,Android系统框架内,Activity生命周期的通知等功能也是通过消息机制来实现的。本篇博文主要是想通过Han

笔沫拾光 2113

“过一会”等于“用不”

今天看书发现了这句话“Later equals never”,发现对自己十分受用。 无论是工作还是学习、生活,我似乎都深受其害。往往对自己“一会再做”的命令,其实就是对事情的推诿和搪塞,是懒惰的体现。 这是个技术博客,但是我希望以此文提醒自己。像爸爸说的,当你想到一件事情,就马上去做。...

chenbeiyou的博客 861

Android中Service完全解析(上)

此源码对应的是我的博客“Android Service完全解析,关于服务你所需知道的一切(上)”,经过测试完美运行,配合着博客和源码,能够快速理解Android中Service组件的工作原理

【Android】消息队列之异步消息

异步消息可以理解为是用来提升消息的重要性,在有异步消息时,会优先处理距当前时间最近的异步消息,同步消息都不会被处理,绘制界面就是一个异步消息; 同步消息:没有设置为异步的消息都是同步消息; Message提供setAsynchronous用来设置异步消息; 一、异步消息的使用流程 1.使用MessageQueue的postSyncBarrier() 来加入同步屏障;同步屏障其实就是一个handler为空的消息,它也存放在消息队列中; 2.往消息队列中加入异步消息,跟平时创建消息没有什么不同,只是额

EnjoyInwind的专栏 2463

从Unitree B2W到四足机器人:用Isaac Lab构建强化学习奖励函数的5个实战技巧

本文分享了在Isaac Lab仿真环境中,为Unitree B2W等四足机器人设计强化学习奖励函数的5个核心实战技巧。内容涵盖奖励函数的结构化组合、轮腿混合机器人的适配策略、课程学习的动态调整、域随机化增强鲁棒性以及高效的调试可视化方法,旨在帮助开发者构建更稳定、高效的机器人运动策略。

lg888的博客 1065

从PWM到正弦波:RC低通滤波器的魔法转换之旅

本文详细介绍了如何利用RC低通滤波器将STM32F103生成的PWM信号转换为高质量正弦波的技术原理与实践方法。通过分析滤波器设计、参数计算、硬件实现及Proteus仿真验证,为嵌入式开发者提供了一套完整的PWM转正弦波解决方案,适用于电机控制、音频信号生成等多种应用场景。

jupi8的博客 380

Android消息机制—MessageQuene插入和读取算法

MessageQuene MessageQuene在Android中是消息队列的意思,但他内部存储结构并不是队列,而是采用单向链表的数据结构 形式存储消息。是Handler的具体实现之一(另外一个是Looper)。 MessageQuene#enqueueMessage() enqueueMessage()是MessageQuene消息插入的实现方法,其实就是一个链表的插入操作。 ...

gybguohao的博客 746

从入门到精通:MediaGo跨平台部署全攻略,Docker+NAS实战案例深度拆解!

本篇将深入解析开源媒体处理工具MediaGo的运作机制,该软件具备跨平台兼容性,支持Windows、MacOS及Linux三大主流操作系统。其核心创新在于采用模块化架构设计,用户无需额外依赖任何插件即可完成快速部署。作为GitHub平台上的开源项目,该工具特别注重用户体验设计,通过Docker容器化方案可无缝接入各类网络存储设备,显著优化系统资源占用并提升数据处理效率。以飞牛云NAS系统为实施案例,本文将详细展示本地化部署全流程。

Yawesh的博客 1953

勒布朗法则

稍后等于永不!Later equals never 。 —— [ LeBlanc ]

webrtc 3020

嵌入式开发必备:手把手教你用DB-9连接器搞定RS-232串口通信(附万用表测试技巧)

本文详细解析了嵌入式开发中RS-232串口通信的硬件连接核心。从DB-9连接器的引脚定义入手,深入讲解DTE与DCE设备角色,明确直连线与交叉线的选择法则,并重点分享了使用万用表进行线缆连通性测试、电压测量及故障诊断的实战技巧,帮助开发者彻底掌握串口通信的硬件连接与排查方法。

tgb345678的博客 1164

电商平台大数据分析指标

一、五大关键数据分析指标 1.1活跃用户量 活跃用户量是一个基本的指标,有 DAU(日活跃用户)、WAU(周活跃用户)和 MAU(月活跃用户)三个层次; 1.2转化 转化是一个非常重要的指标,电商运营需要关注主路径、次路径甚至精细到每一个品类 / SKU 的转化率; 1.3留存 留存要从不同的时间周期上研究,包括次日留存率、3 日、7 日、30 日留存; 1.4复购 复购则要从 3 个角度去看,复购用户量、复购率和复购金额比; 1.5GMV GMV 是最重要的指标,我们的运营最终是围绕这

默主归沙的博客 4818

Android 性能优化系列 - 03 使用对象池优化内存

避免在循环里面或者频繁被调用的方法里面创建对象,在循环或者被频繁调用的方法之外创建对象使用对象池缓存对象,在使用对象的时候,直接从对象池中取对象即可,避免频繁的创建对象一般来讲,实现对象池有三种方法,对应的具体的数据结构即是数组、链表和 Map,下面我将通过示例分别介绍通过这三种数据结构实现的对象池首先是Pool<T>接口定义,代码不难,如下所示,只有两个方法acquire()和方法acquire():从对象池中取一个对象,注意:有可能会返回 null:将一个对象释放到对象池中,

Dream It Possible 1070

LeRobot 入门教程(十一)π0(Pi0)π0.5(Pi0.5)训练、推理、部署

本文介绍了Physical Intelligence公司开发的π₀和π₀.₅系列通用机器人控制视觉-语言-动作模型。π₀作为首款基础模型,融合流匹配、跨实体训练等技术,支持50Hz高频控制。π₀.₅是其升级版,通过异构数据协同训练解决开放世界泛化问题,在Libero基准测试中表现优异。文章详细说明了两个模型的安装要求、使用方法、训练策略及关键参数配置,包括混合精度训练、梯度检查点等优化技术,为机器人在多样化场景中的语义理解和动作控制提供了完整解决方案。

2506_90492529的博客 5034

深入理解Android消息机制

前言 Android的消息机制主要说的是Handler的运行机制,相信大家对Handler已经非常熟悉了,Handler可以轻松的将一个任务切换到Handler所在的线程中去执行。最熟悉的就是我们只能在UI线程中更新UI,所以我们经常来用Handler来更新UI,但Handler并不是专门用来更新UI的。本文源码基于Android8.0。 一、为什么只能在主线程中访问UI 可...

黄林晴 5085

医院批量排班实战指南(三)日期列表生成与时段批量操作

在上一篇《批量排班功能实现指南(二):日期与时段设置》中,我们完成了日期范围选择和重复规则配置的界面搭建,实现了从第一步到第二步的跳转功能。现在我们已经可以选择日期范围和重复规则,但还缺少最关键的功能——根据规则生成日期列表并进行时段配置。本篇将继续深入批量排班的核心功能——日期列表生成与时段批量操作。根据日期范围和重复规则智能生成日期列表为每个日期配置上午、下午、夜诊时段批量时段操作提升配置效率日期列表的展示与管理。

低代码布道师的博客 1345

【大模型+知识图谱】大模型与知识图谱融合:技术演进、实践应用与未来挑战

大模型与知识图谱的融合不是简单拼接,而是认知智能的范式重构。当符号主义的精确性与连接主义的泛化能力深度交织,我们正站在通向可信AGI的关键路口。正如OpenKG技术委员会王昊奋教授所言:“2025年将是图模协同从技术探索走向规模落地的分水岭”。参考文献OpenKG 2024年度报告(同济大学王昊奋)大型语言模型与知识图谱协同研究综述(海豚实验室,2024)知识图谱与大模型结合方法概述(黄巍,2023)图模互补:知识图谱与大模型融合综述(优快云,2024)

知识靠谱的博客 1088

Android :安卓学习笔记之 Handler机制 的简单理解和使用

Android Handler机制的简单理解和使用1、Handler使用引出2、背景和定义3、作用4、相关概念5、工作流程5.1、对应关系6、基本使用7、参考 1、Handler使用引出 现在作为客户,有这样一个需求,当打开Activity界面时,开始倒计时,倒计时结束后跳转新的界面(思维活跃的朋友可能立马想到如果打开后自动倒计时,就类似于各个APP的欢迎闪屏页面),如下图: 可能觉得直接开启一个包含倒序循环的子线程就ok了,具体实现如下: activity_main <?xml version="

JMW1407的博客 6775

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

android msg传递数据_一文搞懂handler:彻底明白Android消息机制的原理及源码

提起Android消息机制,想必都不陌生。其中包含三个部分:Handler,MessageQueue以及Looper,三者共同协作,完成消息机制的运行。本篇文章将由浅入深解析Android消息机制的运行原理,先介绍三者之间的协作框架,然后从源码的角度来分析具体的运行机制。文章已收录至我的知乎专栏《进击的Android秃头》,建议看之前,点一波双关(关注作者+关注专栏)。你会意想不到获得满满的干货!...

weixin_42316079的博客 722

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

这一次,我们用最详细的方式解析Android消息机制的源码

Looper.prepare();mHandler = new Handler() {public void handleMessage(Message msg) {// 处理消息队列中的消息。}};// 开启消息循环,会从消息队列中取出消息,没有消息时等待新消息的到来。Looper.loop();}}综合这两个示例,我们了解了消息机制的使用方法,也看到了发送消息、创建消息队列、开启消息循环以及处理消息的过程,下面给出一个更直观的“消息传递流程图”:通过流程图可以看到整个消息传递过程,也可以看到在不同的阶段

WJud11的博客 947

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Android Handler消息处理机制

消息处理机制本质:一个线程开启循环模式持续监听并依次处理其他线程给它发的消息。简单的说:一个线程开启一个无限循环模式,不断遍历自己的消息列表,如果有消息就挨个拿出来做处理,如果列表没消息,自己就堵塞(相当于wait,让出cpu资源给其他线程),其他线程如果想让该线程做什么事,就往该线程的消息队列插入消息,该线程会不断从队列里拿出消息做处理。

轻口味的专栏 3401

Android异步消息处理机制完全解析,带你从源码的角度彻底理解

我们都知道,Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃。相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了。这种处理方式被称为异步消息处理线程,虽然我相信大家都会用,可是你知道它背后的原理是什么样的吗?今天我们就来一起深入探究一下Handler和

郭霖的专栏 11万+

安卓消息机制之Handler基础知识

相信大家从网上也看到过Handler的代码解析,下面我将从我个人理解的角度,以尽量通俗易懂的语言带领大家对Handler的用法以及源码设计进行解读。

weixin_39330114的博客 1452

从源码看Android消息机制

说到Android的消息机制自然会想到Handler、Message、MessageQueue、Looper这几个类。用一句话概括Android消息机制的过程:通过Handler的sendMessage或post方法发送一个被封装成Message对象的消息,这些消息会被加入到MessageQueue中,由Looper来循环从MessageQueue中取出Message对象,最后回调Handler的

草帽君的博客 649

很容易理解的Android消息机制分析

最近经常使用Handler相关的一些东西。翻了下源码,总结一遍方便回顾。 Android消息机制就是Android的handler机制。 首先是为什么要使用handler? 子线程benign操作ui,因此需要将操作ui的消息传递到主线程,使主线程按照需求更新ui,避免操作不安全。 Handler的重要组成部分:Handler Looper MessageQueue ,其中Looper是Handler和messagequeue通信的桥梁。 Handler是消息处理器。MessageQueue.

xululu0401的博客 1684

Android消息机制源码解析(四)——消息队列MessageQueue

MessageQueue是Android消息机制系列文章的最后一个小节,MessageQueue的主要作用是按一定规则维护一个消息队列,并能够添加消息、取出消息、移除消息等。 一、来看下MessageQueue是怎样定义的: public final class MessageQueue 接下来是一些重要的属性字段: // True if the message queue can be 

Battling against our own ignorance 1735
上一篇: 一步步理解Android事件分发机制
下一篇: 自己动手写一个轻量级的Android网络请求框架
LaterEqualsNever
博客等级 码龄12年 297粉丝 61原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值