Handler、Looper、Message关系详解

Android消息机制详解
本文深入解析Android中的Handler、Looper及Message机制。通过示例代码详细介绍了如何使用这些组件进行跨线程通信,并解释了背后的原理。

这是我的第一篇博客,之前也是酝酿了很久,经过几个伙伴的鼓励和计划之下,还是决定将自己的所学知识通过文字的形式表达出来,也是提升自己的一种方式。本文将通过使用、原理、拓展来详细介绍Handler、Looper、Message之间的关系。好了,废话不多说,直入主题。
一、使用篇
先看下面一个小demo:

public class SecondActivity extends Activity {

    private Handler mHandler;
    private EditText vEditText;

    private static final int MSG_WHAT_1 = 0x11;
    private static final int MSG_WHAT_2 = 0x12;
    private static final int MSG_WHAT_3 = 0x13;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        vEditText = (EditText)findViewById(R.id.cll_read_sms);

        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_WHAT_1:
                        vEditText.setText("hello world!!!");
                        break;
                    case MSG_WHAT_2:
                        vEditText.setText("goodBye world!!!");
                        break;
                    case MSG_WHAT_3:
                        vEditText.setText("welcome world!!!");
                        break;
                    default:
                        break;
                }
            }
        };

        new LooperThread().start();
    }

    class LooperThread extends Thread {

        public void run() {
            mHandler.sendEmptyMessage(MSG_WHAT_1);
            mHandler.sendEmptyMessageDelayed(MSG_WHAT_2, 2000);
            mHandler.sendEmptyMessageDelayed(MSG_WHAT_3, 3000);
        }
    }
}

大家可以看到我定义了一个全局的handler变量,定义了一个LooperThread,在run方法里直接发送了三条消息,
可以看一下运行效果,是先显示出“hello World!!!”过了两秒之后换成“goodBye world!!!”又过了三秒之后换成了“welcome world!!!”,是不是很奇特,那这究竟是为什么呢?请接着往下面看。
二、原理篇
好了,大家看了上面的demo之后可能会提出疑问,为什么我在子线程里发了三条消息可以在主线程里得到操作呢?这些操作和Looper有什么关系呢?这其中又是怎么运行的呢?好,下面我会带着大家一步一步揭开这神秘的面纱。
首先,我们先来看Handler这个类里面的sendEmptyMessage这个方法的实现:

 public final boolean sendEmptyMessage(int what) {
        return sendEmptyMessageDelayed(what, 0);
   }

可以看到这里面只是调用了sendEmptyMessageDelayed 这个方法,我们在上面也调用了这个方法:

 public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

可以看到,在这个方法里,构建了一个Message对象,并且将what这个值赋给msg,好,我们接着往下面看:

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

在这个方法体里,只是对传入的时间做了一些校验,然后直接返回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);
    }

这个方法里面只是校验了一下MessageQueue,然后返回enqueueMessage方法:

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

可以看到,这里面是将message的target赋值为Handler对应的实体,并且最后一句是将这个Message对象成功添加到MessageQueue这个队列里。
好了,那么这个时候有朋友就会问了,上面看见的只是将一个Message对象成功添加到MessageQueue的队列里,并没有出现调用Handler里面方法的地方啊?稍安勿躁,这里就要介绍一个比较隐蔽的知识点,构建Handler之前,一般都要加上Looper.prepare(),构建完必须调用Looper.loop()方法,子线程里都必须要这么使用,UI线程只是说系统已然为我们调用过了。OK,既然知道原理了,那么我们现在就来看Looper这个类里面的方法究竟干了什么吧:

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

我们可以看到调用Looper.prepare()这个方法会构建一个Looper的实例放置到ThreadLocal里,OK,我们继续看:

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.recycleUnchecked();
        }
    }

这段代码有点长,那我们就看主要部分,从上往下面看可以知道,这里面得到Looper对象,然后得到对应的MessageQueue对象,紧接着起了一个循环,不断的从MessageQueue里面取出Message,并且调用msg.target.dispatchMessage(msg);,从上面对sendMessage的分析我们知道,msg的target就是对应着一个handler的实例,那么现在可以进Handler类里面看对应的分发了,离真相越来越近了:

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

可以看到,在else里面调用了handleMessage(msg);这个方法,而我们最开始demo中恰恰是复写了这个方法,由此,整个流程就分析完毕。
那么我们可以整理一下上面分析的结果,会得到下面几个结论:
(1)除了主线程之外,其他线程使用Handler必须首先调用Looper.prepare()这个方法,实例完必须调用Looper.loop()这个方法。
(2)调用Looper.prepare()时会构建一个Looper对象并放置在ThreadLocal中,Looper在构建时会自动构建一个MessageQueue对象并持有。
(3)Handler对象在构造中,会持有Looper和Looper绑定的MessageQueue,保证之后发送的Message会被放置到对应的队列中。
(4)调用Looper.loop()方法,会从Looper持有的MessageQueue中不断的取出Message,并调用Message对应Handler实例dispatchMessage()方法分发。
所以如果在子线程里调用上面demo中的方法的话,就应该这么写了:

Looper.prepare();
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_WHAT_1:
                        vEditText.setText("hello world!!!");
                        break;
                    case MSG_WHAT_2:
                        vEditText.setText("goodBye world!!!");
                        break;
                    case MSG_WHAT_3:
                        vEditText.setText("welcome world!!!");
                        break;
                    default:
                        break;
                }
            }
        };

        new LooperThread().start();
        Looper.loop();

三、拓展篇
现在很多人都不会直接去new一个Thread,而是借助Handler去new一个Runnable,例如:

 new Handler().post(new Runnable() {
            @Override
            public void run() {
//这里执行一个操作
            }
        });

那这边的原理究竟是什么呢?其实很简单,跟上面我们分析的一样,下面给大家简单分析一下:

public final boolean post(Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

除了调用getPostMessage方法之外,就是发送一个Message,很正常。

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

将Runnable对象赋值给Message的callback,直接返回。

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

重点在这边,我们还记得上面给Msg的callback赋值了,所以应该调用hanleCallback方法。

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

果然,在这边直接调用callback的run方法,至此,大功告成!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值