Android学习笔记之消息处理机制

一、消息处理机制的四个组成部分

Android中的消息处理机制主要由四个部分组成:Message、Handler、MessageQueue 和 Looper。

1.Message

Message用于在线程之间传递消息,它可以在内部携带少量的信息。我们常用Message的what字段以及arg1、arg2字段来携带一些整型数据,使用Message的obj字段来携带一个Object对象。

2.Handler

Handler用于发送和处理消息。通过sendMessage()方法发送消息,通过handleMessage()方法处理消息。

3.MessageQueue

MessageQueue是消息队列的意思,它用于存放Handler发送出来的消息。这些消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。

4.Looper

Looper是每个MessageQueue的管家,当我们调用了Looper的loop()方法之后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在一条消息,便会将它取出并传递到Handler的handleMessage()方法中。每个线程中也只有一个Looper对象。

PS:之后我会对它们进行更加详细的讲解,目前只用大致了解概念即可

二、消息处理的简单运用

运用场景:通过点击一个按钮模拟请求网络之后,通过Message通知UI线程更新UI。

1.在子线程中发出Message

public void onClick(View v) {
    switch (v.getId()){
        case R.id.btn_download:
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //模拟请求网络下载文件
                        Thread.sleep(8000);
                        Message message = new Message();
                        message.what = DOWNLOAD_TAG;
                        handler.sendMessage(message);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
            break;
        default:
            break;
    }
}

2.在主线程中处理Message

private Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what){
            case DOWNLOAD_TAG:
                tvDisplay.setText("Download Complete");
                break;
            default:
                break;
        }
    }
};

PS:也可以使用将异步处理机制封装好的runOnUiThread()方法切换到主线程更新UI:

//模拟请求网络下载文件
Thread.sleep(8000);
runOnUiThread(new Runnable() {
    @Override
    public void run() {
        tvDisplay.setText("Download Complete");
    }
});

通过上面这个简单的例子可以看到,我们在主线程中创建了Handler,之后开启子线程处理一些耗时操作,最后发送Message,Handler便会自动地去处理这条消息,然后显示出“Download Complete”的文本。这么一来便实现了跨线程的通讯。但是我们肯定会有很多疑问,比如我们就只看到了Message 和 Handler 不是说有四个组成部分吗?MessageQueue和Looper呢?不是说调用了Looper的loop()方法之后才能处理MessageQueue中的Message吗?我怎么没看到呢?诸如此类的问题可能会有很多,先别急,往下看。

首先,上面的例子比较特殊,是因为Handler创建在主线程,一般的线程默认是没有 Looper 的,如果要使用 Handler,则必须为线程创建 Looper,但是主线程被创建时就会初始化 Looper,因此主线程中默认可以使用 Handler。其次,从开发者的角度来说,Handler 是Android消息机制的上层接口,这使得在开发过程中只需要和Handler交互即可。因此即使不知道MessageQueue和Looper,也不妨碍我们使用它。

话说回来,Android 的消息处理机制主要是指 Handler 的运行机制,Handler 的运行需要底层的 MessageQueue和Looper 的支撑。因此想要了解Android的消息机制也就需要了解MessageQueue和Looper的工作过程。

三、消息处理机制的概述

在讲消息处理机制之前,先解决一个问题:为什么Android系统不允许子线程访问UI?这是因为Android的UI控件不是线程安全的。如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。那么为什么不用同步锁?其原因有二:

  1. 同步锁会让UI的访问逻辑变得复杂
  2. 同步锁会降低UI的访问效率

接下来,简单讲下 Handler 的运行机制。Handler 创建完毕后(当然当前线程得先有Looper) ,这个时候其内部的 Looper 以及 MessageQueue 就可以和 Handler开始协同工作了。每当 Handler 发送一个消息,MessageQueue 就会将消息存入队列,而 Looper 作为他们三最累的那个,它会不停地查看队列中有没有消息,如果有新消息了,它便将消息拉出队列,看这个消息是哪个 Handler 发出的,再交由对应 Handler 去处理消息。可能你从上面的描述中看不出来它哪里跨线程了,这是因为实际使用的时候 Handler 的创建和发送消息通常是分别处于不同线程中的。

四、ThreadLocal 的工作原理

ThreadLocal是一个前文未提及的概念,因为就它本身而言,并不局限于消息处理机制。我们先来看看它是怎么进入到我们的视野里的。比如我们要在一个子线程中使用Handler,首先得创建Looper,然后调用其loop()方法,如下:

Looper.prepare();
Handler handler = new Handler();
Looper.loop();

这时我们去看下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));
}

而 sThreadLocal 就是我这里说的 ThreadLocal。虽然名字里有个Thread 但它不是线程,它是一个线程内部的数据存储类,因此它的作用是可以在每个线程中存储数据。这里的泛型指定的是Looper,因此它所存储的数据也是Looper:

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

它神奇的地方在于不同线程对于同一个 ThreadLocal 的变量 sThreadLocal 调用其get()方法获取的数据是不一样的。因此在消息处理机制中,通过 ThreadLocal 可以在不同的线程中互不干扰的获取每个线程的Looper。究其原因,是因为他有一个内部类ThreadLocalMap,而这个map是根据线程的不同而不同的,然后数据是存储在map中的一个数组里面的(table)。ThreadLocal  的 set() 源码如下:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

可以看到它通过getMap()方法根据当前线程去获取对应的map变量,然后将数据存入,如下:

private void set(ThreadLocal<?> key, Object value) {

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

ThreadLocal 的 get() 方法也是类似,先根据当前线程获取对应的map,然后从map中取值。如下:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

五、MessageQueue 和 Looper 的工作原理

当我们通过Handler post 一个 Runnable 或者 send 一个 Message 时,它们最终都会调用 MessageQueue 的 enqueueMessage 方法。而enqueueMessage的实现,主要操作就是单链表的插入操作。

当我们调用Looper.loop()时,其内部会无限循环的调用 MessageQueue 的 next() 方法。而next方法也是一个无限循环的方法,如果消息队列中没有消息,那么next方法便会一直阻塞在这里。当有新消息来到时,next方法便会返回这条消息并将其从单链表中移除。

接着来看Looper,之前说到在一个子线程中使用Handler之前要调用 Looper.prepare() 方法,这个时候是会新建一个 Looper 对象的:

sThreadLocal.set(new Looper(quitAllowed));
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

我们可以看到,在Looper的构造器中,它会创建一个 MessageQueue 即消息队列,然后将当前线程的对象保存起来。接着来看Looper的 loop 方法,只有调用了 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
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        final long end;
        try {
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        if (slowDispatchThresholdMs > 0) {
            final long time = end - start;
            if (time > slowDispatchThresholdMs) {
                Slog.w(TAG, "Dispatch took " + time + "ms on "
                        + Thread.currentThread().getName() + ", h=" +
                        msg.target + " cb=" + msg.callback + " msg=" + msg.what);
            }
        }

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

可以看到,loop 方法是一个死循环,唯一跳出循环的方式是 MessageQueue 的 next 方法返回了null。当我们去调用 Looper.myLooper().quit() 方法或者 Looper.myLooper().quitSafely() 方法时,它们都会通知消息队列退出,当消息队列被标记为退出状态时,它的next方法便会返回null。因此,当一个子线程的工作结束后,记得退出Looper。接着看loop方法的后续,可以看到它通过msg.target.dispatchMessage(msg) 方法将处理消息的任务交给了 msg.target ,而这就是发送这条消息的Handler对象。如下:

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

六、Handler 的工作原理

Handler 的工作主要包含消息的发送和接收过程。消息的发送可以通过 post 的一系列方法以及 send 的一系列方法来实现,其中post 的一系列方法最终是通过send的一系列方法来实现的。通常发送一条消息都是由 sendMessage 方法开始,经过一系列的委托调用转交给了 enqueueMessage 方法,也就是上一节中最后的那个代码块,可以看到最后 Handler 将工作传递给了消息队列,让它在单链表中插入消息。而这个消息最终会在无限循环的 next 方法中返回给 Looper。然后在上一节的分析中可以看到 Looper 拿到消息之后调用了msg.target.dispatchMessage(msg) 。因此最后又回到了Handler的手上。dispatchMessage的实现如下:

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

其处理消息的过程如下:

  1. 首选判断 Message 的 callback 是否为null,这个callback 实际上是post系列方法所传递的Runnable参数,如果非空,则调用handleCallback进行处理,其实就是调用了Runnable的run方法
  2. 其次判断 mCallback 是否为 null,如果非空,则调用mCallback 的 handleMessage 方法来处理消息。而 Callback 是一个接口,我们可以用它来创建一个 Handler 对象而不需要派生 Handler 的子类:Handler handler = new Handler(callback)
  3. 最后调用 Handler 的 handleMessage 方法来处理消息
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值