欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android的消息机制(线程间通信)

程序员文章站 2024-03-24 22:35:28
...

一.概括

1.1 在Android中消息机制(线程间数据传递)

1.2 ThreadLocal的作用和工作原理

1.3 MessageQueue的工作原理

1.4 Looper的工作原理

1.5 Handler的工作原理

请仔细看代码中的中文注释,这些注释作为串联文章思想而存在。

二.先看图,有利于理解下面文字

这里参看5.1中代码

2.1“线程间数据传递参考方法调用”图

Android的消息机制(线程间通信)

2.2 线程间数据传递的示意图

Android的消息机制(线程间通信)

三.ThreadLocal的作用和工作原理

3.1ThreadLocal是什么和它的使用场景

ThreadLocal是一个线程内部的数据存储类(线程间数据传递的关键);

使用的场景:第一种:当有些数据是以线程为作用域并且不同的线程有不同的数据副本的时候,就可以考虑使用它。第二种:复杂逻辑下的对象的传递。

3.2使用场景的解释

3.2.1第一种

对于Handler来说,他需要获取当前线程的Looper,而Looper就是以线程为作用域的且不同的线程有不同的Looper,这时候使用ThreadLocal就可以轻松的实现Looper在线程中的存取。如果不采用ThreadLocal的话,那么Android系统就要提供一个全局的哈希表来供Handler查找指定线程的Lopper,这样一来就要提供一个全局的例如LooperManager的类,但是系统选择了使用ThreadLocal这种方式。

3.2.2第二种

比如监听器的传递,如果传递的层级很深的话,如果不采用ThreadLocal的方式那就要一层层的传递进去或者作为全局的静态变量。前者在层级较深的情况下是非常糟糕的写法而后者由于一个线程对应一个静态监听器可能造成大量监听器的情况也不可取。

3.3ThreadLocal的简单使用及其源码分析

3.3.1写一个简单的例子来说明ThreadLocal的神奇之处,虽然我们在开发过程中很少使用到他。

我们从ThreadLocal的源码可以看见他是一个泛型类

public class ThreadLocal<T>

这里我们写一个boolean的ThreadLoacl;在主线程中设置为true,第一个子线程中设置为false,第二个子线程中未设置,并分别打印log。

看下面的代码:

private ThreadLocal mThreadLocal = new ThreadLocal<Boolean>();

private void test() {
     mThreadLocal.set(true);
     Log.e("test1",mThreadLocal.get()+"");

     new Thread(){
         @Override
         public void run() {
             mThreadLocal.set(false);
             Log.e("test2",mThreadLocal.get()+"");
         }
     }.start();

     new Thread(){
         @Override
         public void run() {
             Log.e("test3",mThreadLocal.get()+"");
         }
     }.start();
}

log如下:

12-26 08:47:30.189 5045-5045/com.example.gongxiaoou.myapplication E/test1: true
12-26 08:47:30.192 5045-5313/com.example.gongxiaoou.myapplication E/test2: false
12-26 08:47:30.193 5045-5315/com.example.gongxiaoou.myapplication E/test3: null

从上面的例子我们可以看出:虽然是同一个mThreadLocal 实例,但是在不同的线程中获取的数据却是完全分离的,不同的。

3.3.2下面我们从ThreadLocal源码 看看为什么。这里我们只要查看get和set方法便可看出。

3.3.2.1 下面是set方法:

public void set(T value) {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取map
    ThreadLocalMap map = getMap(t);
    //不是空的就放进去
    if (map != null)
        map.set(this, value);
    else
        //重点:空的map就创建,并且以当前线程,和第一个添加数据为参数
        createMap(t, value);
}

先看map的获取如下:

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

很简单就是当前线程的一个成员变量来存储的。

下面我们看重点map的创建,看看t.threadLocals是如何赋值的:

void createMap(Thread t, T firstValue) {
    //创建了一个ThreadLocalMap并将它存储在了当前的线程中,完成t.threadLocals的赋值
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

下面是具体数据的存储如下:

ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

private Entry[] table;
static class Entry extends WeakReference<ThreadLocal> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}

ThreadLocalMap很简单的类,内部具体的实现是创建了Entry数组,并将第一个键值对存了进去。

3.3.2.2下面是get方法

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

也很简单先是获取当前的线程,然后map存在获取数据存储的map从中取出对应的数据;不存在初始化默认数据;下面看一下setInitialValue:

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

protected T initialValue() {
   return null;
}

上面的初始化一个null的数据存储进去并返回给调用者。

3.3.3总结:

从源码中我们看到ThreadLoacal的set和get方法,他们所操作的对象都是各自线程中的table数组,这也就不难理解为什么相同的mThreadLoacl实例在不同的线程中能过保持各自数据的独立性了。

四 MessageQueue的工作原理和源码

其实MessageQueue就是Android中所谓的消息队列,它的内部其实包含俩个操作enqueueMessage和next;前者是将要发送的消息添加到队列中后者则是从队列中取消息并将队列中对应的消息删除(其实就是一个真正的“取出”)。

下面我们来看看添加和取出方法的源码

4.1 enqueueMessage的源码:

boolean enqueueMessage(Message msg, long when) {
    //判断要使用的Handler是否为空
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    //使用状态判断
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    //下面就是单链表方式存储过程
    synchronized (this) {
        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.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            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;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

从上面的代码我们可以看出比较简单,主要就是将添加进来的message以单链表的方式存储。

4.2 next方法源码

 Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    //发现是个死循环,一直在检查是否要处理的message
    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;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            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;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

我们发现next方法是个死循环方法,如果队列中有消息那么“取出”,如果没有就阻塞在这里等待有消息再返回消息。

五.Looper的工作原理

5.1Looper的简单使用

我们知道在子线程中要自己创建Looper实例,创建完之后才能使用Handler(主线程的已经创建好了,所以我们不用管,后面分析主线程中Looper的创建),下面是一个简单的使用例子:

Handler handler;
private void test() {

    //子线程一
    new Thread("Thread1"){
        @Override
        public void run() {
            //5.2重要分析下面的代码
            Looper.prepare();
            //创建Handler实例
            handler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    String str = msg.obj.toString();
                    Log.e("MsgFromThread2",str);
                }
            };
            Looper.loop();
        }
    }.start();

    //为了延时第二个线程创建和消息发送
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    //子线程二
    new Thread("Thread2"){
        @Override
        public void run() {
            Message msg = handler.obtainMessage();
            msg.obj = "我是Thread2中发送过来的数据";
            handler.sendMessage(msg);
        }
    }.start();
}

log如下,比较简单。

12-26 14:32:46.128 20297-20361/com.example.gongxiaoou.myapplication E/MsgFromThread2: 我是Thread2中发送过来的数据

5.2Looper中的方法

5.2.1prepare方法,源码如下

//和三中的ThreadLocal联系起来了,初始话ThreadLocal里面存储Looper对象
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static void prepare() {
    prepare(true);
}

//创建Looper实例放入sThreadLocal中,等待被调用
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        //这里说明一个线程中只能有一个Looper实例,不能创建多个
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

//Looper构造方法
private Looper(boolean quitAllowed) {
    //创建消息队列MessageQueue的实例和获取本线程实例
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

5.3loop方法

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

    //重点,死循环将消息队列queue 中的消息取出交由msg.target(handler)处理
    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(handler)去分发消息,下面介绍Handler的时候具体分析
            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();
    }
}

loop方法其实主要只干了一件事就是循环的将消息队列中的消息取出交由“目的地”线程的handler去分发(其实这里面是个回调);

5.4 quit和quitSafely方法

二者的区别:

quit:直接退出Looper;

quitSafely:设定一个退出的标识,等待消息队列中的消息都处理完毕之后安全的退出。

Looper退出之后,通过Handler发送消息会失败,这个时候send方法返回false。在子线程中,如果手动为其创建Looper,那么在所有的事情完成之后应该调用quit方法来终止消息循环,否则子线程会一直处于等待状态,而如果退出Looper之后,这个线程就立即终止了。

六 Handler的工作原理

6.1使用Handler发送消息

使用Handler发送消息的方法很多这里包括:创建Handler方式;处理从其他线程返回的消息的“地点”不同

6.1.1 下面是通过其他实现方法;

前面介绍Looper中5.1中的例子是其中一种,我们暂且称为第一种方式,还有就是使用如下的方式:

第二种方式:子线程一中改变,子线程二中不变,如下

handler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        String str = msg.obj.toString();
        Log.e("MsgFromThread2",str);
        return true;
    }
});

第三种方式:子线程一中,handler创建的改变如下:

handler = new Handler(Looper.myLooper());

子线程二中的改变如下:

handler.post(new Runnable() {
    @Override
    public void run() {
        Log.e("MsgFromThread2","我是Thread2中发送过来的数据");
    }
});

6.1.2总结一下

上面的三种使用方法已经将Handler用法概括了,别的方法都可以归类其中。

第一种方式和第二种方式对比:前者派生了Handler的子类,而后者没有而是使用了CallBack回调接口的方式替代了派生Handler的子类的方式。

第三种方式和前两种的不同在于,前两种方式使用了默认的Looper,而后者通过指定Looper的方式(指定Looper就是指定目标线程)。

6.2 Handler中主要方法的源码解析

6.2.1先是Handler的三种构造方法

第一种:使用派生子类的方法

//默认的构造方法
public Handler() {
    this(null, false);
}

/**
 * @param callback 通过这个接口实现最终的线程间数据传递
 * @param async message中是否异步的标识(这里不必注意他) 
 */
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());
        }
    }

    //重点,从Looper中取默认线程的Looper(总这里可以解释,如果子线程未初始化对应的Looper那么就会抛出异常的问题)
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    //初始化Handler中的消息队列(从Looper中取)
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

第二种,使用回调接口替代派生子类的方式

public Handler(Callback callback) {
    this(callback, false);
}

这里传入了回调接口在这里面处理回调回来的Message。

第三种,使用指定的Looper为参数创建Handler

public Handler(Looper looper) {
    this(looper, null, false);
}

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

我们发现无论哪种方式都是为mLooper 赋值,为mQueue 赋值,为mCallback 赋值,为mAsynchronous 赋值。

6.2.2Handler发送消息(分两种),实现线程间的通信

6.2.2.1 对应Handler的第一种,第二种构造方式,这里先介绍发送消息的第一种方式send系列。这里以简单的sendEmptyMessage为起点分析,层层剥开如下:

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

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

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

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

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

看起来好像挺复杂,但是很简单从上到下最终调用到了MessageQueue的enqueueMessage,将消息添加到了消息队列中。

6.2.2.2对应Handler的第三种构造方法,第二种消息发送方式post方式,这里以最简单的post方发开始层层剥开。如下

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

//这里和6.2.2.1中第三层方法是一样的哦,下面的省略
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

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

上面可以看出:post方法中的Runnable最终也是包装为Message,只是作为Message中的回调接口存在。

6.2.2.3 发送消息方式总结:最终发送消息本质是将Message添加到了MessageQueue中。而通过上面介绍过的MessageQueue和Looper的知识我们知道Looper是在不停的将MessageQueue中的Message发送回Handler中的回调函数中(或者CallBack的回调函数中)的,从而实现了线程间数据的传递。

七.总结

经过上面的介绍相信对Android中线程间的通信都有了一个了解,这里再总结一下:ThreadLocal是线程间通信的关键;Handler作为绑定好Thread1的“搬运工”将另一个Thread2中的Message装入“传送带”MessageQueue中,MessageQueue在Thread1绑定好的Looper提供的“动力”下不停的运转,最终将Message传送到Thread1中。