持续学习--Handler原理

Handler使用主要场景:子线程完成耗时操作的过程中,通过Handler向主线程发送消息,用来刷新UI界面。

线程之间通过Handler机制通信的大致流程:

  1. Handler通过sendMessage()、postMessage()以及对应的一些delay方法来发送消息;
  2. 消息队列MesageQueue通过enqueueMessage()将消息加入队列(其实是单链表,并且在插入的时候是从头部开始,按照时间优先原则插入);
  3. Looper对象的loop()方法,不间断的通过MessageQueue的next()方法取message,取到msg后,调用msg.target.dispatchMessage(msg),其内部调用了handler.handleMessage()。

常见面试题汇总:

  1. 一个线程有几个Handler? 
           一个线程里可以创建多个Handler对象,在Handler对象的构造方法里会获取looper对象,如果没有获取到就会抛异常(所以创建handler对象之前,一定要先有looper对象)。然后通过looper对象获得MessageQueue对象,这样handler后面发送消息的时候其实是通过这个MessageQueue对象的enqueueMessage()方法实现往消息队列里插入消息。
        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());
                }
            }
    
            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;
        }

     

  2. 一个线程有几个looper,如何保证?
          一个线程只有一个looper对象,因为Looper在调用prepare()方法初始化的时候,对象是被保存在ThreadLocal对象里,通过其get、set方法存取。只要线程的存活的,每个线程都有一个ThreadLocal对象来存储其变量的副本。prepare()方法如下:
    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));
        }

     

  3. Handler内存泄漏的原因?为什么其他的内部类没有这个问题?
     
        内部类默认持有外部类的引用,当外部类需要被销毁时,JVM的的垃圾回收机制会进行可达性分析,如果发现该对象还被其他对象引用,则不会被销毁,所以才会导致内存泄漏。Handler作为内部类,默认持有外部类(比如Activity)的引用,而消息队列(MessageQueue)里的Message对象又持有Handler的引用,如果外部类将要被销毁时,销毁队列里还存有消息,就会导致内存泄漏,其他的内部类一般不涉及可能被长时间被持有引用。Message持有Handler对象的引用是在Handler调用自己的enqueueMessage方法时产生的,代码如下:
     

        //Handler类里的方法,其会被发送消息的方法里调用
        private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }

    利用软引用并且将handler变成静态的,来解决handler内存泄漏。

  4. 为什么主线程可以new handler,如果要在子线程中new handler要做哪些准备?
     
        Handler之所以能有运转,是因为有Looper的作用,在主线程启动的时候就会通过调用Looper.prepareMainLoop()来初始化一个Looper对象,然后调用Looper.loop()方法轮询消息,使得handler机制滚动起来。子线程要想new Handler要先执行以下步骤。
        1)Looper.prepare();
        2)   Looper.loop():这个方法里的这行代码:Message msg = queue.next();会阻塞线程,该行之后的代码默认不会执行。
        3)Looper.quit():注意,主线程里是不可以调用该方法,会给抛异常。因为主线程里所有的逻辑都是在loop()里玩儿的
              ,它不允许主动的结束loop操作。
     

    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;
    
            // Make sure the identity of this thread is that of the local process,
            // and keep track of what that identity token actually is.
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            // Allow overriding a threshold with a system prop. e.g.
            // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
            final int thresholdOverride =
                    SystemProperties.getInt("log.looper."
                            + Process.myUid() + "."
                            + Thread.currentThread().getName()
                            + ".slow", 0);
    
            boolean slowDeliveryDetected = false;
    
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                // This must be in a local variable, in case a UI event sets the logger
                final Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
                final long traceTag = me.mTraceTag;
                long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
                long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
                if (thresholdOverride > 0) {
                    slowDispatchThresholdMs = thresholdOverride;
                    slowDeliveryThresholdMs = thresholdOverride;
                }
                final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
                final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
    
                final boolean needStartTime = logSlowDelivery || logSlowDispatch;
                final boolean needEndTime = logSlowDispatch;
    
                if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
                }
    
                final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
                final long dispatchEnd;
                try {
                    msg.target.dispatchMessage(msg);
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
                if (logSlowDelivery) {
                    if (slowDeliveryDetected) {
                        if ((dispatchStart - msg.when) <= 10) {
                            Slog.w(TAG, "Drained");
                            slowDeliveryDetected = false;
                        }
                    } else {
                        if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                                msg)) {
                            // Once we write a slow delivery log, suppress until the queue drains.
                            slowDeliveryDetected = true;
                        }
                    }
                }
                if (logSlowDispatch) {
                    showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
                }
    
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
    
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf(TAG, "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }
    
                msg.recycleUnchecked();
            }
        }


     

  5. 子线程维护的Looper,消息队列里无消息的时候处理方法是什么?有什么用?
         Looper机制里有两个函数,quit()、quitSafely()。他们的函数里会调用removeAllMessagesLocked()将消息队列里消息全部干掉,这样带来的好处一是释放内存,二是它会唤醒queue.next()里阻塞的地方,使其继续往下执行,这样如果消息队列里没有消息,queue.next()就会返回null,这样,loop()方法的循环里就与执行 if(msg == null)  return; 这样就结束了子线程的Looper运转,子线程也才真正执行完毕,释放线程。

  6. 既然存在多个Handler往MessageQueue里添加数据(发送消息时各个Handler可能处在不同的线程),那它内部是如何确保安全的?
     
         主线程创建的Handler对象,如果在不同的子线程里发送消息,消息最终是被存储在主线程的MessageQueue里。而在MessageQueue的存取方法enqueueMessage() 和next()方法里都有synchronized(this){}锁,来确保安全性。
          所以Handler的delay方法的时间是不完全准确的,因为有同步锁保证安全。

  7. 们使用Message时应该如何创建它?
        使用Message.obtain(),内部使用了享元设计模式,其Message类里有一个类型为Message的sPool,obtain方法是直接从池子里取消息,取出以后再对消息属性进行初始化赋值。当MessageQueue移出消息时,它并没有真正将消息释放,只是把消息里的内容置空。并且池子内消息的数量是有变量控制的,最大值为50个。

  8. 使用Handler的postDelay()后,消息队列会有什么变化?
     
        如果消息队列为空,该消息不会被执行,而是计算需要等待的时间,然后重新让线程睡眠。

Looper死循环为什么不会导致应用卡死?
 
   主线程的所有逻辑都跑在loop()里面,所以其死循环也没事,ANR和loop里死循环没任何关系。Android里ANR产生的情况有两种:1)5秒内没有响应输入的事件(比如按键按下、屏幕触摸);2)Broadcast Receiver在10秒内没有执行完毕。
     每一个应用都有自己的虚拟机,每个应用也都有一个main函数,在ActivityThread里。当点击一个Lancher界面里应用icon时,会启动一个Application,zygote进程给我们分配一个虚拟机,Application运行在这个虚拟机上,然后调用ActivityThead的main函数。
     主线程唤醒的方式:1)输入事件;2)往Looper里添加消息。也就是当有输入事件进来时,Message msg = queue.next();就会被唤醒了,唤醒后,5秒或者10秒没响应完成才会导致ANR。
     每一个事件都是一个Message,所有事件都是在Activity的生命周期里执行的,所有的事件都是运行在loop循环里。界面不断的操作,就会不断的send消息,处理消息,当没有消息执行时,应用就处在睡眠状态,并不是卡死状态。

    在无限循环方法中,通过调用mQueue.next()方法获取message,在next()内部,调用的有一个叫nativePollOnce(ptr , nextPollTimeoutMillis)的Native方法,当调用此方法的时候,主线程会释放CPU资源进入休眠状态,直到下条消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作(这主要是Linux的epoll机制)。


      我们可以从Handler的构造方法开始分析源码,在Handler对象内部会分别持有一个Looper和MessageQueue的全局变量 ,而这两个全局变量就是在Handler的构造方法里初始化的 如下:

final Looper mLooper;
final MessageQueue mQueue;

public Handler(){
    this(null, false);
}

public Handler(Callback callback , boolean async){
   ...
   mLooper = Looper.myLooper();
    if(mLooper == null){
        throw new RuntimeException("Can`t create handler inside thread" + Thread.currentTread() + "that has not called Looper.prepare()");
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
    }
}

      looper对象,在new Handler之前必须已经创建,不然会抛异常,而且在Looper对象里持有一个MessageQueue的全局对象,这个对象在Looper的构造方法里被初始化,而Looper是存在ThreadLocal中,这相当于Looper和当前线程做了绑定。

    Looper就是在一个无限循环里不断从MessageQueue中取出Message,然后处理Message中指定的任务(通过调用msg.target.dispatchMessage(msg))。

    MessageQueue的enqueueMessage方法里会按照Message的时间when来有序得插入MessageQueue中,可以看出MessageQueue实际上是一个有序队列,只不过是按照Message的执行时间来排序。

Handler的post(Runnable) 与sendMessage有什么区别?

看一下post(Runnable)的源码实现如下:

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)会将Runnable赋值到Message的callback变量中,这个runnable在dispatchMessage方法中被执行:

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

private static void handleCallback(Message message){
    message.callback.run();
}

Handler的sendMessageDelayed或者postDelayed是如何实现的?

    我们知道,MessageQueue插入消息的时候,会根据Message的执行时间排序,消息延时处理的核心实现是在取Message阶段。当从MessageQueue中取出一个Message时,如果当前系统时间小于Message.when,会计算出一个timeout,目的是实现在timeout时间段后再将UI线程唤醒,因此后续处理Message的代码只会在timeout时间之后才会被CPU执行;如果当前时间大于或等于Message.when,那么会返回Message给Looper.loop()。但这个逻辑只能保证在when之前,消息不被处理,不能保证一定在when时被处理。

总结:

  • 应用启动是从 ActivityThread 的 main 开始的,先是执行了 Looper.prepare(),该方法先是 new 了一个 Looper 对象,在私有的构造方法中又创建了 MessageQueue 作为此 Looper 对象的成员变量,Looper 对象通过 ThreadLocal 绑定 MainThread 中;
  • 当我们创建 Handler 子类对象时,在构造方法中通过 ThreadLocal 获取绑定的 Looper 对象,并获取此 Looper 对象的成员变量 MessageQueue 作为该 Handler 对象的成员变量;
  • 在子线程中调用上一步创建的 Handler 子类对象的 sendMesage(msg) 方法时,在该方法中将 msg 的 target 属性设置为自己本身,同时调用成员变量 MessageQueue 对象的 enqueueMessag() 方法将 msg 放入 MessageQueue 中;
  • 主线程创建好之后,会执行 Looper.loop() 方法,该方法中获取与线程绑定的 Looper 对象,继而获取该 Looper 对象的成员变量 MessageQueue 对象,并开启一个会阻塞(不占用资源)的死循环,只要 MessageQueue 中有 msg,就会获取该 msg,并执行 msg.target.dispatchMessage(msg) 方法(msg.target 即上一步引用的 handler 对象),此方法中调用了我们第二步创建 handler 子类对象时覆写的 handleMessage() 方法。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值