美文网首页Android开发Android进阶开发互联网科技
Android多线程源码详解一:handler、looper、m

Android多线程源码详解一:handler、looper、m

作者: 881ef7b85f62 | 来源:发表于2019-04-09 20:53 被阅读86次

    之前面试,面试官问到多线程通讯,巴拉巴拉说了些基础实现后面试官问handlerThread的底层实现,就卡住了。所以把Android多线程的知识点复习整理一下,写出来加深印象。

    Android多线程通讯的核心是handler、looper、message、messageQueue,这篇文章就先记录下这套系统的源码要点,具体的实现方法下一篇文章再写。

    内容为自己看源码的理解,如有问题,欢迎留言探讨,共同进步。

    Thread

    用法一:

    handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    switch (msg.what) {
                        case 1:
                            mThread.setText(msg.obj.toString());
                    }
                }
            };
            ...
    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            Log.d("coco", "thread:" + Thread.currentThread().getName());
                            Message message = handler.obtainMessage();
                            message.obj = "thread_msg";
                            message.what = 1;
                            handler.sendMessage(message);
                        }
                    }).start();
    主线程中初始化handler,实现handleMessage,子线程中sendMessage,实现通讯。(ps:handler内存泄漏后面写)
    
    **方法二:**
    
    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            Message message = Message.obtain(handler);
                            message.obj = "thread_msg1";
                            message.what = 1;
                            handler.sendMessage(message);
                        }
                    });
    

    这种方法跟第一种实现原理是一样的,直接返回sendMessageDelayed(getPostMessage(r), 0),通过getPostMessage从Runnable中获取message,然后放到messageQueue中。

    handler

    handler是多线程通讯的控制器,负责消息的发送与处理,handler的初始化代码如下:

    //FIND_POTENTIAL_LEAKS为常量,值为false,即第一个if语句不会执行。内部的代码逻辑是判断handler的创建方式,决定是否需要打
    //印内存泄漏的log,如果是该handler对象是通过匿名类、成员类、内部类、非静态类的话,有可能造成内存泄漏,需要打印log
    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进行判空,如果为空就抛出异常,所以如果在子线程中初始化handler,一定要先初始化looper,主线程在系统创建时就初
    //始化了looper,所以可以直接创建handler。
    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;
    

    mQueue是获取的mLooper的mQueue,所以mQueue也是当前线程相关的,具体原因在looper的源码分析中会讲。mAsynchronous是判断是否有异步消息,Android会优先处理异步消息,具体的实现在messageQueue中会讲到。

    public final Message obtainMessage()
        {
            return Message.obtain(this);
        }
    

    obtainMessage方法是从message的公共池中取出一个message,相对于直接new出来,效率更高。

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

    dispatchMessage方法是handler在接收到message后进行分发时调用的,msg.callback是一个Runnable对象,在message创建时传入,或者通过setCallback方法设置,默认为空;mCallback是Callback对象,在handler初始化的时候传入,默认也为空。所以没有特定设置的情况下,会直接走到handlerMessage中,即我们创建handler时复写的回调方法。

    looper

    looper的主要成员变量如下:

    MessageQueue mQueue跟looper绑定的消息队列。

    Thread mThreadlooper所在线程对象。

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

    sThreadLocal的数据结构为ThreadLocal
    ,在prepare中首先判断sThreadLocal是否为空,表明一个线程只能有一个looper对象,符合单例模式的设计思想。

    sThreadLocal.set(new Looper(quitAllowed))该方法是new一个Looper,并将该Looper与当前线程的threadLocalMap关联起来,所以该looper属于调用prepare方法的线程。

    接下来是最重要的loop方法,loop与prepare方法都是静态方法,通过Looper.prepare跟Looper.loop调用即可,所以在loop开始的时候要先获取当前thread的looper与messageQueue。

    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;
            Binder.clearCallingIdentity();
            ...
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    return;
                }
                ...
                try {
                    msg.target.dispatchMessage(msg);
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
                ...
                msg.recycleUnchecked();
            }
        }
    

    Binder.clearCallingIdentity()方法是为了清除当前线程传入的IPC标识,如果在其他线程传入IPC请求,当前线程又要调用当前线程的本地接口,先清除传入的IPC标识,那么调用本地接口时就不需要进行权限验证。

    然后通过 for(;;) 进行无限循环,直到queue.next不为空,接着调用target(即当前looper绑定的handler,在handler初始化的时候绑定)的 dispatchMessage(msg) 方法,之后走到初始化handler时复写的 handleMessage 中。

    最后通过 recycleUnchecked() 将当前的msg放入到消息池中。

    threadLocal、threadLocalMap
    threadLocal是一个数据对象类,由该类实例化的对象是线程相关的,即不同线程通过同一个threadLocal对象操作的也是各自的线程的备份数据,该功能是由threadLocalMap实现。

    threadLocalMap是一个自定义hashmap,内部持有一个tables变量,类型为Entry[]。Entry为threadLocalMap内部类,继承了ThreadLocal的虚引用,以便实例化的ThreadLocal对象在不用时可以回收;内部只有一个成员变量value,这里的结构为Looper。

    static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    

    threadLocalMap是Thread的一个变量,所以每一个线程只有一个threadLocalMap。

    threadLocal的操作都是以threadLocalMap来实现的,如get()方法,首先获取当前Thread,然后通过获取Thread的threadLocalMap,然后map.getEntry(this)(传入this是因为自定义hashmap的hash算法需要用到threadLocal中的threadLocalHashCode变量)获取当前线程对应的value(looper),以保证在子线程中处理的looper、message都是主线程的looper、message,避免了不同线程数据的同步问题。

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

    其他的set、setInitialValue等方法也是跟get类似,通过threadLocalMap实现。

    由上面的代码可以看出,整个多线程通讯的核心就是threadLocal与threadLocalMap。以普通的子线程发送消息,主线程接收消息的demo为例:handler在主线程创建,所以在初始化时绑定的looper是主线程的looper;主线程的looper在初始化的时候调用prepare,跳转到构造函数创建实例的时候会创建messageQueue并绑定,所以messageQueue也是对应的主线程的looper的内部队列;message无论是obtain还是new出来的,在通过sentMessage发出后,会绑定到当前handler上。综上所述,虽然消息的创建与发送都是在子线程中完成,但由于threadLocal机制,这一系列的实例都是在主线程中完成的,所以不会有不同线程通讯的同步问题。

    message

    message是handler机制中的信息载体,实现了Parcelable接口,主要通过一下变量保存数据:

    int what整形变量,让接受者区分message的标识。

    int arg1, arg2整形变量,可存储简单的int数据。

    Object obj发送任意Object对象给接受者。

    targetmessage所关联的handler

    message的初始化推荐通过Handler.obtainMessage()或者Message.obtain(Handler h),会返回消息池中的message,避免了message的创建与销毁,效率更高。

    首先看一下message的初始化:

    public static Message obtain(Handler h) {
            Message m = obtain();
            m.target = h;
    
            return m;
        }
    ...
    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();
        }
    

    sPool是消息池,实际结构为Message,通过next对象指向下一个message,然后一串message构成消息池。消息池的上限是50,没有初始化,所以第一次调用obtain的时候,也是通过new Message创建的对象,在每次looper.loop()中获取到消息后,将处理完的message通过recycleUnchecked方法添加到消息池中,直到达到上限 50 ,在达到上限50前,消息都不销毁,只会将成员变量初始化。

    无论是通过Handler.obtainMessage()还是直接通过Message.obtain(Handler h),都会调用Message.obtain(),然后将target设为绑定的handler对象,该方法会先判断sPool是否为空,如果不为空,就将sPool返回,然后将sPool指向下一个Message。

    message queue

    消息队列,实际数据结构为链表,模拟的队列特性,初始化、销毁等操作的实现都在native层。

    mQuitAllowedBoolean变量,标识messageQueue是否可以中止。

    messageQueue中的 enqueueMessage 方法是消息队列的入队方法,在handler调用sendMessage后,会调用该方法将msg放入到消息队列中。

    boolean enqueueMessage(Message msg, long when) {
            //判断入队的消息的tartget是否为空,类型为handler,即判断message是否绑定了handler
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
            //判断msg是否被使用,由msg的flag变量与常量的位运算结果控制
            //初始化的message的flag默认为0,计算结果为未使用;
            //使用后flag变为1,计算结果为已使用;
            //如果该msg为异步消息,flag为2.
            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标记为已使用
                msg.when = when;
                Message p = mMessages;
                boolean needWake;
                if (p == null || when == 0 || when < p.when) {
                    // 如果消息队列为空,将mMessages指向msg,msg的next指向空节点,入队完成
                    msg.next = p;
                    mMessages = msg;
                    //needWeke标识队列是否需要唤醒,默认的mBlocked为false,looper调用loop开始轮训后设为true
                    needWake = mBlocked;
                } else {
                    // 根据mBlocked、是否是同步屏障message、该消息是否是异步的判断是否需要唤醒队列
                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                    Message prev;
                    //如果消息队列不为空,采用尾插法将新的msg插入到队尾,但mMessages仍指向第一个message
                    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;
                }
                // 如果需要,则唤醒队列,具体唤醒操作在native层实现
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    

    看完了消息的入队,再看一下消息的出队,消息的出队是通过next()方法实现的,里面的东西比较多,只看下主要逻辑。

    Message next() {
            ...
            // 开始循环,判断需要返回哪一个message
            for (;;) {
                ...
                synchronized (this) {
                    // Try to retrieve the next message.  Return if found.
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                    //msg.target为空,说明碰到了同步屏障,出循环后,prevMsg指向同步屏障,msg指向最近的一个异步消息
                    //同步屏障由postSyncBarrier方法添加,再使用完后需要删除屏障,否则会一直循环查找异步消息,无法抛出同步消息
                    if (msg != null && msg.target == null) {
                        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 {
                            mBlocked = false;
                            if (prevMsg != null) {
                                //prevMsg不为空,说明prevMsg指向同步屏障,说明msg指向异步消息,需要优先抛出异步消息
                                prevMsg.next = msg.next;
                            } else {
                                //prevMsg为空,说明没有异步消息,抛出msg,将mMessages指向下一个message
                                mMessages = msg.next;
                            }
                            //清空msg的next节点,并设为使用中,然后返回
                            msg.next = null;
                            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;
                    }
                    ...
            }
        }
    

    上述next方法中,返回需要处理的message,优先处理异步消息,消息处理按照先进先出的顺序执行。

    异步消息的处理时间更快,需要将消息设为异步(message.setAsynchronous(true)),并配合postSyncBarrier、removeSyncBarrier实现。postSyncBarrier是往队列中添加同步屏障,removeSyncBarrier是删除队列中的同步屏障,如果只添加没有删除,那么next无法抛出同步消息。

    private int postSyncBarrier(long when) {
            synchronized (this) {
                final int token = mNextBarrierToken++;
                final Message msg = Message.obtain();
                msg.markInUse();
                msg.when = when;
                msg.arg1 = token;
    
                Message prev = null;
                Message p = mMessages;
                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;
                }
                return token;
            }
        }
    

    根据源码可以看到,同步屏障实质上也是一个message,只不过target为null,不同于普通message的尾插法,同步屏障是通过头插法实现的,所以next抛出message的时候回直接处理异步消息。

    同步屏障的删除源码比较简单,这里就不贴出来了。只说明一下,同步屏蔽删除后也会优先加入消息缓冲池中,消息池满了后才销毁。

    messageQueue虽然叫消息队列,但实际的逻辑结构是message组成的链表,普通情况下模拟的队列的先进先出的特性,但遇到异步消息时,也不会完全遵守队列特性,实现头部插入功能。

    相关文章

      网友评论

        本文标题:Android多线程源码详解一:handler、looper、m

        本文链接:https://www.haomeiwen.com/subject/mccwiqtx.html