Android消息机制

本来在看WMS,但是中途发现Android消息机制有点不明白,于是记录下学习过程。本文主要讲解java层的消息机制,native层的还未设计,后期也会尝试深入学习下,坑先挖起来,后面有能力就埋,没能力就先等着。
Android消息机制分为三个部分:1、Looper;2、Message;3、Handler

1、 Looper:

Looper顾名思义就是一个循环,其实所有的UI线程都是在一个死循环中一直在等待事件,并通过事件来触发操作。事件可以简单的理解为触摸、键盘、中断等等;这里的入口,就选择在ActivityThread.java的main函数,

public static void main(String[] args) {
Looper.prepareMainLooper();//(1)创建Looper
………………………………
//实例化ActivityThread
ActivityThread thread = new ActivityThread();
//初始化并且通知AMS,startSeq为启动序列号,AMS会为每一个
//待启动的进程生成一个唯一标志
    thread.attach(false, startSeq);

//初始化主线程的Handler,其实主线程就是一个一直在等待消息的循环体,
//所以必须初始化Handler,保证其收到消息后执行该做的任务
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Looper.loop();

//主线程理论上不会出上面的循环,执行到这里肯定是出错了
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

a)、创建Looper

首先看(1)处,如何创建一个Looper,从代码中可以看到,prepare Looper的时候会先调用sThreadLocal.get()去获取Looper,如果能获取到就直接抛出异常。这里就可以看出,一个线程其实只能有一个Looper,如果没获取到,则会创建一个新Looper并放入sThreadLoacl中。

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {//(2)
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));4}
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

b)、ThreadLocal类及其原理(这块不看也不是很影响消息机制的理解,可直接跳到c模块)

这块内容比较多,先从(2)处的sThreadLoacal说起,看定义可知这是一个ThreadLocal类型的变量,即一个泛型为Looper的ThreadLocal变量,查找ThreadLocal.java文件,查看定义。
static final ThreadLocal sThreadLocal = new ThreadLocal();
在文件中发现get()方法的定义如下:

public T get() {
    Thread t = Thread.currentThread();//获取当前线程
//通过当前线程获取一个ThreadLocalMap类型变量map
    ThreadLocalMap map = getMap(t); //(3)
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

于是又发现了新的类,ThreadLocalMap,这里真的是一环套一环,东西特别多,我也不一定能说清楚,大家能看懂更好,我尽力说一下我的理解吧。 首先ThreadLocalMap底层实现其实是通过一个名为table的数组实现的(初始大小为16),table数组中的每个元素是Entry类型。这里差插一个图和代码以便理解:

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
//通过hash取得新添加的Entry的序号
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

在这里插入图片描述

其中Entry类型的定义如下所示:Entry相当于map中的一对数据,key-value一对的,如下定义也可看到,Entry中的key就是ThreadLocal类型变量,value是后面说的,通过ThreadLocal.set()方法设置的一个数据类型,这边直接用Object类,所以他可以是任何类型。不过这里Entry的实现可不仅仅是简单的key-value模式,这里深挖代码还会关联到引用相关,这里先不细说。

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
所以回到上面的(3)处,getMap()方法获取到的什么呢?
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
ThreadLocal.ThreadLocalMap threadLocals = null;

通过定义看到返回的是t.threadLocals,再去Thread.java中查看threadLocals的定义,发现就是ThreadLocalMap类型,这里也可以看出,每个Thread都会有一个ThreadLocalMap类型的属性。于是(3)处的逻辑就清晰了。就是返回当前线程t的ThreadLocalMap属性。然后再于ThreadLocalMap中查找key为sThreadLocal的Entry,这里因为泛型为Looper,所以value的类型就是Looper,返回的result就是Looper类型变量。
从这也可以看出,ThreadLocal为变量在每个线程中创建了一个副本,不同的线程之间threadLocal变量是不共享的。

回到(2)处,即如果当前线程已经有Looper就会抛出异常,若无就进入到(4)处,新建Looper,并将其放入ThreadLocalMap中。这里再看下set方法,毕竟有get必定会有set,不让我们获取什么呢?

public void set(T value) {
//下面两行不再赘述,获取当前线程的ThreadLocalMap
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

查询到ThreadLocalMap后,将当前的sThreadLocal和Looper作为Entry放入其中。

c)、Looper的创建:

Looper的创建很简单,首先新建一个MessageQueue,然后把Looper和当前的线程进行关联。

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

所以这里我们就可以看到Message和Looper的关联了,MessageQueue简单点说就是维护了一个Message类型的队列,而Looper就是一直在循环检查MessageQueue中是否有新消息,一旦有新消息就把他拿出来进行处理。

d)、Looper在干嘛:

public static void loop() {
//获取当前线程的ThreadLocalMap中以sThreadLocal为key的Looper对象
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    if (me.mInLoop) {
        Slog.w(TAG, "Loop again would have the queued messages be executed"
                + " before this one completed.");
    }
    me.mInLoop = true;
……………………………………………………………
    for (;;) {//这里可以看出来是个死循环,只有当if里为true才会退出
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}

private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;//退出的一种情况
    }
    try {
        msg.target.dispatchMessage(msg);//根据消息的目标分发消息
    } catch (Exception exception) {
        if (observer != null) {
            observer.dispatchingThrewException(token, msg, exception);
        }
        throw exception;
    }
    }
    msg.recycleUnchecked();//消息放回消息池中
    return true;
}

如上,loop方法的核心功能就是向消息的target分发消息,这里先不看如何分发,因为handler中会讲到。
所以综上,其实主线程就是一个死循环,因为他会一直阻塞在Looper.loop方法中,除非碰到异常才会退出,不过也会有小伙伴会问,那既然主线程一直在死循环,那怎么响应点击事件呢? 这个问题就涉及到epoll机制了,我暂时还没看,有机会再说吧。总之epoll机制就是能有一种监听的功能,当有事件发生的时候,就会去处理事件,没事件发生时就会一直在监听我们添加到监听列表里的各种事件,就比如是触摸事件,这个功能好比一个保安,当有坏人、可疑人员、发生什么袭击事件,他就会报警或者关上大门保护公司里的安全。

2、 Message:

a)、消息的基础属性和方法:

在Looper中已经讲到了Looper和Message之间的关系,再看看Message有啥。
首先 从属性开始看起,最常见的几个属性:

public int what; //一般根据what判断handler执行的功能
public int arg1;//参数之一不知道啥用处
public Object obj; //可以传入很多东西,因为类型是object
public long when;  //消息的处理时间
Handler target;   //消息的目标handler
Runnable callback;  //消息可以是一个回调函数
Message next;//消息链表中的下一个消息
Message sPool;  //消息池

下面再看下消息的创建,这是默认创建函数,是从消息池中获取一个消息,用消息池的好处就是避免了消息的频繁创建与销毁,将消息池的顶部Message放回,并将容量减1;若无消息池,则直接新建一个消息,这里的构建函数各种各样,传参的构建函数就会将参数中的一些值赋值给我们新创建的消息了。

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

其中recycleUnchecked()方法就是将消息的信息都清空,然后返回消息池中。
这里要说的一个属性就是Target,Target属性是消息的目标,理解的含义就是消息需要分发给谁,后面可以看到,target属性会在sendMessage的时候赋值。

b)、同步消息和异步消息:

public void setAsynchronous(boolean async) {
    if (async) {
        flags |= FLAG_ASYNCHRONOUS;
    } else {
        flags &= ~FLAG_ASYNCHRONOUS;
    }
}

可通过setAsynchronous()方法给消息设置消息是否是同步消息。而将消息设置为同步消息或者异步消息。 这个名字也很直接,消息分为两类,同步消息和异步消息,同步消息和异步消息一般情况下没有区别,只有特定的场景才会有差别。
当开启了同步屏障后就会让异步消息优先被处理,同步消息则只能在消息队列中继续等着,直到移除同步屏障才能继续得到处理。
消息其实分为三类:同步消息、异步消息、屏障消息。前两者的区别已经说了,屏障消息的不同之处就是消息的Target为null,下面看下发送同步屏障的方法postSyncBarrier(long when)是怎么实现的吧:

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
//屏障不是只有一个,会有好多个,那怎么区分呢?就靠这个唯一的标志token
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
//MessageQueue的顺序是按照时间来的,这里就按照时间把屏障消息插入
//MessageQueue当中。
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
//返回屏障消息的唯一标志tokne,用于后面关闭屏障消息
        return token;
    }
}
//下面就是移除屏障消息了,可以看懂这里传入的参数token就是
//前面创建的时候返回的唯一标志
public void removeSyncBarrier(int token) {
    // Remove a sync barrier token from the queue.
    // If the queue is no longer stalled by a barrier then wake it.
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
//往下寻找对应token的屏障消息
        while (p != null && (p.target != null || p.arg1 != token)) {
            prev = p;
            p = p.next;
        }
        if (p == null) {
            throw new IllegalStateException("The specified message queue synchronization "
                    + " barrier token has not been posted or has already been removed.");
        }
        final boolean needWake;
//移除屏障消息
        if (prev != null) {
            prev.next = p.next;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycleUnchecked();
        // If the loop is quitting then it is already awake.
        // We can assume mPtr != 0 when mQuitting is false.
        if (needWake && !mQuitting) {
            nativeWake(mPtr);
        }
    }
}

上面的屏障消息的入队出队流程,其实和正常消息的流程也差不多。都是根据消息的when属性进行排序,然后出队的话就是根据消息的一些标志去找到相应的消息,然后移出MessageQueue。

c)、消息队列的next方法:

我们在处理完Message后,是不是任务就完成了?当然不是啦。处理完Message后还需要去看看这个消息是不是还有下一个消息,那怎么去访问下一个消息呢?

Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        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;
	//这里还记得不,Target为null不就是屏障消息嘛对吧
            if (msg != null && msg.target == null) {
	//如果是屏障消息的话,就执行下面的步骤
                do {
	//一直往下寻找,直到找到异步消息
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
	//找到异步消息了
            if (msg != null) {
                if (now < msg.when) {
	//还没到我们希望消息被执行的时间,先等一会,等时间到了再执行
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {                    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 {//遍历到队尾都没找到异步消息
                nextPollTimeoutMillis = -1;
            }
	//如果消息队列退出
            if (mQuitting) {
                dispose();
                return null;
            }
	//这里都是在等有消息可以执行
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }
//下面是关于mPendingIdleHandlers的处理,我不是很了解,暂时没看明白
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to 
            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}

所以从上面的介绍看出,1、MessageQueue是根据Message.when进行排序的,并且mMessages总是指向这个链表的头部; 2、Looper会一直遍历MessageQueue,有消息就会取出,没消息就会阻塞; 3、MessageQueue当中会有三种消息:同步消息、异步消息、屏障消息,通过屏障消息可以使异步消息优先执行。

3、 Handler:

a)、构造方法:

先看到Handler的构建函数,其中可以看到的几个点:1、handler中会持有一个Looper属性,也就是说一个handler对象只会关联一个looper对象,反过来就是一个looper对象可以对应多个handler对象; 2、Handler还会持有对应Looper的MessageQueue对象。

public Handler(@Nullable 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 " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

b)、dispatchMessage:

再看到Handler的dispatchMessage方法,那就话不多说直接看到这个方法吧,不过看到后发现,其实在处理消息的时候还是有个先后的顺序的,先会检查消息的callback,再检查Handler的callback,最后再执行到Handler的handleMessage方法,而这个handleMessage还是一个空方法,需要我们在创建Handler的时候进行重写。

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

这里消息的callback其实就是把callback当做消息直接发送,这里也可以直接看到Handler类中post(Runnable r)方法,就是直接将runnable直接作为参数发送出去的方法,这里后面就会通过调用该方法的handler和参数r创建一个消息。

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

c)、sendMessage:

发送消息就是post和sendMessage方法,不过这一大堆方法最后都会走到下面这个方法中:

public boolean sendMessageAtTime(@NonNull 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);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
//将消息的target设置为当前Handler
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
    //设置异步消息
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
//将消息放入当前Handler关联的MessageQueue,等待Looper处理
    return queue.enqueueMessage(msg, uptimeMillis);
}

上述sendMessageAtTime()方法,最后就是将消息放入了当前Handler相关联的Looper的MessageQueue当中,最后等待该Looper去处理该消息。

4、总结一下

综上,举个例子,再线程A中希望发送一个消息给到线程B,那需要做什么呢?
1、 创建一个Handler,关联到线程B的Looper,这里有两个方案:直接再Handler构造函数中传入B的looper、 再线程B中创建Looper再从线程A中获取到该Handler B;
2、 生成需要的Message消息;
3、 通过和线程B的Looper关联的Handler B,将消息发送出去;
4、 等待消息被B线程的Looper执行,这里执行的方法也是通过callback或者重写Handler B中的handleMessage方法去实现的。

在这里插入图片描述

再说一下三者的关系:1、一个线程最多只有一个Looper;2、一个Looper有且仅有一个MessageQueue;3、一个Looper可以对应多个Handler,但是一个Handler只能对应1个Looper(MessageQueue)。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值