[android] 安卓Handler

Handler位于 android.os.Handler中。

首先抛出几个问题

  1. 开启了Looper.loop()的线程会处于终止状态吗
  2. MessageQueue中的next()方法是如何阻塞的?
  3. 延时消息会阻塞非延时消息吗
  4. MessagecallbackHandlercallback,HandlerhandleMessgae谁的优先级高,是否互斥?如何改变?
  5. 为什么处理消息一定要调用 Looper.prepare()Looper.loop()?把Looper.loop()写在发送消息的代码前面行不行?像这样
Looper.prepare();
Looper.loop();
new Hander().post(...);
  1. Handler会导致内存泄漏吗,如何解决

首先介绍几个概念

  1. Looper

    在安卓中,一个线程对应一个looper对象,一个looper对象对应一个MessageQueue(消息队列),也就是说,一个线程有一个消息队列。
    默认情况下,安卓新创建的线程中没有开启消息循环,但是主线程除外,系统会自动为主线程创建消息循环并开启,所以在主线程中用Handler handler = new Handler创建Handler对象不会出错,但在非主线程中这回产生java.lang.RuntimeException:Can't create handler inside thread that has not called Looper.prepare()异常
    正确示例:
    public void run(){
        Looper.prepare();  //初始化looper对象
        Handler handler = new Handler(){  //创建Handler对象
            @Override
            public void handleMessage(Message msg){
                    //Do Thing      
            }
        };
        Message m = handler.obtainMessage();  //获取一个空消息,节省资源
        m.what = 1;
        handler.sendMessage(m);               //将m发送到Handler所持有Looper对象所属线程的消息队列
        Looper.loop();
    }
    
    注:Looper.loop之后的代码不会被执行,直到调用Handler.getLooper().quit()方法退出消息循环后,后面的代码才会执行
  2. MessageQueue

以单链表的形式存储Message,所有的Message会以when属性来排序,需要先被处理的Message会放到链表的头部。就是说这个链表是有序的
关键代码在MessageQueueenqueueMessage方法中

...
if (p == null || when == 0 || when < p.when) {
     // New head, wake up the event queue if blocked.
      msg.next = p;
      mMessages = msg;
      needWake = mBlocked;
  }
...
  1. Handler

    Handler允许发送Message到其所持有的Looper对象所在线程上处理,其发送消息的方法,实际上是将消息插入指定Looper所在线程的消息队列中
    示例:上面↑
  2. Message

    Message被存放在MessageQueue中,每个Message对象可通过Message.obtain()Handler.obtainMessage()获得。每个Message对象有arg1(int)、arg2(int)、obj(Object)、what(int)4个常用属性

下面尝试回答问题

  1. 不会,除非调用Looperquit方法。因为Looper.loop()方法内部开启了一个死循环。而quit方法内部只调用了MessageQueuequit方法(下面简称mquit
    mquit中对其内部链表中的Message进行了回收,清空队列。同时设置了一个标志位mQuittingtrue
synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

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

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

而在MessageQueuenext()方法中,检测到这个标志位后会返回null

 // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

loop死循环中,当得到的消息为null时,会退出循环

 Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

循环退出后,Looper.loop()后面的代码才能被执行,所有代码执行完毕后,线程才会正常终止

  1. 通过死循环,next()方法中也又一个死循环,可以认为这个死循环不断的检测当前时间有没有到达或超过消息队列中第一个消息的发送时间。如果到了,则取出这个消息,同时从消息队列中去除它。如果当前时间未到,或队列中没有消息,则不断的执行循环,不返回消息。
             if (msg != null) {
                    if (now < msg.when) {
                        //当前时间未到下一个消息的发送时间,不返回消息
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //当前时间到达或超过了下一消息的发送时间
                        // Got a message.
                        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 {
                    //队列中没有消息,同时又没有退出 `looper`
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
  1. 不会。就像上面MessageQueue中的介绍,非延时消息会插入延时消息的前面,取消息时从头部开始取,因此不会阻塞。

  2. 优先级:Message callback > Handler callback > Handler handleMessage。且他们互斥

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

如何改变:继承重写HandlerdispatchMessage方法。在里面实现自己的逻辑

  1. Looper.prepare()中才会创建当前线程的Looper对象。在使用默认无参构造方法创建Handler时会检测当前线程的Looper,若没有创建,则会抛出异常
private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {//由这里可以看出,一个线程只能有一个Looper对象
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
 if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }



调用了 Looper.loop()后才会开启死循环从队列中检测消息,所以如果不调用 Looper.loop(),不会报错,但会接收不到消息


不行,因为 Looper.loop()后面的代码不会执行,而 quit方法又会退出循环导致不能检测消息

  1. 会,当发送延时消息或没有及时退出looper循环时会发生泄漏。
    解决方法:
  2. 在Activity的onDestroy()中清楚handler的所有消息。
@Override
public void onDestroy() {
    // 移除所有的handler 消息.
    mHandler.removeCallbacksAndMessages(null);
}
  1. 当不需要消息时,及时调用handler.getLooper().quit()方法,使线程正常终止

你可能感兴趣的:([android] 安卓Handler)