Android 学习

谈谈对Handler的理解

2020-10-25  本文已影响0人  lxqljc

一、问题

  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. 大概只是分析了基本流程,需要更深入了解源码。
上一篇下一篇

猜你喜欢

热点阅读