一、概述

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的一个整体结构。

Android线程通信机制-Handler(Java层)_第1张图片 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本身持有了一个初始化线程的引用,当发送消息时,其实都是将消息放入初始化线程的消息队列中。

Android线程通信机制-Handler(Java层)_第2张图片 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处理。

更多相关文章

  1. [原]Android应用程序线程消息循环模型分析
  2. android系统中的多线程(一): 关于在android中启动线程以及线程间
  3. Android中消息系统模型和Handler Looper
  4. Android开发实践:基于命令模式的异步任务线程
  5. 学习Android线程模型解析(包括UI的更新)

随机推荐

  1. Android探索之路(一)——消息处理机制
  2. Android NDK——实战演练之使用Android S
  3. Google Android介绍..
  4. android的intent使用方法
  5. Android核心分析---Android电话系统
  6. 如何在 iOS、Android、macOS、Windows 之
  7. Android View学习示例
  8. Hello, Android(安卓)深入(二)
  9. Android设计登陆界面
  10. 【Android(安卓)开发教程】解决Intent-Fi