Android Handler消息机制详解

       在Android中,只有主线程才能更新UI,但是主线程不能进行耗时操作,否则会产生ANR异常,所以常常把耗时操作放到其他子线程进行。如果在子线程中需要更新UI,一般都是通过Handler发送消息,主线接收消息后进行相应的UI逻辑处理。

一.什么是Handler

       Handler是一个消息分发对象。
       Handler是Android系统提供的一套用来更新UI的机制,也是一套消息处理机制,可以通过Handler发消息,也可以通过Handler处理消息。

二.为什么使用Handler

       为了解决多线程并发的问题!
       比如:如果在一个activity中有多个线程同时更新UI,并且没有加锁,就会出现界面错乱的问题。但是如果对这些更新UI的操作都加锁处理,又会导致性能下降。出于对性能问题的考虑,Android提供这一套使用Handler更新UI的机制,不用再去关心多线程的问题,所有的更新UI的操作,都是在主线程的消息队列中去轮询处理的。
       在Android系统中,只有主线程才能更新UI,提到主线程,就不得说一下ActivityThread,一个应用内部的逻辑处理都是在ActivityThread内部依靠Handler来进行处理的,比如:activity、service相关的创建等相关逻辑,在应用创建后,会调用到ActivityThread内部的main()方法,逻辑如下:

public static void main(String[] args) {
    ......
    //创建Looper
    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    //创建Handler
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    //开启loop()循环
    Looper.loop();
}

      从上面可以看到在ActivityThread里面的main()中,执行了Looper.prepareMainLooper()及Looper.loop(),接下来一起分析一下Android系统的消息处理机制。

三.源码分析

       Android内部的消息处理机制主要是由Handler、Looper、MessageQueue、Message来组成的,具体分工如下:
       Handler:负责发送消息及处理消息
       Looper:不断的从消息队列中取出消息,并且将消息给发送本条消息的Handler
       MessageQueue:负责存储消息
       Message:消息本身,负责携带数据

1.Looper

       Looper分为主线程和其他子线程,前面讲到,主线程的Looper是在进程启动后调用ActivityThread的main()里面通过prepareMainLooper()创建的:

a.prepareMainLooper()

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

       prepareMainLooper()内部会调用prepare(false)来进行创建,且Looper是不能退出的,然后对sMainLooper进行赋值;

b.prepare()

//只能通过Looper.prepare()方法去初始化一个Looper
public static void prepare() {
    prepare(true);
}

//一个线程中只能有一个Looper对象,否则在第二次尝试初始化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));//创建了new Looper
}

       子线程通过prepare()内部调用prepare(true)来创建对应的Looper,且Looper是可以退出的,为什么要退出,后面会讲到;

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

private Looper(boolean quitAllowed) {
    //创建Looper的时候会创建一个MessageQueue
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

       Looper构造方法内会创建MessageQueue(),为后续消息处理做准备,然后获取到当前的Thread赋值给mThread,后续通过getThread()可以获取到当前的thread,可以用来判断是否为主线程。

c.loop()

       Looper在通过prepare(x)后需要执行loop()来将消息进行循环处理;

public static void loop() {
    final Looper me = myLooper();
    //如果Looper为null,则抛出异常
    //主线程启动时会创建Looper,所以主线程创建多个Handler时,通过sThreadLocal.get()获取的是同一个Looper。
    //如果非主线程创建Handler,需要先执行Looper.prepare()来创建Looper,否则loop()时会抛异常
    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
        if (msg == null) {
            // message为空,说明调用了quit(),会return继而退出loop()
            return;
        }

        try {
            //处理消息
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        .......
        //消息处理完后释放
        msg.recycleUnchecked();
     }
}

       可以看到,在loop()内部会先通过myLooper()获取到对应线程的Looper,继而获取到对应的MessageQueue,接下来不断的去读取MessageQueue内部的消息,然后进行处理;
       没有消息时,会一直阻塞在Message msg = queue.next(),等待MessageQueue分发消息;
       有消息时,调用Handler来处理消息;
       消息处理完后进行recycleUnchecked()处理。

d.quitSafely()

       子线程在没有消息需要处理时,需要退出looper,否则一直block在loop()中的Message msg = queue.next();

public void quitSafely() {
    mQueue.quit(true);
}

      前面讲到,子线程的Looper是可以退出的,通过调用以上方法来退出,其实最终调用的MessageQueue的quit(),消息队列退出了,就没有消息进行处理了,Looper也就不需要轮询了,也就可以退出了。

2.Handler

a.Handler()

//默认主线程
public Handler() {
    this(null, false);
}
//使用线程已经创建好的Looper
public Handler(Looper looper) {
    this(looper, null, false);
}

public Handler(Callback callback, boolean async) {
    ......
    //获取上面主线程创建的Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
    }
    //获取上面主线程Looper里面创建的MessageQueue
    mQueue = mLooper.mQueue;
    //默认为null,通过handler中的handleMessage来处理
    mCallback = callback;
    .......
}

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

       通过以上可以看到,在Handler的构造方法会分为几类:
       无参:代表默认使用当前线程的handler(一般是主线程),会通过Looper.myLooper()来获取到对应的Looper,如果Looper为null的话会抛异常,所以子线程使用Handler需要先通过Looper.prepare()来创建Looper;
       传入Looper参数:直接使用传入的Looper(使用HandlerThread时这样操作);
       从Looper中获取到对应的MessageQueue,Callback默认为null,通过实现的handleMessage()来处理。
       可以看到,在创建Handler时会通过myLooper()来获取到对应线程的Looper,此时两者建立了联系;

b.sendMessage()

       当调用Handler的sendMessagexx()发送消息时,最终都会调用到sendMessageAtTime(),内部就是将message加入到messagequeue中。

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    //获取MessgeQueue(即主线程的MessageQueue,上面已经创建完成)
    MessageQueue queue = mQueue;
    if (queue == null) {
        return false;
    }
    //将message加入队列
    return enqueueMessage(queue, msg, uptimeMillis);
}

      调用post(Runnable)也会将runnable转换为message:

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

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

      通过post(Runnable r)方式,最终也会转换为Message方式,将Runnable赋值给callback,有消息到来时,调用run方法来UI更新逻辑。

c.enqueueMessage()

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

       可以看到,在该方法内部主要执行了三件事:
       1.将Handler对象赋值给msg.target,从而message持有Handler对象的引用;
       2.如果mAsynchronous为true,会执行setAsynchronous(true)将其设置为异步消息,跟消息屏障一起使用,处理优先级高的消息(UI绘制);
       3.调用MessageQueue内的enqueueMessage()最终把发送的Message放进了主线程的MessageQueue里面进行循环;

d.dispatchMessage()

       前面讲到,Looper在loop()时,从MessageQueue中取到消息时,会通过msg.target.dispatchMessage()来通知Handler来进行处理;

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

       可以看到,在该方法内部会进行顺序判断来执行不同的消息处理逻辑:
       1.callback是通过post(Runnable)方式中的Runnable[下面有解释],通过sendMessage时为null;
       2.mCallback默认为null;
       3.调用子类的handleMessage方法,最终会调用本地实现的Handler中的handleMessage()来进行处理。

3.MessageQueue

       MessageQueue的主要方法有enqueueMessage():将消息添加到queue里;next():不断的取出message;quit():退出消息循环,继而退出Looper.loop()。

a.enqueueMessage()

boolean enqueueMessage(Message msg, long when) {
    ......
    //----------------分析点1------------------------
    synchronized (this) {
        ......
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        //----------------分析点2------------------------
        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;
            //-------------------分析点3---------------------------
            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.
        //-------------------分析点4---------------------------
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

      从上面代码可以看到,主要的点为:
      分析点1:加入synchronized (this)是防止多个Handler同时往queue中插入消息,出现混乱的场景。
      分析点2:消息的queue中没有消息或message需要立刻执行(when=0)或当前message执行时间小于queue里面所有消息的最早执行时间时,就把当前message插于queue的头部,然后执行nativeWake()唤醒next(),然后将message最早给loop()执行;
      分析点3:当不满足以上3个条件时,则需要把当前消息根据执行时间(when)插入到queue中的合适位置,此时不需要执行nativeWake()唤醒next(),因为不执行当前消息;
      分析点4:是否需要唤醒next(),来按顺序执行消息;

b.quit()

void quit(boolean safe) {
    //主线程的MessageQueue不允许退出
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    //加入同步锁,如果正在退出则直接返回
    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;

        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }

        // We can assume mPtr != 0 because mQuitting was previously false.
        //唤醒锁
        nativeWake(mPtr);
    }
}

      当执行Looper.quit()后,实际执行的是Message.quit(),在quit()里面设置mQuitting=true,最后执行nativeWake(mPtr)来唤醒next(),最终返回message为null,然后Looper.loop()里面检测到获取到的message为null,直接退出loop()。

c.next()

Message next() {
    .........
    for (;;) {
        //-------------------分析点1---------------------------
        nativePollOnce(ptr, nextPollTimeoutMillis);
        //-------------------分析点2---------------------------
        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            //-------------------分析点3---------------------------
            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) {
                    //-------------------分析点4---------------------------
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    //-------------------分析点5---------------------------
                    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 {
                //queue里面没消息时,就将nextPollTimeoutMillis置为-1,一直等待中
                nextPollTimeoutMillis = -1;
            }
        // Process the quit message now that all pending messages have been handled.
        //当执行quit()时,会执行以下逻辑
        if (mQuitting) {
            dispose();
            //返回的message为null,Looper.loop()中的Message msg = queue.next()接收到message为null,直接return,从而退出loop()
            return null;
        }
        ........ 
        ........
        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        //-------------------分析点6---------------------------
        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;
        }
}

      从上面代码可以看到,主要的点为:
      分析点1:nativePollOnce(ptr, nextPollTimeoutMillis)是native方法,最终会调用到Linux的epoll_wait()进行阻塞等待,nextPollTimeoutMillis是等待时间,当消息队列中没有消息时,就一直等待,直到通过nativeWeak()来唤醒;有消息但是没有到消息的执行时间时,需要等待delay时间,然后执行下面的逻辑;
      分析点2:加入synchronized (this)来确保取message的正确性;
      分析点3:消息屏障,即:msg.target == null,只能通过postSyncBarrier()来插入屏障消息,不能通过enqueueMessage()(会判断msg.target==null,直接抛异常),当消息队列头是屏障信息时,会循环找到消息队列中的异步消息(asynchronous),如果找到,执行分析点4;如果未找到的话,会一直休眠,等待唤醒;

    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

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

      从暴露的方法来看是hide及private的,应用是调用不到的,除非通过反射调用,从hide的方法可以看到,调用该方法后,会将该屏障消息置于消息队列的头部,因为SystemClock.uptimeMillis()肯定小于当前时间的,然后返回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;
            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;
            //----------------------判断a----------------------------
            if (prev != null) {
                prev.next = p.next;
                needWake = false;
            //----------------------判断b----------------------------
            } 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);
            }
        }
    }

      根据上面返回的token来移除屏障消息,然后判断是否需要执行nativeWake()来唤醒next(),来执行队列中的普通消息,判断是否需要执行nativeWake()条件如下:
      判断a:prev不为null,说明屏障消息不处于mMessages队列头,即还未执行到,不必执行唤醒;
      判断b:prev为null,说明屏障消息处于mMessages队列头,如果mMessages中有普通消息的话肯定未执行,因此来判断如果next是普通消息或mMessages为空时,进行nativeWeak();如果不为null,且next还是屏障信息,不需要nativeWake()。
      分析点4:当queue头的message未到执行时间时,会计算出nextPollTimeoutMillis,最后通过nativePollOnce(ptr, nextPollTimeoutMillis)等待时间到来执行message或新的nativeWeak();
      分析点5:从queue头取出message,然后queue头指向下一个执行的message,最后返回取出的message;
      分析点6:当队列中下一个需要执行的消息还未到时间(即需要等待nextPollTimeoutMillis),或消息队列中没有需要执行的消息(nextPollTimeoutMillis=-1),此时可以执行IdleHandler,即空闲的Handler,场景:1.延迟执行,没有固定时间;2.批量任务,任务密集,且只关注最终结果;使用方式如下:

     Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            //do something
            return false;
        }
     });

4.Message

a.类型

      根据前面的分析,消息有三种类型:普通消息、异步消息及消息屏障;
      普通消息:常用的通过Message.obtain()来获取的消息,target不能为null;
      异步消息:和普通消息一样,target不能为null,且通过setAsynchronous(true)设置,有了这个标志位,消息机制会对它有些特别的处理;
      消息屏障:也是一种消息,但是它的target为 null。只能通过MessageQueue中的postSyncBarrier方法发送一个消息屏障(该方法为私有,需要反射调用);
      通过前面的分析可以看到,消息屏障和异步消息的作用很明显,在设置消息屏障后,异步消息具有优先处理的权利。比如:ViewRootImpl中UI绘制前会先发送消息屏障,再发送异步消息来绘制UI。

b.obtain()

      在创建新的消息时,不会去通过new Message来创建,而是通过obtain(),先看一下:

//创建Message,通过Message.obtain(xx,xx,...)最终会调用到该方法
//当sPool不为空时,会从sPool里面取出message,不会频繁去new message()
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不为null时,说明已经有执行过的message,此时取出链表头的msg1,然后把sPool指向sPool.next,假如sPool就存储了一个message,则如果在msg1未执行前,有新的message请求,需要执行new message();

c.recycleUnchecked()

//释放message,没有delete,只是将内容置空,然后把该消息放在sPool的head,供后续创建新的message使用
void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    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++;
        }
    }
}

      在message处理完后,会调用recycleUnchecked()来将Message内容置空,然后判断当sPoolSize小于50时,将刚执行完的msg的next指向sPool,sPool指向该msg,即把该msg作为表头,然后size++;当sPoolSize大于50时,就不会再插入了;
      通过以上分析,执行完的message以链表形式存储,获取message时,会取出sPool表头的msg,然后sPool指向next、size--;该种创建及获取消息方式的好处:内存复用,防止内存抖动(不断的创建,释放),用到了享元设计模式

四.代码实现

a.创建一个Handler

//创建Handler
private final UIHandler mHandler = new UIHandler(this);
private static class UIHandler extends Handler {

    private static final int MSG_UPDATE_BG = 1;
    //防止内存泄露,使用弱引用,否则Handler会持有Activity的强引用,影响内存回收
    private final WeakReference<DisplayActivity> mActivity;

    private UIHandler(DisplayActivity activity) {
        mActivity = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        DisplayActivity activity = mActivity.get();
        if (activity == null) {
            FLog.e(TAG,"activity is null, handleMessage return");
            super.handleMessage(msg);
            return;
        }
        switch (msg.what) {
            case MSG_UPDATE_BG:
                activity.updateUI(msg.arg1, msg.arg2, (boolean) msg.obj);
                break;
            default:
                super.handleMessage(msg);
                break;
        }
    }
}

b.使用Handler发送消息

//简单的消息只传递what
mHandler.sendEmptyMessageDelayed(UIHandler.MSG_UPDATE_BG, 500);
//通过obtainMessage,可以传递what,arg1,arg2及obj参数
mHandler.sendMessage(mHandler.obtainMessage(UIHandler.MSG_UPDATE_BG1,
                display, statusCode, false));

c.处理消息

   @Override
    public void handleMessage(Message msg) {
        DisplayActivity activity = mActivity.get();
        if (activity == null) {
            FLog.e(TAG,"activity is null, handleMessage return");
            super.handleMessage(msg);
            return;
        }
        switch (msg.what) {
            case MSG_UPDATE_BG:
                activity.updateUI(msg.arg1, msg.arg2, (boolean) msg.obj);
                break;
            default:
                super.handleMessage(msg);
                break;
        }
    }

      上面已经一步步的解释了一个消息从发送到处理的整个流程,用流程图概况Handler的工作流程如下:


1.png

五.ThreadLocal

      在主线程里面创建多个Handler的时候,为什么都能更新UI?换句话说,为什么都能共享主线程的Looper?那么就用到了ThreadLocal这个东西了,一起看一下。
      对ThreadLocal的误解:
      1.ThreadLocal为解决多线程程序的并发问题提供了一种新的思路
      2.ThreadLocal的目的是为了解决多线程访问资源时的共享问题
      官方注释:

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
*/

      从注释可以看到:ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程的上下文。
      概括如下:
      1.ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
      2.ThreadLocal的设计初衷就是为了提供线程内部的局部变量,方便在本线程内随时随地的读取,并且与其他线程隔离。
      3.ThreadLocal实现了一个线程相关的存储,即每个线程都有自己独立的变量。所有的线程都共享这一个ThreadLocal对象,并且当一个线程的值发生改变之后,不会影响其他的线程的值。
      从Looper.java代码分析一下:

// sThreadLocal.get() will return null unless you've called prepare().
// 用ThreadLocal存储Looper,除非调用prepare(),否则的话sThreadLocal.get()会返回null
// ThreadLocal自身有一个ThreadLocalMap,来管理所有线程对应的变量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

//在prepare里面创建Looper
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //创建Looper,执行set()
    sThreadLocal.set(new Looper(quitAllowed));
}

//获取当前的Looper
public static @Nullable Looper myLooper() {
    //通过sThreadLocal.get()获取
    return sThreadLocal.get();
}

      从ThreadLocal.java代码分析一下:

public T get() {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程对应的map
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //从线程对应的map里面取变量值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    //threadLocals是线程的变量
    return t.threadLocals;
}

      前面分析到,Looper.myLooper()内部是通过sThreadLocal.get()来返回Looper的,在get()方法内,会先获取到当前线程,然后获取到线程对应的map,最后从map中取出对应的value。

public void set(T value) {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程对应的map
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //向线程对应的map里面给变量赋值
        map.set(this, value);
    else
        //首次赋值时需要创建map
        createMap(t, value);
}

void createMap(Thread t, T firstValue) {
    //对当前线程的threadLocals进行赋值
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

      前面分析到,Looper.prepare()内部是通过sThreadLocal.set(new Looper())来存储Looper的,在set()方法内,会先获取到当前线程,然后获取到线程对应的map,如果为null,就创建map,然后存入只;不为null,就直接更新map的值即可。
      看一下getMap(t)取出的threadLocals是从何而来?如何确保Looper的唯一性?

//Thread.java
/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

      ThreadLocal通过获取当前线程中的values属性,从而实现了每个单独线程的信息绑定。在Android的消息机制中,Looper便是采用ThreadLocal作为存储结构,所以looper对象的存储只会在当前线程中,子线程若是使用消息机制的话,必须调用Looper.prepare()方法来在线程中新建一个Looper的对象。
线程中的Looper唯一性:
      1.Looper创建是在prepare()里面触发的,当get()不为null时,会抛异常,确保不会重复创建;当get()为null时,会先通过setInitialValue为对应线程的ThreadLocalMap变量进行赋值,后面通过set()时,会调用ThreadLocalMap的set():key是sThreadLocal,static final修饰,确保key和value唯一;
      2.一个线程对应唯一的ThreadLocalMap变量,当去get()时,会根据当前线程去获取该线程的ThreadLocalMap变量,通过ThreadLocalMap去获取对应的Looper,确保一个线程对应一个Looper。

六.总结

a.Handler机制中最重要的四个对象:

      Handler:负责发送消息及处理消息
      Looper:复制不断的从消息队列中取出消息,并且将消息给发送本条消息的Handler
      MessageQueue:负责存储消息
      Message:消息本身,负责携带数据

b.Looper

      每一个线程中都对应一个Looper[sThreadLocal存储],通过Looper.prepare()为当前线程创建Looper,通过Looper.myLooper()获得当前线程的Looper对象,每一个Looper都对应一个MessageQueue,一个Handler对应唯一Looper,一个Looper可以对应多个Handler;

c.引用关系

      Handler持有Activity的引用,Message持有Handler的引用,MessageQueue持有Message的引用,Looper持有MessageQueue的引用,ActivityThread的main()中创建了sMainLooper;

d.内存泄露问题

      注意Handler使用导致的内存泄漏问题,在Java中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用。静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。当需要在静态内部类中调用外部的Activity时,可以使用弱引用来处理。

七.常见问题

a.一个线程有几个Handler?

      可以有N个Handler;

b.一个线程有几个Looper,如何保证?

      一个线程有1个Looper;
      Looper创建是在prepare()里面触发的,当sThreadLocal.get()不为null时,会抛异常,确保不会重复创建,另外在存储Looper时,用的是对应线程的ThreadLocalMap,然后通过ThreadLocalMap存储时key用的是sThreadLocal,保证key和value唯一;
      一个线程对应唯一的ThreadLocalMap变量,当在sThreadLocal去get()时,会根据当前线程去获取该线程的ThreadLocalMap,通过ThreadLocalMap去获取对应的Looper,确保一个线程对应一个Looper。

c.Handler内存泄露原因?为什么其他内部类没有该问题?

      发送的延迟消息(EmptyMessageDelayed)后、消息处理被前,该消息会一直保存在主线程的消息队列里持续时间,在持续时间里,该消息内部持有对handler的引用,由于handler属于非静态内部类,所以又持有对其外部类(即MainActivity实例)的潜在引用,引用关系如下:


1.png

      这条引用关系会一直保持直到消息得到处理,从而阻止了MainActivity被垃圾回收器(GC)回收,同时造成应用程序的内存泄漏,如下:


2.png

      其他内部类中没有持续持有该内部类应用的东西,则内部类就不会存在一直持有外部类引用的场景,所以不会造成内存泄露。

d.为何主线程可以new Handler?如果在子线程new Handler需要做什么工作?

      因为主线程一启动的时候,在main()函数中,由系统已经帮我们完成了,我们主线程中的所有代码,全都运行在这两个函数(prepare() 和 loop())之间。
      所有的线程在使用Handler时都必须要prepare()和loop(),如果子线程中想要进行Handler操作,就必须在子线程中执行prepare() 和 loop();
      Looper.prepare(); Handler handler = new Handler();Looper.loop();
      子线程如果没有消息处理时,则需要执行Looper.quitSafely();主线程不允许quit(),执行quit方法时会抛异常。

e.子线程维护的Looper,消息队列无消息时的处理方案是什么?有什么用?

      当消息队列无消息时,loop()会block在Message msg = queue.next();
      调用Looper.quitSafely()方法,在Handler机制里面有一个Looper,在Looper机制里面有一个函数,叫做quitSafely()和quit()函数。
      这两个函数是调用的MessageQueue的quit()方法,执行nativeWake(mPtr),唤醒MessageQueue的next()里面的nativePollOnce(),最终返回message为null,loop()方法在判断message为null后会return,退出loop()。
      1.remove消息,把消息队列中的全部消息给干掉,也就释放了内存;达成了第一个作用:释放内存
      2.Looper结束(跳出死循环),则达成了第二个作用:释放线程

f.既然可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不同的线程),那么它内部是如何确保线程安全的?

      synchronized()

g.使用Message时应该如何创建它?

      通过Message.obtain()来创建Message ;
      Message会维护一个大小为50的sPool,每次释放message时,只是把内容置空;有新消息创建时,从sPool里面取出head位置的message,然后初始化即可;
      内存复用,防止内存抖动(不断的创建,释放)
      享元设计模式

h.Looper死循环为什么不会导致应用卡死?

      卡死:是anr,输入事件在指定的时间内没有响应。
      loop():是睡眠,没有消息处理时,会block在Message msg = queue.next(),当有消息触发时,会唤醒执行 ;
      Looper.loop不断的接收处理事件,每一个点击触摸或者Activity每一个生命周期都是在Looper.loop()的控制之下进行的。

i.消息屏障和异步消息

      上面分析到,消息有三种类型普通消息、同步屏障消息和异步消息;因为消息处理有轻重缓急,就诞生了同步屏障消息和异步消息。它们俩是配套使用的,当消息队列中同时存在这三种消息时,如果碰到了同步屏障消息,那么会优先执行异步消息;比如:ViewRootImpl中的scheduleTraverals()中会先发送屏障消息,然后通过Choreographer发送异步消息来确保UI绘制优先执行。

j.IdleHandler

      IdleHandler是一个接口,里面有queueIdle()方法,从字面意思来看是空闲的Handler,通过MessageQueue中的addIdleHandler()执行,在前面的分析MessageQueue的next()方法中,如果消息队列中没到消息执行时间或没有消息时,会循环处理IdleHandler;适合场景:1.延迟执行,没有确切的时间;2.批量任务,任务密集,且只关注最终结果;

      以上就是对Handler消息机制的详细介绍及常见的问题,后续有新的收获会继续更新!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345