对Handler,Looper,messagequeue,HandlerThread的深入理解,Handler常见面试题

欢迎转载,请标明出处:http://blog.csdn.net/a_long_/article/details/51635378 ,本文出自【阿隆 a_long_】

本文目录:

1.Handler的两个作用
2.为什么创建Handler之前必须要调用Looper的prepare()方法?
3.为什么所以每个线程对应一个looper对象,同样对应一个messageQueue?
4.为什么子线程不更新UI,也会需要用到Looper?
5.如果子线程需要用Looper,怎么使用?
6.为什么主线程不需要prepare()?
7.Handler的构造函数里还有一个async是干什么的?
8.Handler是在哪个线程执行的post和handleMessage?

9.Handler里面的callback又是什么鬼?

10.HandlerThread的run方法已经被写死了,那还怎么在子线程中干活:

11.    MessageQueue如何根据规定的时间对信息执行调度,如何在无信息的时候阻塞,有信息的时候唤醒:





1.Handler的两个作用

1:调度消息:在未来执行某操作(放到Looper里有顺序的执行)。

2:在别的线程(而不是自己的线程)执行某操作。(子线程耗时操作后利用Hadnler在主线程更新UI)

源码中的解释:

There are two main uses for a Handler: (1) to schedule messages and
 runnables to be executed as some point in the future; and (2) to enqueue
 an action to be performed on a different thread than your own.


2.为什么创建Handler之前必须要调用Looper的prepare()方法?

由于:

原因1:Handler的构造函数有很多种,但最终都会直接去拿当前线程的sThreadLocal里面的Looper实例(sThreadLocal.get();),如果没有实例就报异常。

 
  
  if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }

原因2: Looper的构造函数是private,所以不能直接创建Looper对象,只能通过prepare()方法。
所以导致:创建Handler之前必须要调用Looper的prepare()方法。




3.为什么所以每个线程对应一个looper对象,同样对应一个messageQueue?

Looper在创建实例的时候,会和当前线程绑定且创造出一个对应的MessageQueue,构造函数如下:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
由于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));
    }
由于每个线程都有自己独立的ThreadLocal,所以每个线程对应一个looper对象,同样只对应一个messageQueue。


4.为什么子线程不更新UI,也会需要用到Looper?


应用实例:大量图片的加载工具:比如GridView实现手机的相册功能,一般会用到LruCache,线程池,任务队列等;那么异步消息处理可以用哪呢?

1、用于UI线程当Bitmap加载完成后更新ImageView

2、在图片加载类初始化时,我们会在一个子线程中维护一个Loop实例,当然子线程中也就有了MessageQueue,Looper会一直在那loop停着等待消息的到达,当有消息到达时,从任务队列按照队列调度的方式(FIFO,LIFO等),取出一个任务放入线程池中进行处理。


简易的一个流程:当需要加载一张图片,首先把加载图片加入任务队列,然后使用loop线程(子线程)中的hander发送一个消息,提示有任务到达,loop()(子线程)中会接着取出一个任务,去加载图片,当图片加载完成,会使用UI线程的handler发送一个消息去更新UI界面。

(此例摘自鸿洋老师)


总结:子线程的Looper是维护消息队列、调度用的,使得线程池有序运作。如果要更新UI,还是需要主线程的Handler。




5.如果子线程需要用Looper,怎么使用?

方法一:Looper类里面的描述,非常清楚:

  * Class used to run a message loop for a thread.  Threads by default do
  * not have a message loop associated with them; to create one, call
  * {@link #prepare} in the thread that is to run the loop, and then
  * {@link #loop} to have it process messages until the loop is stopped.
  *
  * 

Most interaction with a message loop is through the * {@link Handler} class. * *

This is a typical example of the implementation of a Looper thread, * using the separation of {@link #prepare} and {@link #loop} to create an * initial Handler to communicate with the Looper.

    class LooperThread extends Thread {
        public Handler mHandler;
  
        public void run() {
            Looper.prepare();
  
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };
  
            Looper.loop();
        }
  }


方法二:使用HandlerThread,在创建Handler的时候给一个参数Handler handler = new Handler(handlerThread.getLooper())。

原理和方法一是一样的,只是HandlerThread帮忙封装了prepare(), Looper.loop();方法,所以不用自己来调用。

源码如下:

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }


6.为什么主线程不需要prepare()?

因为在创建ActivityThread的时候调用了prepareMainLooper(),这时已经创建了,无需自己再调用。

源码如下:

/**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }


7.Handler的构造函数里还有一个async是干什么的?

这个参数默认是false,也就是默认Handler是非异步(同步)的,也就是Looper一个一个往外拿Message,这才能保证UI线程不报异常。

但是如果一个Looper的MessageQueue里都可以接受同步,那么把这个参数设成true可以提高效率。

不过实际上这样的构造函数都被@hide了,所以实际上所有的Handler都是同步的。


Handler类对async参数的解释:

     * Handlers are synchronous by default unless this constructor is used to make
     * one that is strictly asynchronous.
     *
     * Asynchronous messages represent interrupts or events that do not require global ordering
     * with respect to synchronous messages.  Asynchronous messages are not subject to
     * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
     *
     * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
     * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.



8.Handler是在哪个线程执行的post和handleMessage?

都是在Handler所在的线程。比如Handler一般在主线程创建,那么post的runnable对象以及handleMessage都是在主线程工作的,容易造成ANR。应该建立子线程,在run函数中通过主线程handler的引用来sendMessage,这样就不会耗时。或者在子线程的Handler中的handleMessage中循环调用给自己发消息并且利用主线程的Handler更新UI,也可以实现子线程做耗时任务最终UI线程更新UI的目的。

handler的发送message和task只是往某一个looper的MessageQueue注入一项。
Looper是和thread结合的,就是一个事件循环。当该线程回到其事件循环时,之前注入的项就会得到执行。
至于在哪个线程里执行,要看handler发送到哪个线程的looper了,创建handler时都会绑定一个looper(ui线程是自动绑定的),handler发送的message等都会在该looper的线程得到处理。

如果是想在HandlerThread执行,那么在创建Handler的时候new Hadnler(handlerThread.getLooper())即可。


9.Handler里面的callback接口又是什么鬼?


根据源码里面的说明:Callback interface you can use when instantiating a Handler to avoid having to implement your own subclass of Handler.

可以用callback实例化Handler而不必自己实现Handler的子类。

所以和自己实现Handler的子类的效果是相同的,只是方便一些。


10.HandlerThread的run方法已经被写死了,那还怎么在子线程中干活:

用子线程的Handler的方法,有sandMessage、sendMessageAtTime等,比如可以在handleMessage里面加入sendMessageAtTime(message,1000),那么子线程就会每秒钟去干活。


11.MessageQueue如何根据规定的时间对信息执行调度,如何在无信息的时候阻塞,有信息的时候唤醒:

这不是队列,而是一个链表,放入链表中合适的位置用enqueueMessage函数:

boolean enqueueMessage(Message msg, long when) {



        synchronized (this) {
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                //p为null(代表MessageQueue没有消息) 或者msg的触发时间是队列中最早的, 则进入该该分支
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked; //当阻塞时需要唤醒
            } else {
                //将消息按时间顺序插入到MessageQueue。一般地,不需要唤醒事件队列,除非
                //消息队头存在barrier,并且同时Message是队列中最早的异步消息。
                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;
                prev.next = msg;
 }



MessageQueue是按照Message触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。

 

读取下一条messageMessageQueuenext函数:

@Override
Message next() {
    final long ptr = mPtr;
    int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                //当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,则退出循环。
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    //当异步消息触发时间大于当前时间,则设置下一次轮询的超时时长
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 获取一条消息,并返回
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    //设置消息的使用状态,即flags |= FLAG_IN_USE
                    msg.markInUse();
                    return msg;   //成功地获取MessageQueue中的下一条即将要执行的消息
                }
            } else {
                //没有消息
                nextPollTimeoutMillis = -1;
            }
            //当消息队列为空,或者是消息队列的第一个消息时
            if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                //没有idle handlers 需要运行,则循环并等待。
                mBlocked = true;
                continue;
            }
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        }
        //当调用一个空闲handler时,一个新message能够被分发,因此无需等待可以直接查询pending message.
        nextPollTimeoutMillis = 0;
    }
 }



 















参考:

鸿洋大神的分析

guolin大神的Handler分析

广泛的网上对Handler、Looper的源码分析

Mars老师的教程

Handler详细使用方法实例










你可能感兴趣的:(Handler,Android)