Android学习之Handler

2019-08-11  本文已影响0人  Llianhua

Handler内存泄露

sendMessage方法内存泄露

有这么一个需求,延迟执行一段逻辑,先看第一种方式,直接让线程sleep:

 private val handler2 = object : Handler() {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            startActivity(Intent(this@HandlerActivity, XXXActivity::class.java))
        }
    }
    
     private fun test() {
        thread {
            val message = Message()

            SystemClock.sleep(3000) //1

            message.apply {
                what = 3
                obj = "hahaha"
            }
            handler2.sendMessage(message)
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("XX", "onDestroy")
        handler2.removeMessages(3) //2
    }

从上面的代码里看到在代码1处让程序休眠3秒,然后点击返回按钮销毁此Activity,那么即使在onDestroy方法里移除掉这个message,也是没有效果的。原因在于此时这个message还没有添加到MessageQueue里,所以移除的是null。
那么该如何解决呢,使用以下代码:

 private fun test() {
        thread {
            val message = Message()
            message.apply {
                what = 3
                obj = "hahaha"
            }
            handler2.sendMessageDelayed(message,3000)
        }
    }

使用sendMessageDelayed方法执行延迟操作,即可以避免带来的内存泄露。

为什么不能在子线程new Handler

我们在单独的线程里初始化一个Handler

private fun test() {
        thread {
           Handler()
        }
    }

然后在onCreate方法里调用,发现会闪退,报了一个错误,如下:

java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-4,5,main] that has not called Looper.prepare()

那么为什么我们在子线程中new Handler会报这个异常呢,原因是我们的Handler需要初始化一个loop对象,而我们没有做。那么为什么在Android的主线程中我们可以直接使用Handler而不报错呢,是因为在应用启动的时候已经帮我们调用了初始化loop。在ActivityThread类里main方法中已经帮我们初始化好了loop,这个loop绑定的是主线程。

 public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        ......

        Looper.prepareMainLooper();

        ......
 }

Looper.prepareMainLooper();就是这行代码完成了loop的初始化工作。那么我们再进入到这个方法中看看:

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

可以看到这里先调用了prepare方法,然后初始化sMainLooper对象,并且可以看出sMainLooper只能被初始化一次,否则被抛出异常。
再进入到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));
    }
    
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

从代码里可以看出,prepare方法初始化了looper对象,并且在looper中绑定了当前线程和new除了一个MessageQueue。并且把这个looper对象放入了sThreadLocal中。然后通过调用myLooper()方法从sThreadLocal中取得looper对象,至此完成looper的初始化工作。

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

那么,我们该如何在子线程中使用Handler呢,很简单,在我们使用Handler之前调用一下Looper的prepare方法即可:

    private fun test() {
        thread {
            Looper.prepare();
            Handler()
        }
    }

这样的话,这个Handler就是运行在我们new出来的子线程中了,当然这个Handler也不能去更改UI了。

更改UI只能在主线程中操作吗

我们学习Android的时候就知道,不能在非UI线程中去更新UI,否则会报错,那么真的是绝对的吗,看下面的代码:

    private fun test() {
        thread {
            btnTxt.text = "我哦喔喔"
        }
    }

我们在子线程中将一个Button控件的text值修改,并且成功运行没有报错,但是在某些手机或者某些系统上就会抛出异常。那么是为什么呢?原因是我们在调用setText的时候会调用requestLayout();方法,这个方法里又会调用ViewRootImpl的requestLayout方法:

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

在这个方法中就会检查线程是否正确,即调用checkThread方法:

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

这里就会做线程的检查,如果不是主线程,就会抛出异常,但是在执行requestLayout方法时还会并行的执行invalidate方法,所以,有可能页面invalidate方法先执行了,然后才触发checkThread方法,那么就不会抛出异常。
我们可以修改一下上面的代码验证一下:

    private fun test() {
        thread {
            SystemClock.sleep(1000)
            btnTxt.text = "我哦喔喔"
        }
    }

我们让修改的代码延迟一秒执行,可以发现,程序就闪退了,并且抛出了上面的异常。


屏幕快照 2019-08-11 17.38.54.png

Handler的dispatchMessage方法分析

我们在通过Handler去发送消息,并执行的时候可以有三种方式:

    //方式一
    private val handler = Handler(Handler.Callback {
        when (it.what) {
            3 -> {}
            else -> {}
        }
        false
    })
    
    //方式二
    private val handler2 = object : Handler() {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            startActivity(Intent(this@HandlerActivity, AopActivity::class.java))
        }
    }
    
    //方式三
    handler.post(){
            btnTxt.text = "handler"
    }
    

那么这三种方式有什么区别呢,答案就在Handler的dispatchMessage方法源码里:

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

注释1处的代码

让我先看注释1处的代码,也就是dispatchMessage首先判断了msg里的callback是否是null,如果不为null,那么就会调用handleCallback方法。那么这个callback是什么呢,就是我们调用Handler.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封装到Message对象里并且返回,那么在dispatchMessage方法里这个最先被判断的就不会为空,也就会执行handleCallback方法:

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

这个handleCallback方法也就是我们传进Runnable的run方法。

注释2处的代码

dispatchMessage方法里,如果callback为null了又进行判断mCallback是否为null。那这个mCallback是什么呢,就是我们初始化Handler对象是在构造方法中传进来的Handler.Callback对象,它是一个被定义在Handler中的接口:

    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }

如果我们在初始化中传进来这个CallBack,那么将执行它里面的handleMessage方法。

注释3处的代码

那么,如果以上这两个对象都为null的话,将调用Handler内部的handleMessage方法,这个方法是个空实现,也就是我们自己实现的handleMessage方法。

Handler的发送消息和执行消息过程

Handler的发送消息

当我们调用了Handler的sentXXX方法时,到最终都会调用enqueueMessage方法,这个方法代码如下:

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

从上面的代码中可以看出,这个方法做了两件事

  1. 将当前的Handler对象赋值给msg.target对象
  2. 调用MessageQueue中的enqueueMessage方法
    那么,我们再到enqueueMessage方法中看一下逻辑:
boolean enqueueMessage(Message msg, long when) {
        ......
     
        synchronized (this) {

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

            ......
        }
        return true;
    }

代码很长,其中最重要的一句就是mMessages = msg;即将传进来的msg赋值给全局的mMessages。这个过程就是Handler的发送消息的过程。

Handler的执行消息

那么我们在发送消息的时候最终将msg赋值到了MessageQueue中的全局对象mMessages中,是如何将它取出执行的呢。
首先是通过Looper.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;
        
        ......

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

            ......
            
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
          ......
    }

这段代码很长,我截取了比较关键的部分,可以看出loop方法先是取出looper对象,然后从looper对象中取出MessageQueue对象,接着在一个死循环中取出queue中的msg,如果为null,就返回。否则就调用msg.target的dispatchMessage方法,那么这里的msg.target就是刚才发送消息时绑定的Handler对象,所以最终会通过Handler的dispatchMessage方法调用我们的回调方法。
至此,Handler的发送消息和执行消息就分析完了。

上一篇下一篇

猜你喜欢

热点阅读