前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试问关于Handler的这些问题你知道吗?

面试问关于Handler的这些问题你知道吗?

作者头像
103style
发布2022-12-19 13:45:40
2700
发布2022-12-19 13:45:40
举报
文章被收录于专栏:Android开发经验分享

转载请以链接形式标明出处: 本文出自:103style的博客

目录

  • Handler 相关的问题 文末参考文章中找到一些以及自己编的一些
  • Handler 相关问题的解答
  • Handler 及相关源码的介绍 base on android-28

Handler 相关的问题

  • 在线程中可以直接调用 Handler 无参的构造方法吗?在主线程和子线程中有没有区别?
  • Handler 机制中涉及到哪些类,各自的功能是什么?
  • Handler 是可以调用哪些方法发送 Message 到 MessageQueue中?
  • MessageQueue 保存 Message 的数据结构是什么样的?
  • MessageQueue 添加 Message 的方式是什么样的,头插法、尾插法 还是 其它?
  • Looper 是怎么从 MessageQueue 获取 Message 的?
  • Looper.loop() 为什么不会阻塞APP?
  • 可以监测到 MessageQueue 中无数据的情况吗?可以的话,通过什么方式?
  • 在 Looper 中处理多个 Handler 的 Message 时,怎么知道 Message 是要交给哪个 Handler 来处理的?
  • 线程 和 Looper 的对应关系是,一对一、一对多、多对一 还是 其它?Looper 与 MessageQueue 呢?
  • Handler 发送 Message 到 MessageQueue 中,我们可以通过那些方式来监听轮到 Message 执行了呢?这几种监听都加了的话,是否都能收到回调?
  • Handler 容易造成内存泄漏的原因?
  • 在子线程中如何获取当前线程的 Looper?
  • 如果在任意线程获取主线程的 Looper?
  • 如何判断当前线程是不是主线程?

Handler 相关问题的解答

以下代码中省略了无关代码

Q :在线程中可以直接调用 Handler 无参的构造方法吗?在主线程和子线程中有没有区别? A:在主线程中可以;在子线程中会抛出RuntimeException, 需要先调用 Looper.prepare()。主线程在启动的时候已经在调用过Looper.prepare()

代码语言:javascript
复制
public Handler() {
    this(null, false);
}
public Handler(Callback callback, boolean async) {
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException("...");
    }
}

Q :Handler 机制中涉及到哪些类,各自的功能是什么? A:Handler、Looper、MessageQueue、Message Handler:将 Message 对象发送到 MessageQueue 中去。 LooperMessage 对象从 MessageQueue 中取出来,然后交给 Handler 去处理。 MessageQueue:负责 Message 的保存和取出。 Message:用于保存相关信息的消息。


Q :Handler 是可以调用哪些方法发送 Message 到 MessageQueue中? A:如下

代码语言:javascript
复制
public final boolean post(Runnable r)
public final boolean postAtTime(Runnable r, long uptimeMillis)
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
public final boolean postDelayed(Runnable r, long delayMillis)
public final boolean postDelayed(Runnable r, Object token, long delayMillis)
public final boolean postAtFrontOfQueue(Runnable r)
public final boolean sendMessage(Message msg)
public final boolean sendEmptyMessage(int what)
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
public final boolean sendMessageAtFrontOfQueue(Message msg)

Q:MessageQueue 保存 Message 的数据结构是什么样的? A:由 Message 组成的单链表。

代码语言:javascript
复制
public final class Message implements Parcelable {
    ...
    Message next;
    ...
}

Q :MessageQueue 添加 Message 的方式是什么样的,头插法、尾插法 还是 其它? A: 根据参数 when 排序,如 when <= 0,则添加到最前面,否则按 when 值进行升序排列,插入到对应的位置,如果值(when > 0)相等,则按添加顺序排列。

代码语言:javascript
复制
enqueueMessage(Message msg, long when){
    Message p = mMessages;
    if (p == null || when == 0 || when < p.when) {
        msg.next = p;
        mMessages = msg;
    } else {
        Message prev;
        for (;;) {
            prev = p;
            p = p.next;
            if (p == null || when < p.when) {
                break;
            }
        }
        msg.next = p; 
        prev.next = msg;
    }
}

Q :Looper 是怎么从 MessageQueue 获取 Message 的? A:通过 for 循环,调用 MessageQueue.next()MessageQueue 中获取可用的 Message

代码语言:javascript
复制
public static void loop() {
    for (;;) {
        Message msg = queue.next();
    }
}

Q:Looper.loop() 为什么不会阻塞APP? A:因为在没有可用消息的时候会休眠,然后 当 MessageQueue 有可用消息之后(新增的 when<=0 的消息或者到达指定when时)会通过 epoll机制 唤醒。可参考文末参考文章 我所理解的Handler 中关于Java端与Native端建立连接 的介绍。

代码语言:javascript
复制
public static void loop() {
    for (;;) {
        Message msg = queue.next();
        if (msg == null) {
            return;
        }
    }
}

Q:可以监测到 MessageQueue 中无数据的情况吗?可以的话,通过什么方式? A:在 api 23+ 可以通过执行 handler.getLooper().getQueue().addIdleHandler()来添加监听,queueIdle()return false 表示只监听一次无数据的情况,return true 则表示监听每次无数据的情况,

代码语言:javascript
复制
handler.getLooper().getQueue().addIdleHandler(
        new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                return false;
            }
        });

Q:在 Looper 中处理多个 Handler 的 Message 时,怎么知道 Message 是要交给哪个 Handler 来处理的? A:因为 Message 中的 target 变量即为对应的 Handler.

代码语言:javascript
复制
public static void loop() {
    for (;;) {
        Message msg = queue.next(); 
        try {
            msg.target.dispatchMessage(msg);
        } finally {
        }
    }
}

Q:线程 和 Looper 的对应关系是,一对一、一对多、多对一 还是 其它?Looper 与 MessageQueue 呢? A:一个线程只能有一个 Looper,当设置之后再调用prepare()会抛出RuntimeException; 但是一个 Looper 可以设置给多个线程,所以是 多对一 的对应关系。LooperMessageQueue 则是一对一关系,MessageQueueLooper 成员变量。

代码语言:javascript
复制
public static void prepare() {
    prepare(true);
}
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();
}

Q :Handler 发送 Message 到 MessageQueue 中,我们可以通过那些方式来监听轮到 Message 执行了呢?这几种监听都加了的话,是否都能收到回调? A:可以通过三种方式:1通过Message.obtain(handler, callback)Message 设置 callback2通过在创建 Handler 的时候设置 Callback3可以重写 HandlerhandleMessage(msg) 方法。

当三个方式都设置了,也只会有一个回调,优先级按上述的123排列,优先Message 自己的 callback, 然后再是HandlerCallback,最后才是重写 HandlerhandleMessage(msg) 方法。

代码语言:javascript
复制
public static void loop() {
    for (;;) {
        Message msg = queue.next(); 
        try {
            msg.target.dispatchMessage(msg);
        } finally {
        }
    }
}
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

Q:Handler 容易造成内存泄漏的原因? AMessage.target 存有 Handler 的引用,以知道自身由哪一个Handler 来处理。因此,当 Handler 为非静态内部类、或持有关键对象的其它表现形式时(如Activity 常表现为 Context ),就引用了其它外部对象。当 Message 得不到处理时,被 Handler 持有的外部对象会一直处于内存泄漏状态。


Q :在子线程中如何获取当前线程的 Looper? A:调用静态方式 Looper.myLooper(),在子线程中没有调用 Looper.prepare()时,返回null.

代码语言:javascript
复制
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

Q :如果在任意线程获取主线程的 Looper? A:调用静态方式 Looper.getMainLooper().

代码语言:javascript
复制
public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

Q :如何判断当前线程是不是主线程? A:调用 Looper.getMainLooper().isCurrentThread() 或者 Looper.getMainLooper() == Looper.myLooper()


Handler 及相关源码的介绍

Handler的构造函数

代码语言:javascript
复制
public Handler() {
    this(null, false);
}
public Handler(Callback callback) {
    this(callback, false);
}
public Handler(Looper looper) {
    this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}
public Handler(boolean async) {
    this(null, async);
}
public Handler(Callback callback, boolean async) {
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException("...");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

Looper的创建

代码语言:javascript
复制
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();
}

MessageQueue

代码语言:javascript
复制
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit(); // jni调用c层 以实现 epoll机制
}

Message

代码语言:javascript
复制
long when;
Bundle data;
Handler target;
Runnable callback;
Message next;

Looper.loop() 循环遍历消息队列

代码语言:javascript
复制
public static void loop() {
    final Looper me = myLooper();
    //...
    final MessageQueue queue = me.mQueue;
    for (;;) {
        //获取可以执行的消息
        Message msg = queue.next();
        if (msg == null) {
            //睡眠 等待唤醒
            return;
        }
        try {
            msg.target.dispatchMessage(msg);
        //...
        msg.recycleUnchecked();
    }
}

MessageQueue.enqueueMessage(…) 保存消息到队列中

代码语言:javascript
复制
boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            //队列会空 或 when=0 或 小于首个消息的when  添加到最前面
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            Message prev;
            //找到when对应的位置
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p;
            prev.next = msg;
        }
        //判断是否需要唤醒
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

MessageQueue.next() 寻找队列可用消息

代码语言:javascript
复制
Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {
        //获取NativeMessageQueue地址失败,无法正常使用epoll机制
        return null;
    }
    //用来保存注册到消息队列中的空闲消息处理器(IdleHandler)的个数
    int pendingIdleHandlerCount = -1;
    int nextPollTimeoutMillis = 0;
    for (;;) {
        //...
        //检查当前线程的消息队列中是否有新的消息需要处理,尝试进入休眠
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            //获取有效的消息
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }

            if (msg != null) {
                if (now < msg.when) {
                    //没达到消息需要被处理的时间
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    mBlocked = false;
                    //返回有效消息  并从链表中删除当前消息
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                //没有更多消息,休眠时间无限
                nextPollTimeoutMillis = -1;
            }
            //...
            //获取空闲处理回调
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                //进入休眠 等待唤醒
                mBlocked = true;
                continue;
            }
            //...
        }
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            //执行空闲处理
            //....
        }

        //重置变量
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}

Handler.dispatchMessage()

代码语言:javascript
复制
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

参考文章

你真的懂Handler吗?Handler问答 我所理解的Handler


如果觉得不错的话,请帮忙点个赞呗。

以上


本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-10-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
    • Handler 相关的问题
      • Handler 相关问题的解答
        • Handler 及相关源码的介绍
          • 参考文章
          相关产品与服务
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档