《Android 开发艺术探索》笔记:(10)Android 的消息机制

28 篇文章 0 订阅
3 篇文章 0 订阅

一、Android 消息机制概述

Android 消息机制主要由 Handler,Looper,MessageQueue 三者组成。

1.1 他们的关系

  • MessageQueue 是 Looper 内的对象,Looper 是 Handler 内的对象。

  • 一个线程拥有一个 Looper,一个 Looper 拥有一个消息队列,一个 Looper 可以服务于多个 Handler。

( 一个线程拥有一个 Looper 的原因是在添加第二个 Looper 时会抛出异常,其实 ThreadLocal 的机制其是允许存在两个 TreadLocal<Looper> 的)

1.2 Handler 为什么能用来进行线程间沟通

消息机制用来在两个线程之间沟通,既然是沟通,那么就需要一个桥梁,Android 里这个桥梁就是 Handler。

前提条件:

  1. 线程之间共享的区域有堆、方法区。
  2. 变量按作用域可分为:静态变量,成员变量和局部变量。
  3. 静态变量存放在方法区中,成员变量存放在堆中,局部变量存放在虚拟机栈中。

我们一般将 Handler 作为成员变量,所以,两个线程都能够对它进行访问,从而进行沟通。

二、Android 消息机制分析

2.1 ThreadLocal 的工作原理

ThreadLocal 用于为线程提供线程私有变量。具体解析可见Java 基础:ThreadLocal 解析

Android 中用 ThreadLocal 为 Thread 添加了一个 Looper 类型的成员变量。
这里写图片描述

ps:上图中的 ThreadLocal.Values 在后来的版本改为 ThreadLocal.ThreadLocalMap,但含义一致

Thread 中有一个 ThreadLocalMap 类型的成员变量 threadLocals,代表一个 ThreadLocal->Object 的映射。

Looper 中有一个 ThreadLocal<Looper> 类型的静态变量 sThreadLocal,用于在各线程的 ThreadLocalMap 中作为 key,通过这个 key,可以 set、get 一个 Looper 类型的 Object。

  • set 时,sTreadLocal 会找到当前线程的 threadLocals,为它添加一个 Looper 对象。

  • get 时,sTreadLocal 会找到当前线程的 threadLocals,从它中间里面去get()。

在调用 Looper.prepare() 方法时,调用 sThreadLocal 的 set 方法,从而为当前线程添加一个 Looper 对象。

主线程 ActivityThread 运行时,会调用 Looper.parpareMainLooper() (主线程特殊的 Looper.prepare()),同样是为主线程添加了一个 Looper 成员变量。

//ActivityThread.java
Looper.prepareMainLooper(); // 会调用 Looper.prepare(false);

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

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

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

创建 Handler 时,Looper.myLooper() 方法会调用 sThreadLocal 的 get 方法,找不到会报错,在主线程中,因为已经创建,一定找得到,找到 Looper,相应的消息队列也就找到了。

//Handler.java
mLooper = Looper.myLooper();

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

//ThreadLocal.java
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();
}

使用时,在子线程中利用 Handler.sendMessage() 发出消息,发出消息的动作会将消息存入 MessageQueue 中,Looper 时刻监督着 MessageQueue,这时它就会启动,将消息再传给Handler,Handler 来处理。

2.2 MessageQueue 的工作原理

MessageQueue 包含两个操作:插入(enqueueMessage)和读取(next)。读取本身会伴随着删除操作。

MessageQueue 是用单链表实现的,单链表在插入删除上比较有优势。

enqueueMessage 主要就是单链表的插入操作,主要看一下 next 方法。

Message next() {
    ......
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                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);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }
			......
		}
		......
    }
}

for (;;)可以发现 next 方法是一个无限循环的方法,如果没有消息,next 方法会一直阻塞在这里。当有新消息到来时,next 方法会返回这条消息并将其从单链表中删除。

next 方法在主线程无限循环,而没有导致 ANR,是因为在 nativePollOnce 方法中执行了阻塞操作。主线程大多数时候都是处于休眠状态,并不会消耗大量 CPU 资源。

nativePollOnce 的阻塞采用 Linux pipe/epoll 机制。当执行 nativePollOnce 方法后,如果没有消息,主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。

线程阻塞的情况分三种:

等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

nativePollOnce 就是一个 I/O 请求阻塞。

2.3 Looper 的工作原理

2.3.1 Looper 的创建

Looper.prepare() 可以为当前线程创建一个 Looper 成员变量。

Looper.prepareMainLooper() 可以为主线程创建一个 Looper 成员变量。

当 Looper 的构造方法中它会创建一个 MessageQueue。

2.3.2 Looper 的循环

Looper.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 traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            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();
    }
}

可以看出,Looper.loop() 中开启了一个无限循环,里面调用了 MessageQueue 的 next 方法,上面说过,next 方法是一个阻塞操作,没有消息时,next 方法会阻塞,于是 loop 方法也阻塞在那里。

有消息时,Looper 就会处理消息,msg.target.dispatchMessage(msg);,这里的 msg.target 就是发送消息的 Handler,它的 dispatchMessage 方法运行在当前线程的 Looper 中,这就完成了线程切换。

2.3.3 Looper 的退出

Looper 的 quit()、quitSafely() 都可以用来退出。quit() 直接退出,quitSafely() 在处理完现有消息后才退出。

退出 Looper 后,线程就会立即终止。

quit()、quitSafely() 会调用 MessageQueue 的 quit() 方法,之后 next 方法就会返回 null,next 的无限循环、loop 的无限循环都随之结束,Looper 退出。

2.4 Handler 的工作原理

handler 的工作原理包括发送和处理消息。

发送使用 post 和 send 方法,post 最终也是调用 send 方法。

send 方法中通过 queue.enqueueMessage 将消息插入消息队列。之后 next 方法就会将消息传给 Looper,Looper 又调用 Handler 的 dispatchMesaage 方法进行处理。

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

三、主线程的消息循环

主要包括:
主线程、 Binder 线程、主线程的 Handler、主线程的 Looper、主线程的 MessageQueue。

3.1 主线程

主线程就是 ActivityThread 所在的线程,ActivityThread 本身并不是一个线程,承载ActivityThread 的主线程是由 Zygote fork 而创建的进程。

ActivityThread 的入口方法为 main 方法。

public static void main(String[] args) {
    ......
    Process.setArgV0("<pre-initialized>");

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

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

主要过程:

调用 Looper.prepareMainLooper() 来创建主线程的 Looper 以及 MessageQueue。

生成 ActivityThread 对象后,调用 thread.attch(false),创建一个 Binder 线程(ApplicationThread 在其中运行,它是 Binder 的服务端,用于接收系统服务 AMS 发送来的事件),该 Binder 线程通过 Handler 将 Message 发送给主线程。

调用 Looper.loop() 开启主线程的消息循环。

3.1.1 主线程的 Handler

主线程中的 Handler 为 ActivtyThread 类中的内部类 H。H 中定义了一组消息类型,包含了四大组件的启动和停止。

private class H extends Handler {
    public static final int LAUNCH_ACTIVITY         = 100;
    public static final int PAUSE_ACTIVITY          = 101;
    public static final int PAUSE_ACTIVITY_FINISHING= 102;
    public static final int STOP_ACTIVITY_SHOW      = 103;
    public static final int STOP_ACTIVITY_HIDE      = 104;
    public static final int SHOW_WINDOW             = 105;
    public static final int HIDE_WINDOW             = 106;
    public static final int RESUME_ACTIVITY         = 107;
    public static final int SEND_RESULT             = 108;
    public static final int DESTROY_ACTIVITY        = 109;
    public static final int BIND_APPLICATION        = 110;
    public static final int EXIT_APPLICATION        = 111;
    public static final int NEW_INTENT              = 112;
    ......
}

3.2 消息循环

ActivityThread 通过 ApplicationThread 和 AMS 进行进程间通信,AMS 以进程间通信的方式完成 ActivityThread 的请求后会回调 ApplicationThread 中的 Binder 方法,然后 ApplicationThread 会向 H 发送消息,H 收到消息后会将 ApplicationThread 中的逻辑切换到 ActivityThread 中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值