美文网首页Android异步处理Android
Android线程通信机制-Handler(Java层)

Android线程通信机制-Handler(Java层)

作者: 知鱼不知愚 | 来源:发表于2017-02-26 23:11 被阅读130次

    一、概述

    Android的单线程UI模型,决定了在UI线程中不能进行耗时任务,在开发过程中,需要将网络、io等耗时任务放在工作线程中执行,工作线程中执行完成后需要在UI线程中进行刷新,因此就有了Handler进程内线程通信机制,当然Handler并不是只能用在UI线程与工作线程间的切换,Android中任何线程间通信都可以使用Handler机制。
    Android的Handler机制应该说是有两套实现,Java层与native层分别实现了Handler机制,也就是说在Java层与native层各自维护了自己的消息队列,native层消息优先于Java层消息处理,在MessageQueue的源码中可以看到很多的native代码。这里只对Java层做个分析。

    二、使用Handler实现线程间通信

    1、在UI线程中使用Handler

    UI线程中使用Handler非常简单,因为框架已经帮我们初始化好了Looper,只需要创建一个Handler对象即可,之后便可直接使用这个Handler实例向UI线程发送消息。

    private Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // do something
            }
        };
    

    注意:这种做法会导致内存泄漏。
    我们通过Handler发送消息,在Message对象中会持有当前Handler对象的引用,在Java中非静态成员类、内部类、匿名类会持有外部对象的引用(这里在源码中有提到),而Looper是线程局部变量,其生命周期与UI线程相同,Looper持有MessageQueue的引用,MessageQueue持有Message的引用,当通过Handler发送一个延时消息未处理之前用户已经离开当前Activity,会导致Activity不能及时释放而内存泄漏。

    解决思路:

    既然知道是因为Handler持有Activity的引用而导致内存泄漏,那便让Activity在结束的时候不再有对象持有当前Activity的引用,或者不再有对象持有该Handler的引用,总之便是将这条引用链切断。

    2、在非UI线程中使用Handler

    在非UI线程中使用Handler一定要注意必须在创建Handler之前调用Looper.prepare()方法来初始化Looper,否则会报异常。如果不调用Looper.loop()方法,线程会在执行完毕后退出,也无法接收到消息。

        private Handler handler;
    
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                // 这一步其实是创建threadA的线程本地变量Looper
                Looper.prepare();
                // 创建Handler
                handler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        Bundle bundle =  msg.getData();
                        System.out.println(bundle.getString("msg"));
                    }
                };
                // 让threadA进入Looper循环中,不断的获取消息
                Looper.loop();
            }
        });
    
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("<<<<<<<<<<<");
                // 创建消息
                Message msg = Message.obtain();
                Bundle bundle = new Bundle();
                bundle.putString("msg", "Hello World!");
                msg.setData(bundle);
                // 发送消息
                handler.sendMessage(msg);
                System.out.println(">>>>>>>>>>>");
            }
        });
    

    这里只是为了简单写的示例代码,在实际开发中当然不会这么做,异步任务都会管理起来,不然离开了当前界面还有任务在执行(比如请求数据),那便没有意义了。

    三、Handler机制实现原理

    1、UML类图

    Handler机制主要由Handler、Looper、MessageQueue、Message四个类组成,从下面的UML中可以看到Handler持有一个Lopper实例,这个Looper实例与线程相关,而Looper中管理着一个MessageQueue消息队列,MessageQueue本质上是一个链表。从UML类图大致能看到Handler的一个整体结构。

    Handler、Looper、MessageQueue、Message类图

    2、Handler工作流程

    2.1、基本流程

    Handler工作流程主要分为两条支线,工作线程(也就是要发送消息的线程,后同)中发送消息实际上是将消息插入到消息队列MessageQueue中,初始化Handler的线程(即接受消息的线程,后同)则通过Looper.loop()方法进入无限循坏,不断的从消息队列MessageQueue中取出消息,通过Message本身持有的Handler去分发消息。

    2.2、线程切换的关键

    1、在Looper初始化的时候,其实是在当前线程的本地变量(ThreadLocal)中存储了一个Looper,而Looper.loop()在进入循环时,便是通过当前线程拿到Looper对象,从而拿到当前线程维持的MessageQueue消息队列,不断的读取消息。至于在另一个线程中通过Handler发送消息简单的说,读消息一直都是在初始化Handler的线程中进行,之后的分发消息与处理消息当然也是了。
    2、在工作线程中发送消息,Handler本身持有了一个初始化线程的引用,当发送消息时,其实都是将消息放入初始化线程的消息队列中。

    Handler工作流程图

    3、源码解析

    3.1 Handler源码

    从创建Handler开始看,Handler的构造方法有多个重载,最终都会走Handler(Callback callback, boolean async)或者Handler(Looper looper, Callback callback, boolean async)这两个构造方法。

    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.myLooper()是从线程本地存储中拿到与当前线程相关的Looper
            mLooper = Looper.myLooper();
            // 这里抛出的异常就是我们经常看见的,未再非UI线程中使用Handler却没有调用Looperprepare()初始化Looper
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }
            // 接下来几行代码初始化一些成员变量
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    

    Handler创建好了,接下来便是使用Handler,也就是通过Handler来发送消息。发送消息的方法主要有两大类,post****系列发送一个Runnable,作为处理消息的callback,或者send****系列方法发送一个Message,本质上都是构造一个Message对象,加上消息分发的时间点,最终都会调用sendMessageAtTime(Message msg, long uptimeMillis)这个方法。
    有一个例外的方法,sendMessageAtFrontOfQueue(Message msg),默认将消息的执行时间点置为0,也就是立即分发,在入消息队列时会放置在队列头。

    /**
     * @param msg 要发送的消息
     * @param uptimeMillis 消息的投递时间
     * @return true 消息放入队列成功, false则是失败
     */
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
            MessageQueue queue = mQueue;
            // 这个方法很简单,在这里检查了下MessageQueue是否准备好了
            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);
     }
    

    这个方法只有几行代码,真正入队列还是在MessageQueue中做的,却在入队列之前做了一件很重要的事情。

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            // 重要的事情在这,设置了Message的target,这也是后面分发消息的关键
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            // 调用MessageQueue的成员方法将消息入队列
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    Handler的另一个任务是分发消息并交给合适的方法去处理消息,由Handler.dispatchMessage(Message msg)这个方法完成。

      public void dispatchMessage(Message msg) {
            if (msg.callback != null) {  
                // 这里的callback就是个Runable对象,会执行其run()方法
                handleCallback(msg);
            } else {
                if (mCallback != null) {  
                     // mCallback是在创建Handler对象时设置的监听
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                // 这个方法很熟悉了,就是创建Handler时复写的,也是最常用的
                handleMessage(msg);
            }
       }
    
    3.2 Looper源码

    Looper的代码非常少,先来看一下初始化方法,有两个重载Looper. prepare()Looper. prepare(boolean quitAllowed)

     public static void prepare() {
            // 调用的有参的那个构造方法
            prepare(true);
       }
    
      /**
       * @param quitAllowed 是否允许退出循环
       */
      private static void prepare(boolean quitAllowed) {
            // 检查是否已经初始化过了,从里可以看到`Looper.prepare()`在一个线程中只能调用一次
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            // 这里可以看到创建了一个Looper对象,并存入TLS
            sThreadLocal.set(new Looper(quitAllowed));
       }
    

    接下来时Looper的构造方法,这个构造方法是私有的,也就是说我们在初始化Looper时只能通过Looper.prepare()这个方法。

    private Looper(boolean quitAllowed) {
            // 创建了消息队列,每个线程只有一个MessageQueue对象
            mQueue = new MessageQueue(quitAllowed);
            // 存储当前线程的引用
            mThread = Thread.currentThread();
     }
    

    还有个prepareMainLooper()方法,也是用来初始化Looper的,这个方法只是为了UI线程使用,UI线程Looper的初始化是在ActivityThreadmain()方法中进行的。我们可以通过Looper.getMainLooper().getThread()来获取UI线程。

    接下来是Looper的消息循环方法Looper.loop(),这个方法去掉一些安全性验证与Log,核心代码也很短。

    public static void loop() {
            // 获取当前线程绑定的Looper
            final Looper me = myLooper();
            // 未初始化Looper
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }   
           // 当前线程的消息队列
            final MessageQueue queue = me.mQueue;
            ...
            for (;;) {
                // 从消息队列中取出下一个消息,没有则阻塞
                Message msg = queue.next(); // might block
                // 取到null,这是因为MessageQueue已经退出消息循坏
                if (msg == null) {
                    return;
                }
                // 分发消息,这里的target就是Handler对象,在Handler中消息入队列时设置的
                msg.target.dispatchMessage(msg);
                ...
                // 将Message放入消息池
                msg.recycleUnchecked();
            }
     }
    

    Looper的任务就是创建一个循环器,不断的从消息队列取消息,交给Handler去分发。

    3.3 MessageQueue源码

    前面说Handler机制在native层与Java层都有实现,而HandlerLooper中都未出现native层的代码,其实是在MessageQueue中将Java层与native层联系了起来,这里只分析Java层实现,在做应用开发的时候也往往只使用Java层的Handler机制。
    首先来看一下MessageQueue的构造方法,这个构造方法是包级权限,也就是说我们是无法在自定义的类中创建MessageQueue这个类的实例的。

    MessageQueue(boolean quitAllowed) {
            // 是否允许退出消息循环
            mQuitAllowed = quitAllowed;
            // mPtr是native层消息队列的头指针
            mPtr = nativeInit();
    }
    

    在分析Handler发送消息的时候,可以看到最终都是将消息Message放入消息队列MessageQueue中,MessageQueue称为消息队列,其实现的数据结构却并不是真正的队列,而是一个单链表,在插入消息节点Message时按照时间点来确定位置。看下一下将消息入队列的MessageQueue.enqueueMessage(Message msg, long when)方法。

    boolean enqueueMessage(Message msg, long when) {
            // 如果没有target,消息无法被处理
            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) {
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {   // 根据消息的时间将消息放入队列的合适位置,就是单链表的插入
                    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;
        }
    

    接下来看一下取出消息MessageQueue.next()方法,这个方法挺长的,而且与native层交互很多。

    简单说下大概的逻辑:
    • 1.消息循环已经退出,则返回null,在Looper.loop()方法结束循环
    • 2.阻塞操作,等待nextPollTimeoutMillis到达,或者线程被唤醒未到下一次唤醒时间,则阻塞线程,等待唤醒,在MessageQueue.enqueueMessage(Message msg, long when)方法中我们看到了唤醒的操作。
    • 3.将取到的消息的时间与当前时间做比较,若还未到处理时间,则设置下一次轮询的超时时间
    • 4.取出一条消息返回
    • 5.消息队列为空,设置下一次超时时间为-1,会使线程一直阻塞,等待唤醒
     Message next() {
            // step 1
            final long ptr = mPtr;
            if (ptr == 0) {
                return null;
            }
    
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            int nextPollTimeoutMillis = 0;
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
                // step 2
                nativePollOnce(ptr, nextPollTimeoutMillis);
                 // 查找下一条消息,找到则返回
                synchronized (this) {
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                    if (msg != null && msg.target == null) {
                        // 忽略所有的同步消息
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                    if (msg != null) {
                        if (now < msg.when) {   // step 3
                            // 下一条消息执行时间还未到,设置一个超时时间
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {    // step 4
                            mBlocked = false;
                            if (prevMsg != null) {
                                prevMsg.next = msg.next;
                            } else {
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            // 设置消息使用的标志位
                            msg.markInUse();
                            return msg;
                        }
                    } else { 
                        // step 5
                        nextPollTimeoutMillis = -1;
                    }
    
                    // 执行退出消息,返回null
                    if (mQuitting) {
                        dispose();
                        return null;
                    }
    
                    // 消息队列为空或者消息未到执行时间,线程空闲,可以执行IdleHandlers
                    if (pendingIdleHandlerCount < 0
                            && (mMessages == null || now < mMessages.when)) {
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
                    if (pendingIdleHandlerCount <= 0) {
                        // 没有IdleHandlers执行,直接进入下一次循环继续等待消息
                        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);
                        }
                    }
                }
    
                // 重置IdleHandler,不会被再次执行
                pendingIdleHandlerCount = 0;
                nextPollTimeoutMillis = 0;
            }
        }
    
    3.4 Message源码

    Message就是消息的实体类,也是消息队列MessageQueue的节点,除了具有一些消息的基本属性,还持有下一条消息的指针。
    Message的构造方法是空的,其所有属性都是通过set方法来设置。在开发过程中,尽量使用obtain系列的方法来获取一个消息实例,内部通过消息池来实现,减少因为创建对象而造成的开销,以达到复用的效果。
    obtain方法有许多重载,本质上都是调用无参的Message.obtain()方法从消息池中取出一个消息实例,设置不同的属性。

    public static Message obtain() {
            // 通过同步sPoolSync对象,将sPool加锁,保证线程安全
            synchronized (sPoolSync) {
                if (sPool != null) {  // 消息池不为空,则从消息池中取出一个消息实例
                    Message m = sPool;
                    sPool = m.next;
                    m.next = null;
                    m.flags = 0; // 清楚使用标志位
                    sPoolSize--;  // 消息池数量减1
                    return m;
                }
            }
            // 如果消息池为空,则创建一个新的消息对象
            return new Message();
        }
    

    既然有消息池,那么消息池中的消息是哪来的呢,Message中有个Message.recycle()方法,这个方法便是将消息放入消息池中。

    public void recycle() {
            if (isInUse()) {  // 正在使用的消息无法回收
                if (gCheckRecycle) {
                    throw new IllegalStateException("This message cannot be recycled because it "
                            + "is still in use.");
                }
                return;
            }
            // 真正的实现回收消息
            recycleUnchecked();
        }
    

    Message.recycleUnchecked()多了个uncheck,是真的不检查是否在使用,强行回收。

    void recycleUnchecked() {
            // 设置使用标志位
            flags = FLAG_IN_USE;
            // 清楚Message的所有属性
            what = 0;
            arg1 = 0;
            arg2 = 0;
            obj = null;
            replyTo = null;
            sendingUid = -1;
            when = 0;
            target = null;
            callback = null;
            data = null;
            // 同步
            synchronized (sPoolSync) {
                // 如果消息池未满,将消息放入消息池中
                if (sPoolSize < MAX_POOL_SIZE) {
                    next = sPool;
                    sPool = this;
                    sPoolSize++;
                }
            }
        }
    

    四、结束

    大概记录了Handler机制在Java层的一个实现流程,从Handler发送消息,将消息加入到MessageQueue中,Looper不断的循环从MessageQueue中取出消息,将消息交给Handler处理。

    相关文章

      网友评论

        本文标题:Android线程通信机制-Handler(Java层)

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