美文网首页
Android学习之Handler

Android学习之Handler

作者: Llianhua | 来源:发表于2019-08-11 17:51 被阅读0次

    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的发送消息和执行消息就分析完了。

    相关文章

      网友评论

          本文标题:Android学习之Handler

          本文链接:https://www.haomeiwen.com/subject/fhpsjctx.html