美文网首页Android 学习
谈谈对Handler的理解

谈谈对Handler的理解

作者: lxqljc | 来源:发表于2020-10-25 16:33 被阅读0次

    一、问题

    1. handler是什么?
    2. handler与Looper、MessageQueue、Message的关系,它们的作用分别是什么?
    3. handler 引发内存泄漏,怎么处理?

    二、场景介绍

    刚接触android时,最常用的场景就是,在主线程new 一个Handler对象,在子线程中回调中,通过handler对象发送消息,之后由handler接收处理对象。例如下面的代码

    package com.lxqljc.handlerdemo2;
    
    import androidx.annotation.NonNull;
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.util.Log;
    import android.view.View;
    
    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = "MainActivity";
    
        private static final int FLAG = 0x10;
    
        private Handler handler = new Handler() {
            @Override
            public void handleMessage(@NonNull Message msg) {
                //接收消息处理
                switch (msg.what) {
                    case FLAG:
                        Log.d(TAG, "handleMessage: 消息处理");
                        break;
                }
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
    
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            //模拟请求网络
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            Message msg = new Message();
                            msg.what = FLAG;
                            //发送消息
                            handler.sendMessage(msg);
                        }
                    }).start();
                }
            });
        }
    
    
    }
    

    通过上面的代码示例,我们可以回答上面问题了。

    1. handler是什么?
      handler是一个线程间通信媒介,因为android系统规定,ui的更新只能在主线程,防止ui在多线程下更新会造成混乱问题,统一由ui线程管理。
    2. handler的作用是啥?
      通过代码可以知道,主要用于发送消息和接收消息处理。

    三、Handler原理解析

    上面只是简单的应用,你可能会好奇handler发送消息到接收消息,这中间隐藏了什么样的秘密,我们简单介绍一个流程:

    1. handler 发送message消息
     handler.sendMessage(msg); 
    

    源码调用链
    sendMessage(@NonNull Message msg) --> sendMessageDelayed(msg, 0) --> sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis) --> enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
    long uptimeMillis)
    通过调用链分析,最后进入了enqueueMessage,我们看看代码。

     private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
            msg.target = this;
            msg.workSourceUid = ThreadLocalWorkSource.getUid();
    
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    
    1. message 添加到MessageQueue消息队列中(链表)
     boolean enqueueMessage(Message msg, long when) {
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
    
            synchronized (this) {
                if (mQuitting) {
                    IllegalStateException e = new IllegalStateException(
                            msg.target + " sending message to a Handler on a dead thread");
                    Log.w(TAG, e.getMessage(), e);
                    msg.recycle();
                    return false;
                }
    
                msg.markInUse();
                msg.when = when;
                Message p = mMessages;
                boolean needWake;
                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 {
                    // Inserted within the middle of the queue.  Usually we don't have to wake
                    // up the event queue unless there is a barrier at the head of the queue
                    // and the message is the earliest asynchronous message in the queue.
                    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; // invariant: p == prev.next
                    prev.next = msg;
                }
    
                // We can assume mPtr != 0 because mQuitting is false.
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    
    1. 消息已经添加到了队列了,那么此时谁取消息呢??没错,就是Looper对象,Looper对象已经主线程帮我们创建好了,接下来就是通过Looper的loop()方法,循环去取message消息。
     public static void loop() {
     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);
                    if (observer != null) {
                        observer.messageDispatched(token, msg);
                    }
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } catch (Exception exception) {
                    if (observer != null) {
                        observer.dispatchingThrewException(token, msg, exception);
                    }
                    throw exception;
                } finally {
                    ThreadLocalWorkSource.restore(origWorkSource);
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
            }
    
    1. message消息分发给对应的handler处理,也就是消息是谁发送的,就分发给谁处理。
     //消息分发
     msg.target.dispatchMessage(msg);
     //这里的this, 就是handler
     private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
            msg.target = this;
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    大概原理就是这样啦,具体去看看源码吧。

    四、常见问题

    1. 匿名内部类引用外部对象会造成内存泄漏,编译器警告了,所以还要处理下。


      image.png

      那到底是哪里引用了外部对象了,msg.target 就是handler,回调的对象引用了外部对象了。
      怎么处理?
      ① 将handler定义为静态内部类。
      ② 如果回调处理用到Activity,要用软引用包装,用户内存不足时,释放对象。
      我们将代码改一改,此时就没有警告了,因为静态内部类的对象,完全属于外部类本身,不属于外部类某一个对象。

     /**
         * 静态内部类
         */
        static class MyHandler extends Handler {
            @Override
            public void handleMessage(@NonNull Message msg) {
                //接收消息处理
                switch (msg.what) {
                    case FLAG:
                        Log.d(TAG, "handleMessage: 消息处理");
                        break;
                }
            }
        }
    
    1. Message复用问题。
      前面的例子,是直接new Message的,这样有个问题,会增加内存消耗,因为每次都是新创建对象,最好这样用:
    handler.obtainMessage();
    或者
     Message m = obtain();
     /**
         * Return a new Message instance from the global pool. Allows us to
         * avoid allocating new objects in many cases.
         */
        public static Message obtain() {
            synchronized (sPoolSync) {
                if (sPool != null) {
                    Message m = sPool;
                    sPool = m.next;
                    m.next = null;
                    m.flags = 0; // clear in-use flag
                    sPoolSize--;
                    return m;
                }
            }
            return new Message();
        }
    

    优先缓存中取消息复用,否则重新创建。

    五、总结

    1. 面试一般都会问题,所以看看源码还是必要的。
    2. 大概只是分析了基本流程,需要更深入了解源码。

    相关文章

      网友评论

        本文标题:谈谈对Handler的理解

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