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

转载请以链接形式标明出处:
本文出自: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()

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:如下

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 组成的单链表。

public final class Message implements Parcelable {
    ...
    Message next;
    ...
}

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

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

public static void loop() {
    for (;;) {
        Message msg = queue.next();
    }
}

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

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 则表示监听每次无数据的情况,

handler.getLooper().getQueue().addIdleHandler(
        new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                return false;
            }
        });

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

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 成员变量。

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) 方法。

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.

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

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

public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

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


Handler 及相关源码的介绍

Handler的构造函数

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的创建

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

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit(); // jni调用c层 以实现 epoll机制
}

Message

long when;
Bundle data;
Handler target;
Runnable callback;
Message next;

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

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(…) 保存消息到队列中

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() 寻找队列可用消息

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()

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


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

以上


扫描下面的二维码,关注我的公众号 Android1024, 点关注,不迷路。
Android1024

<think> 嗯,用户这次的需求很明确,是关于安卓/Linux驱动面试问题的整理。从对话历史看,用户之前已经询过Linux内核面试问题,现在转向更具体的驱动开发方向,特别是安卓相关的场景。用户还提供了几篇参考资料,说明ta正在认真准备技术面试,可能需要实战性强的问题清单。 用户引用的资料[1]~[4]很有价值,其中提到中断处理、内存管理、并发控制等核心知识点。结合这些内容和行业常见考点,应该重点覆盖以下维度:首先是硬件交互层(如寄存器操作、中断处理),然后是驱动框架(如Linux设备模型、安卓HAL层),最后是系统优化(启动加速、功耗管理)。尤其要注意安卓特有的binder、ashmem等机制。 考虑到用户可能是求职者,问题设计需要区分难度层级:基础题考察概念理解(如IRQ/FIQ区别),进阶题关注实现原理(如自旋锁实现),实战题侧重场景应对(如LCD调试流程)。答案示例要简洁清晰,比如用代码片段说明mmap用法,用表格对比下半部处理机制。 用户资料[3]提到安卓启动优化,这在实际工作中很重要,可以单独列出优化方法表格。另外ta提到的"裸机寄存器操作与系统环境下差异"很有意思,这正好体现驱动开发在不同场景的适配能力,应当在并发控制问题中展开。 最后生成的相关问题要引导深入学习,比如从binder机制延伸到进程通信安全,从启动优化关联到init进程分析。用户明显是技术背景,问题可以适当深入内核源码层面。 </think> 以下是安卓/Linux驱动面试高频问题及解析,结合硬件交互与系统特性分类整理: --- ### **一、驱动开发基础** 1. **Linux内存分配函数对比** | 函数 | 适用场景 | 特性 | |---------------|-------------------------|-----------------------------| | `kmalloc` | 小内存连续物理地址 | 最大分配4MB,可能触发直接内存回收[^1] | | `vmalloc` | 大内存非连续物理地址 | 虚拟地址连续,适用于DMA映射 | | `kzalloc` | 要求内存清零 | 等价于`kmalloc + memset(0)` | | `alloc_pages` | 页级分配(如Buddy System) | 直接获取`struct page`结构 | 2. **IRQ与FIQ硬件级差异** - **优先级**:FIQ > IRQ(FIQ有私有寄存器r8-r14减少压栈开销) - **响应速度**:FIQ向量地址在中断向量表末端(`0x1C`),可连续执行代码无需跳转 - **用途**:FIQ用于实时性要求极高的场景(如触控采样)[^4] --- ### **二、中断与并发控制** 3. **中断分上下半部的原因** - **上半部**:快速响应硬件,仅做关键操作(如清除中断标志),**不可中断** - **下半部**:延迟处理耗时任务(如数据处理),**可被新中断打断** - **实现方式**: ```c // 上半部注册 request_irq(irq, top_half_handler, IRQF_SHARED, "dev", dev); // 下半部选择 tasklet_schedule(&bottom_tasklet); // 软中断上下文,不可睡眠 schedule_work(&work_queue); // 进程上下文,可睡眠 ``` [^4][^2] 4. **自旋锁实现原理** - **原子操作**:基于CPU的`test-and-set`指令(如ARM的`LDREX/STREX`) - **忙等待**:未获取锁时循环检测(`while (lock != 0);`) - **注意事项**:禁止在持有锁时睡眠(否则引发死锁)[^4] --- ### **三、内核机制深度** 5. **mmap实现机制** - **核心流程**: ```mermaid graph LR A[用户空间mmap调用] --> B[驱动mmap_operation] B --> C[remap_pfn_range] C --> D[建立页表映射] D --> E[用户空间直接访物理内存] ``` - **应用场景**:LCD帧缓冲区映射(避免用户态-内核态拷贝)[^3] 6. **并发控制必要性及实现** - **竞态场景**:多核同时访设备寄存器(如传感器数据读取) - **解决方案**: ```c static DEFINE_SPINLOCK(data_lock); // 声明自旋锁 void read_sensor_data(void) { spin_lock(&data_lock); iowrite32(REG_ADDR, ...); // 临界区操作 spin_unlock(&data_lock); } ``` [^4] --- ### **四、安卓系统优化** 7. **启动加速关键手段** | 优化方向 | 具体措施 | |--------------|------------------------------------------------------------------------| | **内核裁剪** | 移除无用驱动(`make menuconfig`关闭未用外设支持) | | **Init优化** | 并行启动服务(`init.rc`使用`exec_start`并行执行) | | **打印控制** | 降低Kernel Log等级(`loglevel=1`启动参数) | | **固件加载** | 预加载固件到内存(减少文件系统访)[^3] | 8. **LCD驱动调试流程** 1. 获取屏幕规格书(确认接口类型:MIPI/LVDS/eDP) 2. 配置DTS参数(时序参数、背光控制引脚) ```dts panel: panel@0 { compatible = "samsung,lcd-panel"; backlight-gpios = <&gpio2 15 GPIO_ACTIVE_HIGH>; // 背光控制GPIO timing: 1280x720p60 { ... }; // 时序配置 }; ``` 3. 验证帧率与色彩(通过`fbset`调整分辨率)[^3] --- ### **五、嵌入式系统特性** 9. **Linux vs WinCE特性对比** | **特性** | **嵌入式Linux** | **WinCE** | |---------------|--------------------------------|------------------------| | **实时性** | 需打PREEMPT_RT补丁 | 原生支持硬实时 | | **文件系统** | 支持EXT4/F2FS/Btrfs等 | 仅支持FAT/NTFS | | **开发成本** | 工具链免费 | 需购买License | | **驱动模型** | 统一设备树(DTS) | 注册表配置 | 10. **TTY驱动体系结构** ```mermaid graph TB User[用户空间] -->|read/write| TTY_Core[TTY核心层] TTY_Core -->|数据处理| Line_Discipline[线路规程层] Line_Discipline -->|字节流| TTY_Driver[底层驱动(UART/USB)] ``` - **关键层**:线路规程层实现协议解析(如PPP/SLIP)[^1] --- ### 相关进阶问题 1. 如何通过eBPF监控驱动中的函数调用? 2. 解释Binder驱动在安卓进程通信中的作用? 3. Ashmem(匿名共享内存)相比mmap的优势? 4. 如何诊断因自旋锁引发的系统死锁? 5. 在安卓系统中调试I2C设备失败的常见原因? > 注:面试中常要求手写关键结构体(如`file_operations`)或分析驱动oops日志。 [^1]: 寄存器操作与内存分配机制 [^2]: 中断上半部与下半部实现原理 [^3]: 安卓启动优化与外设调试实战 [^4]: 并发控制与内核同步机制
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值