HandlerThread

3 阅读23分钟

HandlerThread

📌 面试重要度:⭐⭐⭐⭐⭐

考察频率:字节 85% | 阿里 78% | 腾讯 72%

一、核心概念

1.1 定义与作用

一句话定义: HandlerThread 是 Android 封装的带有 Looper 的线程类,继承自 Thread,内部自动创建 Looper 并启动消息循环,专门用于在子线程中处理异步任务。

为什么重要

  • 架构地位:Android 官方提供的线程间通信标准方案,许多系统服务(如 IntentService)基于它实现
  • 面试高频:结合了 Thread + Handler + Looper 三大核心知识点,是考察对 Handler 机制理解深度的重要指标
  • 实际价值:解决了在子线程中手动创建 Looper 的繁琐操作,避免常见错误,是串行化执行异步任务的最佳实践

与普通线程的核心区别

  • 普通 Thread:run() 执行完即结束,无法接收持续的任务
  • HandlerThread:内部维护消息队列,可持续接收并串行处理任务,直到主动退出

1.2 与其他概念的关系

在 Handler 体系中的位置

  • Handler 基础(详见 ../01-Handler基础/):HandlerThread 是 Handler 机制的典型应用场景
  • Looper 原理(详见 ../02-Looper原理/):HandlerThread 内部封装了 Looper 的创建和启动逻辑
  • IntentService(详见 ./02-IntentService.md):IntentService 内部使用 HandlerThread 实现异步任务队列

二、核心原理

2.1 工作机制

整体流程: 创建 HandlerThread → 调用 start() → run() 中准备 Looper → 获取 Looper 创建 Handler → 发送消息 → 循环处理消息 → quit() 退出循环

关键步骤详解

  1. 线程启动:调用 start() 触发 run() 方法执行
  2. Looper 初始化:在 run() 中调用 Looper.prepare() 创建当前线程的 Looper
  3. 通知准备完成:设置 Looper 引用并唤醒等待线程(通过 notifyAll()
  4. 消息循环开启:调用 Looper.loop() 进入死循环,等待处理消息
  5. 外部任务投递:主线程通过 HandlerThread.getLooper() 获取 Looper,创建 Handler 并发送消息
  6. 串行处理任务:HandlerThread 按顺序从消息队列中取出并处理任务
  7. 退出循环:调用 quit()/quitSafely() 终止 Looper 循环,线程结束

2.2 源码分析

核心类结构
// Android 11 源码:frameworks/base/core/java/android/os/HandlerThread.java
public class HandlerThread extends Thread {
    int mPriority;              // 线程优先级
    int mTid = -1;              // 线程 ID
    Looper mLooper;             // 当前线程的 Looper(核心)
    private @Nullable Handler mHandler; // 可选的内部 Handler

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority; // 设置线程优先级(-20 ~ 19)
    }
}

源码解读

  • mLooper:核心成员变量,存储该线程的 Looper 实例,用于外部获取并创建 Handler
  • mPriority:线程优先级,默认为 Process.THREAD_PRIORITY_DEFAULT(0),后台任务建议设置为 THREAD_PRIORITY_BACKGROUND(10)
  • mTid:线程 ID,在 run() 中通过 Process.myTid() 获取
启动流程(run 方法)
// Android 11 源码:frameworks/base/core/java/android/os/HandlerThread.java
@Override
public void run() {
    // 步骤1:获取当前线程 ID
    mTid = Process.myTid();

    // 步骤2:创建当前线程的 Looper(关键)
    Looper.prepare();

    // 步骤3:加锁并保存 Looper 引用
    synchronized (this) {
        mLooper = Looper.myLooper(); // 获取刚创建的 Looper
        notifyAll(); // 唤醒 getLooper() 中等待的线程
    }

    // 步骤4:设置线程优先级
    Process.setThreadPriority(mPriority);

    // 步骤5:回调钩子方法(子类可重写,初始化逻辑)
    onLooperPrepared();

    // 步骤6:开启消息循环(阻塞在这里)
    Looper.loop();

    // 步骤7:退出后执行(loop() 结束后才会到这里)
    mTid = -1;
}

源码解读

  • 设计意图:封装 Looper 的标准创建流程,避免开发者手动调用 Looper.prepare()Looper.loop()

  • 关键细节

    • synchronized (this) + notifyAll():解决并发问题,确保 getLooper() 能在 Looper 创建后才返回
    • onLooperPrepared():空实现的钩子方法,允许子类在 Looper 启动前执行初始化(如创建 Handler)
    • Looper.loop():死循环等待消息,只有调用 quit() 才会退出
获取 Looper(getLooper 方法)
// Android 11 源码:frameworks/base/core/java/android/os/HandlerThread.java
public Looper getLooper() {
    // 步骤1:检查线程是否已启动
    if (!isAlive()) {
        return null;
    }

    // 步骤2:等待 Looper 创建完成
    synchronized (this) {
        while (isAlive() && mLooper == null) {
            try {
                wait(); // 阻塞等待,直到 run() 中 notifyAll()
            } catch (InterruptedException e) {
            }
        }
    }

    // 步骤3:返回 Looper 引用
    return mLooper;
}

源码解读

  • 并发控制:通过 wait() + notifyAll() 确保 Looper 创建完成后才返回,避免返回 null
  • 使用时机:必须在 start() 之后调用,否则返回 null
  • 阻塞风险:如果 run() 未执行(如线程启动失败),会一直阻塞在 wait()
退出机制(quit 方法)
// Android 11 源码:frameworks/base/core/java/android/os/HandlerThread.java
public boolean quit() {
    Looper looper = getLooper();
    if (looper != null) {
        looper.quit(); // 立即终止消息循环,丢弃未处理的消息
        return true;
    }
    return false;
}

public boolean quitSafely() {
    Looper looper = getLooper();
    if (looper != null) {
        looper.quitSafely(); // 处理完已有消息后再退出
        return true;
    }
    return false;
}

源码解读

  • quit() vs quitSafely()

    • quit():立即设置退出标志,MessageQueue 后续所有消息被丢弃
    • quitSafely():仅移除延迟消息(when > now),立即消息会执行完
  • 调用时机:任务处理完成后必须调用,否则线程不会结束(造成资源泄漏)

  • 线程终止:quit 后 Looper.loop() 退出,run() 方法结束,线程终止

2.3 重要细节与边界条件

细节1:线程优先级设置

// 前台任务(如网络请求)
HandlerThread thread = new HandlerThread("network", Process.THREAD_PRIORITY_DEFAULT);

// 后台任务(如日志写入)
HandlerThread thread = new HandlerThread("logger", Process.THREAD_PRIORITY_BACKGROUND);
  • 优先级范围:-20(最高优先级)~ 19(最低优先级)

  • 推荐值

    • 默认任务:THREAD_PRIORITY_DEFAULT(0)
    • 后台任务:THREAD_PRIORITY_BACKGROUND(10)
    • 紧急任务:THREAD_PRIORITY_URGENT_DISPLAY(-8)

细节2:内存泄漏风险

// ❌ 错误:未调用 quit(),线程永不结束
HandlerThread thread = new HandlerThread("leak");
thread.start();
Handler handler = new Handler(thread.getLooper());
// Activity 销毁但线程仍在运行 → 泄漏

// ✅ 正确:及时退出线程
@Override
protected void onDestroy() {
    thread.quitSafely(); // 安全退出
    super.onDestroy();
}

细节3:getLooper() 的调用时机

// ❌ 错误:start() 前调用
HandlerThread thread = new HandlerThread("test");
Looper looper = thread.getLooper(); // null

// ✅ 正确:start() 后调用
thread.start();
Looper looper = thread.getLooper(); // 等待 Looper 创建完成

边界情况

  • 重复启动:调用两次 start() 会抛出 IllegalThreadStateException
  • 退出后发送消息:quit() 后 MessageQueue 不再接收新消息,返回 false
  • Looper 为 null:未 start() 或线程未启动成功时,getLooper() 返回 null

三、实际应用

3.1 典型场景

场景1:后台串行任务处理

  • 需求:顺序执行多个耗时任务(如数据库写入、文件操作)

  • 使用方式

    HandlerThread dbThread = new HandlerThread("database",
        Process.THREAD_PRIORITY_BACKGROUND);
    dbThread.start();
    
    Handler dbHandler = new Handler(dbThread.getLooper());
    
    // 发送任务(按顺序执行)
    dbHandler.post(() -> database.insert(data1));
    dbHandler.post(() -> database.insert(data2));
    dbHandler.post(() -> database.insert(data3));
    
  • 注意事项

    • 任务按发送顺序串行执行,不适合并行任务
    • 长时间运行需监控任务堆积情况

场景2:定时轮询任务

  • 需求:每隔 N 秒执行一次检查(如心跳包、传感器数据采集)

  • 使用方式

    HandlerThread pollingThread = new HandlerThread("polling");
    pollingThread.start();
    Handler pollingHandler = new Handler(pollingThread.getLooper());
    
    Runnable pollingTask = new Runnable() {
        @Override
        public void run() {
            // 执行轮询逻辑
            checkServerStatus();
    
            // 继续下一次轮询
            pollingHandler.postDelayed(this, 5000); // 5秒后再执行
        }
    };
    
    pollingHandler.post(pollingTask); // 启动轮询
    
    // 停止轮询
    pollingHandler.removeCallbacks(pollingTask);
    pollingThread.quitSafely();
    
  • 注意事项

    • 退出时先 removeCallbacks()quit(),避免泄漏
    • 不建议用于高频轮询(<100ms),考虑性能开销

场景3:多业务线程池替代

  • 需求:为不同业务模块创建独立的异步处理线程

  • 使用方式

    public class ThreadManager {
        private static HandlerThread sNetworkThread;
        private static Handler sNetworkHandler;
    
        private static HandlerThread sIoThread;
        private static Handler sIoHandler;
    
        public static Handler getNetworkHandler() {
            if (sNetworkHandler == null) {
                sNetworkThread = new HandlerThread("network");
                sNetworkThread.start();
                sNetworkHandler = new Handler(sNetworkThread.getLooper());
            }
            return sNetworkHandler;
        }
    
        public static Handler getIoHandler() {
            if (sIoHandler == null) {
                sIoThread = new HandlerThread("io",
                    Process.THREAD_PRIORITY_BACKGROUND);
                sIoThread.start();
                sIoHandler = new Handler(sIoThread.getLooper());
            }
            return sIoHandler;
        }
    }
    
    // 使用
    ThreadManager.getNetworkHandler().post(() -> httpRequest());
    ThreadManager.getIoHandler().post(() -> writeFile());
    
  • 注意事项

    • 避免创建过多 HandlerThread(推荐 3-5 个)
    • 应用退出时统一调用 quit() 释放资源

3.2 最佳实践

推荐做法

  1. 设置有意义的线程名称

    // 便于调试和性能分析
    HandlerThread thread = new HandlerThread("MyApp-Database");
    
  2. 根据任务类型设置优先级

    // 后台任务降低优先级,避免抢占 UI 线程资源
    HandlerThread logThread = new HandlerThread("log",
        Process.THREAD_PRIORITY_BACKGROUND);
    
  3. 优先使用 quitSafely()

    // 确保重要任务执行完再退出
    @Override
    protected void onDestroy() {
        mHandlerThread.quitSafely();
        super.onDestroy();
    }
    
  4. 异常处理

    Handler handler = new Handler(thread.getLooper()) {
        @Override
        public void handleMessage(Message msg) {
            try {
                // 任务逻辑
                processTask(msg);
            } catch (Exception e) {
                Log.e(TAG, "Task failed", e);
                // 通知主线程或上报错误
            }
        }
    };
    
  5. 生命周期管理

    public class MyActivity extends Activity {
        private HandlerThread mThread;
        private Handler mHandler;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mThread = new HandlerThread("MyActivity-Worker");
            mThread.start();
            mHandler = new Handler(mThread.getLooper());
        }
    
        @Override
        protected void onDestroy() {
            mHandler.removeCallbacksAndMessages(null); // 清除所有任务
            mThread.quitSafely();
            try {
                mThread.join(1000); // 等待线程结束(最多1秒)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            super.onDestroy();
        }
    }
    

常见错误

  1. 忘记调用 start()

    // ❌ 错误
    HandlerThread thread = new HandlerThread("test");
    Handler handler = new Handler(thread.getLooper()); // 阻塞或返回 null
    
    // ✅ 正确
    thread.start();
    Handler handler = new Handler(thread.getLooper());
    
  2. 忘记调用 quit()

    // ❌ 错误:线程永不结束
    HandlerThread thread = new HandlerThread("leak");
    thread.start();
    // Activity 销毁但线程仍在循环
    
    // ✅ 正确
    @Override
    protected void onDestroy() {
        thread.quitSafely();
        super.onDestroy();
    }
    
  3. 在主线程执行耗时操作

    // ❌ 错误:阻塞主线程
    Handler mainHandler = new Handler(Looper.getMainLooper());
    mainHandler.post(() -> {
        // 耗时操作
        queryDatabase();
    });
    
    // ✅ 正确:使用 HandlerThread
    Handler bgHandler = new Handler(mHandlerThread.getLooper());
    bgHandler.post(() -> {
        Result result = queryDatabase();
        // 切回主线程更新 UI
        mainHandler.post(() -> updateUI(result));
    });
    

3.3 性能优化建议

优化1:复用 HandlerThread

// ❌ 避免:为每个任务创建新线程
for (Task task : tasks) {
    HandlerThread thread = new HandlerThread("task");
    thread.start();
    new Handler(thread.getLooper()).post(task);
}

// ✅ 推荐:复用同一个 HandlerThread
HandlerThread sharedThread = new HandlerThread("shared-worker");
sharedThread.start();
Handler handler = new Handler(sharedThread.getLooper());
for (Task task : tasks) {
    handler.post(task);
}

优化2:控制消息队列长度

// 监控队列积压情况
Handler handler = new Handler(thread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        if (hasMessages(MSG_TASK)) {
            int pending = // 统计待处理消息数
            if (pending > 100) {
                Log.w(TAG, "Message queue overloaded: " + pending);
            }
        }
        super.handleMessage(msg);
    }
};

优化3:避免频繁创建 Message

// ❌ 避免:每次都 new Message()
handler.post(() -> doWork());

// ✅ 推荐:使用 Message.obtain() 复用
Message msg = Message.obtain(handler, () -> doWork());
handler.sendMessage(msg);

四、面试真题解析

4.1 基础必答题(P5必须掌握)


【高频题1】HandlerThread 和普通 Thread 有什么区别?

标准答案(30秒) : HandlerThread 继承自 Thread,主要区别是内部自动创建了 Looper 并开启消息循环。普通 Thread 的 run() 方法执行完就结束了,而 HandlerThread 会持续运行,可以通过 Handler 向它发送任务,实现串行化异步处理。使用完需要调用 quit() 退出循环。

深入展开(追问后) : 从源码层面看,HandlerThread 在 run() 方法中做了三件关键的事:

  1. 调用 Looper.prepare() 为当前线程创建 Looper
  2. 通过 synchronized + notifyAll() 保证 getLooper() 能安全获取 Looper 引用
  3. 调用 Looper.loop() 开启消息循环,阻塞等待处理消息

这样外部线程就可以通过 thread.getLooper() 获取 Looper,创建 Handler 并发送消息到该线程执行。普通 Thread 需要手动实现这些逻辑,且容易出错(如忘记 prepare 或 loop)。

面试官追问

  • 追问1:为什么 getLooper() 要用 synchronized 和 wait()?

    • 答:因为 start() 后线程不一定立即执行 run(),如果直接返回 mLooper 可能为 null。通过 wait() 阻塞等待,直到 run()notifyAll() 唤醒,确保 Looper 创建完成后才返回。这是典型的生产者-消费者模式的并发控制。
  • 追问2:quit() 和 quitSafely() 有什么区别?

    • 答:

      • quit():立即终止 Looper 循环,MessageQueue 中未处理的消息会被丢弃
      • quitSafely():先处理完消息队列中所有立即消息(when <= now),再退出循环,延迟消息会被移除
      • 实际开发推荐用 quitSafely(),避免丢失重要任务

【高频题2】HandlerThread 如何保证线程安全?

标准答案(30秒) : HandlerThread 的线程安全主要体现在两个方面:一是通过 synchronized 和 wait/notifyAll 保证 Looper 创建完成后才能被获取;二是 Looper 内部的 MessageQueue 使用了同步锁,保证多线程向同一个 Handler 发送消息时的线程安全。另外 HandlerThread 本身是单线程串行处理消息,不存在并发问题。

深入展开(追问后) : 从源码看,HandlerThread 的线程安全设计有三层保障:

  1. Looper 获取的并发控制(HandlerThread.java:110-120):

    public Looper getLooper() {
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                wait(); // 阻塞等待 run() 创建 Looper
            }
        }
        return mLooper;
    }
    

    通过对象锁 + wait/notify 确保多线程调用 getLooper() 时,只有 Looper 创建完成才返回。

  2. MessageQueue 的内部锁(MessageQueue.java): MessageQueue 的 enqueueMessage()next() 都使用了 synchronized (this) 保护消息链表操作,避免多线程竞争。

  3. 单线程消费者模型: HandlerThread 只有一个线程从队列取消息执行,任务串行处理,不会有多线程并发修改共享数据的问题。

面试官追问

  • 追问1:如果多个线程同时调用 getLooper() 会怎样?

    • 答:会安全等待并返回同一个 Looper 实例。第一个线程进入 synchronized 块后,如果 mLooper == null,会调用 wait() 释放锁并等待。当 run() 中设置 mLoopernotifyAll() 后,所有等待线程被唤醒,再次检查 mLooper != null,退出循环并返回。
  • 追问2:HandlerThread 中的任务会并发执行吗?

    • 答:不会。HandlerThread 只有一个线程从 MessageQueue 取消息并执行,所有任务按发送顺序串行处理。如果需要并发执行,应该使用线程池(如 ThreadPoolExecutor)而不是 HandlerThread。

【高频题3】HandlerThread 有哪些使用场景?什么情况不适合用?

标准答案(30秒) : HandlerThread 适合串行化执行异步任务的场景,比如数据库操作、日志写入、定时轮询、文件 I/O 等。它的优势是简化子线程 Looper 创建,且任务按顺序执行。不适合的场景包括:需要并发执行的任务、CPU 密集型计算、实时性要求极高的任务(因为前面任务阻塞会影响后续任务)。

深入展开(追问后)适合场景

  1. 顺序执行的 I/O 操作:如日志写入、SharedPreferences 写入,避免并发冲突
  2. 定时任务:通过 postDelayed() 实现心跳、数据同步等轮询
  3. 生产者-消费者模式:多个生产者(如 UI 线程)向 HandlerThread 发送任务,单线程消费
  4. 系统服务:IntentService 内部就是用 HandlerThread 实现的串行处理 Intent

不适合场景

  1. 并发任务:HandlerThread 是单线程,多个任务只能排队执行,无法利用多核
  2. 长时间阻塞任务:如果某个任务耗时很久,会阻塞后续所有任务
  3. 大量短任务:线程切换和消息分发有开销,不如直接在当前线程执行
  4. 实时性要求高:MessageQueue 是队列模型,无法保证任务立即执行

面试官追问

  • 追问1:HandlerThread 和线程池(ThreadPoolExecutor)如何选择?

    • 答:

      • HandlerThread:任务需要串行执行,或需要消息机制(延迟、移除、优先级)
      • 线程池:任务可以并发执行,需要控制线程数量,或任务间无依赖
      • 例如:数据库写入用 HandlerThread(避免并发冲突),图片下载用线程池(提升速度)
  • 追问2:能用多个 HandlerThread 提升并发吗?

    • 答:可以,但要注意:

      • 每个 HandlerThread 是独立线程,可以并行执行
      • 但如果任务操作共享资源(如数据库),需要额外加锁,失去了 HandlerThread 的简洁性
      • 通常做法是按业务模块划分(如网络线程、I/O 线程),而不是为每个任务创建 HandlerThread

【高频题4】HandlerThread 如何避免内存泄漏?

标准答案(30秒) : HandlerThread 的内存泄漏主要是因为线程一直运行导致关联对象无法释放。避免方法:一是在不需要时调用 quit()quitSafely() 退出循环;二是清除 Handler 中的待处理消息(removeCallbacksAndMessages(null));三是避免 Handler 持有 Activity 等组件的强引用,使用静态内部类 + 弱引用。

深入展开(追问后)泄漏原因分析

  1. 线程不结束:HandlerThread 的 Looper.loop() 是死循环,不调用 quit() 线程永不结束

  2. Handler 持有外部引用:非静态内部类 Handler 隐式持有外部类(如 Activity)引用

  3. 消息队列持有 Message:Message 持有 Handler 引用,Handler 又持有 Activity,形成引用链:

    MessageQueue → Message → Handler → Activity
    

    只要消息未处理完,Activity 就无法被 GC

完整防护方案

public class MyActivity extends Activity {
    private HandlerThread mThread;
    private MyHandler mHandler;

    // 1. 使用静态内部类 + 弱引用
    private static class MyHandler extends Handler {
        private final WeakReference<MyActivity> mActivity;

        MyHandler(Looper looper, MyActivity activity) {
            super(looper);
            mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MyActivity activity = mActivity.get();
            if (activity == null) return; // Activity 已销毁
            // 处理消息
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mThread = new HandlerThread("worker");
        mThread.start();
        mHandler = new MyHandler(mThread.getLooper(), this);
    }

    @Override
    protected void onDestroy() {
        // 2. 清除所有待处理消息
        mHandler.removeCallbacksAndMessages(null);

        // 3. 退出 Looper 循环
        mThread.quitSafely();

        // 4. 可选:等待线程结束
        try {
            mThread.join(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        super.onDestroy();
    }
}

面试官追问

  • 追问1:只调用 quit() 不清除消息会泄漏吗?

    • 答:可能会短暂泄漏。quit() 会移除所有消息,但如果调用 quit() 时正在处理某个消息,该消息执行完之前 Handler 和外部对象的引用链仍然存在。所以最佳实践是先 removeCallbacksAndMessages(null)quit()
  • 追问2:join() 的作用是什么?

    • 答:join() 等待线程结束,避免 Activity 销毁后线程仍在执行。参数是超时时间(毫秒),如果线程在超时时间内未结束,join() 返回但线程继续运行。这是一种防御性编程,确保资源及时释放。

【高频题5】HandlerThread 的 onLooperPrepared() 有什么用?

标准答案(30秒)onLooperPrepared() 是 HandlerThread 提供的钩子方法,在 Looper 准备完成后、开启消息循环前调用。子类可以重写这个方法,在 HandlerThread 自己的线程中初始化一些资源,比如创建内部的 Handler,避免外部手动调用 getLooper() 的并发问题。

深入展开(追问后) : 从源码看(HandlerThread.java:110-135):

@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared(); // ← 调用时机:Looper 已创建,loop() 未开始
    Looper.loop();
    mTid = -1;
}

protected void onLooperPrepared() {
    // 空实现,子类可重写
}

典型使用场景

public class DatabaseThread extends HandlerThread {
    private Handler mHandler;

    public DatabaseThread() {
        super("database", Process.THREAD_PRIORITY_BACKGROUND);
    }

    @Override
    protected void onLooperPrepared() {
        // 在当前线程(HandlerThread)中创建 Handler
        mHandler = new Handler(Looper.myLooper()) {
            @Override
            public void handleMessage(Message msg) {
                // 处理数据库操作
            }
        };
    }

    public Handler getHandler() {
        return mHandler; // 无需 getLooper(),避免并发等待
    }
}

面试官追问

  • 追问1:为什么不直接在 start() 后获取 Looper 创建 Handler?

    • 答:两种方式都可以,但 onLooperPrepared() 的优势是:

      1. 在 HandlerThread 自己的线程中初始化,避免主线程阻塞在 getLooper()wait()
      2. 可以直接用 Looper.myLooper() 而不是 thread.getLooper(),更直接
      3. 适合在子类中封装内部逻辑,外部只调用业务方法
  • 追问2:onLooperPrepared() 执行在哪个线程?

    • 答:执行在 HandlerThread 自己的线程中,也就是 run() 方法所在的线程。此时 Looper 已经创建完成,但消息循环还未开始,是初始化资源的最佳时机。

4.2 进阶加分题(P6/P6+)


【进阶题1】HandlerThread 的线程优先级是如何设置的?对性能有什么影响?

参考答案: HandlerThread 通过构造函数的 priority 参数设置线程优先级,最终调用 Process.setThreadPriority(mPriority)run() 中生效。线程优先级范围是 -20(最高)到 19(最低),默认值是 0。

从源码看(HandlerThread.java:85-95):

public HandlerThread(String name, int priority) {
    super(name);
    mPriority = priority;
}

@Override
public void run() {
    // ...
    Process.setThreadPriority(mPriority); // 设置优先级
    onLooperPrepared();
    Looper.loop();
}

性能影响

  1. 高优先级(负值) :线程更容易被调度,适合实时性要求高的任务(如网络请求),但会抢占 CPU,影响其他线程
  2. 低优先级(正值) :线程被调度频率降低,适合后台任务(如日志、数据同步),避免影响 UI 流畅度
  3. 默认优先级(0) :与主线程同优先级,适合一般任务

实际建议

  • 前台任务THREAD_PRIORITY_DEFAULT(0)或 THREAD_PRIORITY_DISPLAY(-4)
  • 后台任务THREAD_PRIORITY_BACKGROUND(10)
  • 紧急任务THREAD_PRIORITY_URGENT_DISPLAY(-8),需谨慎使用

追问

  • 如果设置优先级为 -20,会发生什么?

    • 答:线程会以最高优先级运行,极大概率抢占 CPU 时间片,可能导致主线程卡顿、ANR。Android 系统会记录这种行为,长期使用可能触发性能警告。实际开发不建议使用 < -10 的优先级。

【进阶题2】HandlerThread 和 IntentService 的关系是什么?为什么 IntentService 被废弃了?

参考答案: IntentService 是基于 HandlerThread 实现的服务组件,内部创建了一个 HandlerThread 用于串行处理 Intent 任务。每次调用 startService() 时,Intent 被封装成消息发送到 HandlerThread,在子线程中执行 onHandleIntent(),执行完自动调用 stopSelf()

源码关系(IntentService.java,Android 11):

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper; // HandlerThread 的 Looper
    private volatile ServiceHandler mServiceHandler; // 处理 Intent 的 Handler

    @Override
    public void onCreate() {
        super.onCreate();
        // 创建 HandlerThread
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        // 获取 Looper 并创建 Handler
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj); // 子线程执行
            stopSelf(msg.arg1); // 执行完自动停止
        }
    }
}

被废弃的原因(Android 11 标记 @Deprecated):

  1. 后台限制:Android 8.0+ 对后台服务有严格限制,IntentService 作为后台服务很容易被杀死
  2. 功能单一:只能串行处理任务,无法并发,且无法自定义优先级
  3. WorkManager 替代:Google 推荐使用 WorkManager 处理后台任务,支持约束条件、重试机制、链式调用
  4. 前台服务要求:Android 8.0+ 后台服务需要在 5 秒内调用 startForeground(),IntentService 没有自动处理

追问

  • IntentService 和直接用 HandlerThread 有什么区别?

    • 答:

      • IntentService 自动管理生命周期(任务完成自动 stopSelf),HandlerThread 需要手动 quit
      • IntentService 封装了 Intent 处理逻辑,适合处理 Service 任务,HandlerThread 更通用
      • IntentService 绑定 Service 生命周期,HandlerThread 可以独立于组件存在

【进阶题3】如果 HandlerThread 执行任务时抛出异常,会发生什么?

参考答案: 如果 HandlerThread 处理消息时抛出未捕获异常,会触发 UncaughtExceptionHandler,默认行为是打印堆栈并终止线程。此时 Looper 循环退出,HandlerThread 线程结束,后续消息无法处理。

异常传播流程

handleMessage() 抛异常
  ↓
Looper.loop() 捕获异常(try-catch)
  ↓
调用 Thread.getDefaultUncaughtExceptionHandler()
  ↓
默认 handler 打印堆栈并调用 System.exit()(JVM 行为)
  ↓
Android 中触发应用崩溃或线程终止

从 Looper 源码看(Looper.java:194-210,Android 11):

public static void loop() {
    for (;;) {
        Message msg = queue.next();
        if (msg == null) return; // 队列退出

        try {
            msg.target.dispatchMessage(msg); // 可能抛异常
        } catch (Exception exception) {
            // Looper 本身不处理异常,继续循环
            throw exception; // 重新抛出,交给线程的 UncaughtExceptionHandler
        } finally {
            msg.recycleUnchecked();
        }
    }
}

防护方案

// 方案1:在 handleMessage 中捕获异常
Handler handler = new Handler(thread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        try {
            // 任务逻辑
            riskyOperation();
        } catch (Exception e) {
            Log.e(TAG, "Task failed", e);
            // 上报错误或降级处理
        }
    }
};

// 方案2:设置全局异常处理器
thread.setUncaughtExceptionHandler((t, e) -> {
    Log.e(TAG, "Thread crashed: " + t.getName(), e);
    // 重新创建 HandlerThread(可选)
    recreateThread();
});

追问

  • Looper.loop() 为什么不直接 catch 住异常继续循环?

    • 答:因为 loop() 不知道异常的严重程度,如果是致命错误(如 OOM),继续运行可能导致不可预期的行为。Android 的设计哲学是"快速失败"(Fail Fast),让异常暴露出来,而不是静默吞掉。开发者应该在业务代码中处理可预期的异常。

4.3 实战场景题


【场景题】如何用 HandlerThread 实现一个线程安全的日志写入类?

问题描述: 需要设计一个日志管理类,要求:

  1. 支持多个线程并发写入日志
  2. 日志按时间顺序写入文件,不能乱序
  3. 避免频繁 I/O 操作,支持批量写入
  4. 应用退出时确保日志全部写入

答案思路

1. 分析

  • 并发安全:多线程写日志需要线程安全,HandlerThread 单线程消费天然保证顺序
  • 串行写入:HandlerThread 按消息顺序处理,满足时间顺序要求
  • 批量优化:可以用延迟消息合并多条日志再写入
  • 可靠退出:quit 前处理完所有日志消息

2. 实现方案

public class LogWriter {
    private static final int MSG_WRITE = 1;
    private static final int BATCH_SIZE = 50;
    private static final int FLUSH_DELAY = 1000; // 1秒

    private HandlerThread mThread;
    private Handler mHandler;
    private List<String> mBuffer = new ArrayList<>();
    private File mLogFile;

    public LogWriter(String filePath) {
        mLogFile = new File(filePath);

        // 创建后台线程,降低优先级
        mThread = new HandlerThread("LogWriter",
            Process.THREAD_PRIORITY_BACKGROUND);
        mThread.start();

        // 创建 Handler
        mHandler = new Handler(mThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == MSG_WRITE) {
                    String log = (String) msg.obj;
                    synchronized (mBuffer) {
                        mBuffer.add(log);

                        // 达到批量大小立即写入
                        if (mBuffer.size() >= BATCH_SIZE) {
                            flushLogs();
                        } else {
                            // 否则延迟写入(合并后续日志)
                            removeMessages(MSG_WRITE);
                            sendEmptyMessageDelayed(MSG_WRITE, FLUSH_DELAY);
                        }
                    }
                }
            }
        };
    }

    // 对外接口:写入日志(线程安全)
    public void log(String message) {
        String logEntry = System.currentTimeMillis() + " " + message + "\n";
        Message msg = Message.obtain(mHandler, MSG_WRITE, logEntry);
        mHandler.sendMessage(msg);
    }

    // 批量写入文件
    private void flushLogs() {
        synchronized (mBuffer) {
            if (mBuffer.isEmpty()) return;

            try (FileWriter writer = new FileWriter(mLogFile, true)) {
                for (String log : mBuffer) {
                    writer.write(log);
                }
                writer.flush();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                mBuffer.clear();
            }
        }
    }

    // 应用退出时调用
    public void close() {
        // 清除延迟消息,立即写入
        mHandler.removeMessages(MSG_WRITE);
        flushLogs();

        // 安全退出线程
        mThread.quitSafely();
        try {
            mThread.join(2000); // 等待最多2秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

// 使用
LogWriter logger = new LogWriter("/sdcard/app.log");
logger.log("User login"); // 线程1
logger.log("Network request"); // 线程2
logger.log("Database query"); // 线程3
// ...
logger.close(); // 应用退出时

3. 关键点

  • 使用 HandlerThread 保证日志串行写入,避免文件并发冲突
  • 通过缓冲区 + 批量写入减少 I/O 次数
  • 延迟消息实现自动 flush,避免日志丢失
  • close() 中先清除延迟消息再 flush,确保所有日志写入

追问

  • 方案缺点?

    • 答:

      1. 单线程写入,高并发时可能积压消息(可通过监控队列长度优化)
      2. 内存缓冲区有丢失风险(应用崩溃时未写入的日志丢失,可定期 flush)
      3. 文件 I/O 仍可能阻塞线程(可使用 BufferedWriter 优化)
  • 其他方案?

    • 答:

      1. AsyncTask(已废弃) :不适合长时间运行
      2. 线程池 + 同步锁:可并发但需要额外加锁,复杂度高
      3. 第三方库:如 Timber、Logback,功能更强大但引入依赖
  • 如何优化?

    • 答:

      1. 监控消息队列长度,超过阈值暂停接收或丢弃低优先级日志
      2. 使用 BufferedWriter 代替 FileWriter,减少系统调用
      3. 日志文件轮转(按大小或日期切分),避免单文件过大

五、对比与总结

5.1 关键API对比

API/方法作用使用场景注意事项
HandlerThread(String name)创建默认优先级的 HandlerThread通用异步任务需手动调用 start()
HandlerThread(String name, int priority)创建指定优先级的 HandlerThread后台任务(设为 BACKGROUND)、前台任务优先级范围 -20 ~ 19
start()启动线程,执行 run()创建后立即调用只能调用一次,重复调用抛异常
getLooper()获取线程的 Looper创建 Handler 时必须在 start() 后调用,会阻塞等待
quit()立即退出 Looper 循环任务处理完不再需要时未处理的消息会被丢弃
quitSafely()处理完立即消息后退出需要确保重要任务执行完延迟消息会被移除
onLooperPrepared()Looper 准备完成的回调子类需要初始化内部资源执行在 HandlerThread 线程中
getThreadId()获取线程 ID调试、日志记录run() 执行前返回 -1

5.2 核心要点速记

一句话记忆: HandlerThread = Thread + Looper,封装了在子线程中创建消息循环的标准流程,用于串行化处理异步任务。

3个关键点

  1. 自动化创建 Looper:内部自动调用 Looper.prepare()Looper.loop(),无需手动管理
  2. 线程安全的 Looper 获取:通过 synchronized + wait/notify 保证 getLooper() 在 Looper 创建后才返回
  3. 必须手动退出:使用完必须调用 quit()quitSafely(),否则线程永不结束(内存泄漏)

面试官最爱问

  1. HandlerThread 和普通 Thread 的区别? (必问)
  2. quit() 和 quitSafely() 的区别? (高频)
  3. 如何避免 HandlerThread 内存泄漏? (高频)
  4. HandlerThread 适合什么场景? (常问)
  5. HandlerThread 如何保证线程安全? (进阶)

六、关联知识点

前置知识

  • Handler 基础(详见:../01-Handler基础/01-Handler基本概念.md):理解 Handler 的作用和基本用法
  • Looper 原理(详见:../02-Looper原理/01-Looper创建与启动.md):理解 Looper.prepare()Looper.loop() 的作用
  • MessageQueue 工作原理(详见:../03-MessageQueue工作原理/):理解消息如何入队和出队

后续扩展

  • IntentService(详见:./02-IntentService.md):学习 HandlerThread 在系统服务中的典型应用
  • 主线程 Looper 创建时机(详见:./03-主线程Looper创建时机.md):了解主线程 Looper 的特殊性
  • ANR 监控原理(详见:./04-ANR监控原理.md):理解消息处理超时的监控机制
  • 内存泄漏(详见:../07-内存泄漏/):深入学习 Handler 相关的内存泄漏场景

相关文件

  • ./02-IntentService.md - IntentService 内部使用 HandlerThread 实现异步任务队列
  • ../05-同步屏障/ - 理解消息优先级机制(HandlerThread 中也可用)
  • ../06-IdleHandler/ - 在 HandlerThread 空闲时执行任务的机制