面试题 - Android 异步任务和消息机制

153 阅读25分钟

1. HandlerThread 的使用场景和实现原理

HandlerThread 其实就是一个带有消息循环的线程,特别适合用在需要长期在后台运行,并且需要按顺序处理任务的场景。

使用场景:

  • 网络请求队列处理
  • 文件读写操作
  • 数据库操作

实现原理:

public class HandlerThread extends Thread {
    @Override
    public void run() {
        Looper.prepare(); // 创建消息循环
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Looper.loop(); // 开启消息循环
    }
}

底层实现原理

让我以生动形象的方式讲解 HandlerThread 的实现原理:

🌟 HandlerThread 的形象比喻

想象一下有一个专门的快递分拣员(HandlerThread),他有以下特点:

  1. 他有自己的工作台(Looper)
  2. 有一个任务箱(MessageQueue)
  3. 按顺序处理包裹(Message)
  4. 一直工作直到被告知停止(quit)

🔄 工作流程

public class HandlerThread extends Thread {
    Looper mLooper;
    private @Nullable Handler mHandler;
    
    @Override
    public void run() {
        // 1. 准备工作台
        Looper.prepare();
        
        // 2. 通知工作台准备完毕
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        
        // 3. 开始无限循环处理消息
        Looper.loop();
    }
    
    // 获取该线程的Looper
    public Looper getLooper() {
        // 如果线程没有启动或已经结束,返回null
        if (!isAlive()) {
            return null;
        }
        
        // 等待线程创建Looper
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
}

📊 流程图

graph TD
    A[创建HandlerThread] --> B[调用start方法]
    B --> C[run方法执行]
    C --> D[Looper.prepare创建Looper]
    D --> E[保存Looper实例并通知等待线程]
    E --> F[Looper.loop开启消息循环]
    F --> G{是否调用quit?}
    G -->|否| F
    G -->|是| H[结束循环]
    
    I[外部获取Looper] --> J{线程是否存活?}
    J -->|是| K[等待Looper创建完成]
    J -->|否| L[返回null]
    K --> M[返回Looper实例]

🌈 使用示例

// 创建HandlerThread
HandlerThread handlerThread = new HandlerThread("MyHandlerThread");
// 启动线程
handlerThread.start();

// 创建Handler
Handler handler = new Handler(handlerThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        // 处理消息
    }
};

// 发送消息
handler.sendMessage(msg);

// 使用完毕后退出
handlerThread.quit();

💡 关键点总结

  1. 初始化过程

    • 创建 HandlerThread 实例
    • 调用 start() 方法启动线程
    • 在 run() 方法中创建 Looper
    • 保存 Looper 实例并通知等待线程
    • 开启消息循环
  2. 同步机制

    • 使用 synchronized 和 wait/notify 确保 Looper 创建完成
    • getLooper() 方法会等待直到 Looper 创建完成
  3. 消息处理

    • 所有消息在同一个线程中按顺序处理
    • 通过 Handler 发送消息到 HandlerThread
    • 可以通过 quit() 方法终止消息循环
  4. 生命周期

    • 创建 → 启动 → 循环处理消息 → 退出
    • 需要手动调用 quit() 来终止线程

这就是 HandlerThread 的核心实现原理,它通过封装 Handler 和 Looper 的创建过程,为我们提供了一个简单易用的消息处理线程。

更生活化的例子

让我用一个真实的快递分拣中心的例子来解释 HandlerThread。

想象你开了一个快递分拣中心:

  1. 前台接待员(主线程)
  • 负责接收快递包裹
  • 负责与客户沟通
  • 不能去后面分拣,否则没人接待客户了
  1. 分拣工人(HandlerThread)
  • 专门在后面分拣包裹
  • 按照包裹到达的顺序处理
  • 一个人按顺序处理,不会搞乱
  1. 传送带(Handler)
  • 用来传递包裹和消息
  • 接待员用它传递包裹给分拣工人
  • 分拣工人用它通知接待员分拣完成

看代码示例:

public class ExpressCenter extends AppCompatActivity {
    private HandlerThread sorterThread;    // 分拣工人
    private Handler sorterHandler;         // 传递给分拣工人的传送带
    private Handler frontDeskHandler;      // 前台接待的传送带

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 1. 雇佣一个分拣工人
        sorterThread = new HandlerThread("分拣工人");
        sorterThread.start();

        // 2. 给前台一个传送带
        frontDeskHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                // 前台收到分拣完成的通知,更新状态
                if (msg.what == 1) {
                    String packageId = (String) msg.obj;
                    updateUI("包裹 " + packageId + " 分拣完成");
                }
            }
        };

        // 3. 给分拣工人一个传送带
        sorterHandler = new Handler(sorterThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                // 分拣工人开始处理包裹
                if (msg.what == 1) {
                    String packageId = (String) msg.obj;
                    
                    // 模拟分拣时间
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    // 分拣完成,通知前台
                    Message doneMsg = frontDeskHandler.obtainMessage(1);
                    doneMsg.obj = packageId;
                    frontDeskHandler.sendMessage(doneMsg);
                }
            }
        };

        // 4. 当新包裹到达时
        Button newPackageButton = findViewById(R.id.newPackageButton);
        newPackageButton.setOnClickListener(v -> {
            // 生成包裹ID
            String packageId = "PKG" + System.currentTimeMillis();
            
            // 通过传送带发给分拣工人
            Message msg = sorterHandler.obtainMessage(1);
            msg.obj = packageId;
            sorterHandler.sendMessage(msg);
            
            updateUI("包裹 " + packageId + " 已交给分拣工人");
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 5. 下班了,让分拣工人回家
        sorterThread.quitSafely();
    }
}

特点说明:

  1. 有序处理

    • 就像快递分拣一样,一个包裹一个包裹按顺序处理
    • 不会因为同时处理多个包裹而混乱
  2. 专人专责

    • 分拣工人(HandlerThread)专门负责分拣
    • 前台(主线程)专门负责接待
    • 各司其职,互不干扰
  3. 通信方便

    • 通过传送带(Handler)方便地传递包裹和消息
    • 前台知道每个包裹的分拣状态
    • 分拣工人也能及时通知前台

生活中的其他例子:

  • 餐厅的后厨(一个厨师按顺序做菜)
  • 咖啡店的咖啡师(按顺序制作咖啡)
  • 理发店的理发师(按顺序为客人理发)

这些场景都和 HandlerThread 很像:

  1. 需要按顺序处理任务
  2. 有专门的人(线程)处理
  3. 处理过程不能打断
  4. 需要和前台及时通信

这样理解 HandlerThread 是不是清晰多了?

2. IntentService 的应用场景和实现原理

IntentService 是一个很特别的 Service,它能自动处理异步任务并在处理完成后自动停止。

应用场景:

  • 后台下载
  • 数据备份
  • 文件上传

核心实现:

public abstract class IntentService extends Service {
    private final class ServiceHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj); // 处理Intent
            stopSelf(msg.arg1); // 任务完成后自动停止
        }
    }
}

底层实现原理

IntentService 工作原理详解

想象一下,IntentService 就像是一个有序的快递处理中心:

  1. 你发送的每个 Intent 就像一个快递包裹
  2. HandlerThread 就像是一个专门处理包裹的工作人员
  3. ServiceHandler 就像是工作人员的任务清单

核心实现代码解析

public abstract class IntentService extends Service {
    // 专门处理任务的线程
    private volatile HandlerThread mServiceThread;
    
    // 用于在工作线程处理消息的 Handler
    private volatile ServiceHandler mServiceHandler;
    
    // Handler 的具体实现
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            // 处理具体的 Intent
            onHandleIntent((Intent)msg.obj);
            // 处理完一个消息后,检查是否还有待处理的消息
            // 如果没有,就停止 Service
            stopSelf(msg.arg1);
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        // 创建后台工作线程
        mServiceThread = new HandlerThread("IntentService[" + mName + "]");
        // 启动线程
        mServiceThread.start();
        // 获取工作线程的 Looper
        mServiceHandler = new ServiceHandler(mServiceThread.getLooper());
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 将 Intent 包装成消息,发送给工作线程处理
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        // 退出工作线程的消息循环
        mServiceThread.quit();
    }
}

工作流程图

graph TD
    A[应用程序] -->|startService| B[IntentService]
    B -->|onCreate| C[创建HandlerThread]
    C -->|start| D[创建ServiceHandler]
    B -->|onStartCommand| E[发送消息到Handler]
    E -->|handleMessage| F[onHandleIntent处理任务]
    F -->|处理完成| G[stopSelf停止服务]
    G -->|如果没有新消息| H[onDestroy销毁服务]

关键特点总结

  1. 串行处理

    • 所有任务在同一个工作线程中按顺序处理
    • 不需要担心线程同步问题
  2. 自动停止

    • 处理完所有任务后自动停止服务
    • 不需要手动管理服务的生命周期
  3. 工作流程

    • 创建服务时启动工作线程
    • 接收到 Intent 时将其封装成消息
    • 在工作线程中处理消息
    • 处理完成后检查是否需要停止服务

使用示例

public class DownloadService extends IntentService {
    public DownloadService() {
        super("DownloadService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // 在工作线程中执行下载任务
        String url = intent.getStringExtra("url");
        downloadFile(url);
    }
}

// 使用方式
Intent intent = new Intent(context, DownloadService.class);
intent.putExtra("url", "http://example.com/file.pdf");
context.startService(intent);

适用场景

  1. 后台下载:按顺序下载多个文件
  2. 数据同步:按顺序同步多个数据源
  3. 定时任务:需要在后台按顺序处理的定时任务

这就是 IntentService 的核心实现原理,它通过 HandlerThread 和 Handler 的配合,实现了一个简单但功能强大的异步任务处理机制。

3. AsyncTask 的优缺点和实现原理

优点:

  • 简化异步操作
  • 自动切换线程
  • 可以显示进度

缺点:

  • 可能导致内存泄露
  • 在 Activity 旋转时容易出问题
  • 在 Android 3.0 后串行执行任务

实现原理:

public abstract class AsyncTask<Params, Progress, Result> {
    private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR;
    
    @MainThread
    protected void onPreExecute() {} // 主线程执行
    
    @WorkerThread
    protected abstract Result doInBackground(Params... params); // 工作线程执行
    
    @MainThread
    protected void onPostExecute(Result result) {} // 主线程执行
}

底层原理

AsyncTask 工作流程图

graph TD
    A[新建AsyncTask] --> B[执行execute]
    B --> C[onPreExecute 主线程]
    C --> D[doInBackground 后台线程]
    D --> |进度更新| E[onProgressUpdate 主线程]
    D --> F[onPostExecute 主线程]
    
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style D fill:#bbf,stroke:#333,stroke-width:2px
    style F fill:#bfb,stroke:#333,stroke-width:2px

详细实现原理讲解

想象 AsyncTask 就像一个搬家公司的工作流程:

  1. 准备阶段 (构造函数):
public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() {
            // 相当于搬家公司的工人准备工具
            return doInBackground(mParams);
        }
    };
    
    mFuture = new FutureTask<Result>(mWorker);
}
  1. 开工前的准备 (execute 方法):
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    // 就像搬家前的现场勘察
    onPreExecute(); // 在主线程执行
    
    // 派遣工人去干活
    sExecutor.execute(mFuture);
    return this;
}
  1. 实际工作过程 (doInBackground):
protected abstract Result doInBackground(Params... params);
// 这就像搬家工人在实际搬运物品
// 在后台线程执行耗时操作
  1. 进度通知 (publishProgress):
protected final void publishProgress(Progress... values) {
    // 就像工人实时汇报工作进度
    sHandler.obtainMessage(MESSAGE_POST_PROGRESS, 
        new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
  1. 完工通知 (onPostExecute):
protected void onPostExecute(Result result) {
    // 相当于工作完成后的验收环节
}

线程池机制

AsyncTask 使用的线程池就像一个搬家公司的人力资源管理:

private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
    // 核心线程数(常驻工人)
    CORE_POOL_SIZE,
    // 最大线程数(最多可以雇佣的临时工)
    MAXIMUM_POOL_SIZE,
    // 空闲线程等待新任务的最长时间
    KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
    // 任务队列(等待处理的搬家订单)
    sPoolWorkQueue
);

消息传递机制

private static class InternalHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        AsyncTaskResult result = (AsyncTaskResult) msg.obj;
        switch (msg.what) {
            case MESSAGE_POST_PROGRESS:
                // 通知主线程更新进度
                result.mTask.onProgressUpdate(result.mData);
                break;
            case MESSAGE_POST_RESULT:
                // 通知主线程任务完成
                result.mTask.finish(result.mData[0]);
                break;
        }
    }
}

总结要点:

  1. 线程切换

    • onPreExecute:主线程
    • doInBackground:后台线程
    • onProgressUpdate:主线程
    • onPostExecute:主线程
  2. 工作流程

    • 创建 → 准备 → 执行 → 进度更新 → 完成
  3. 特点

    • 自动线程切换
    • 支持进度更新
    • 有序的任务队列
    • 结果自动回调主线程

这就像一个完整的搬家服务:从接单、准备工具、实际搬运、进度通知到最终完工验收,每个环节都井然有序。

4. Activity.runOnUiThread 的理解

这是一个很巧妙的设计,它能确保代码在主线程执行:

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() == mUiThread) {
        action.run();
    } else {
        mHandler.post(action);
    }
}

底层原理

1. 形象化解释

想象一下我们在玩篮球:

  • 主线程就像是球场上的主裁判
  • runOnUiThread 就像是一个"传球"的动作
  • Runnable 就是我们要传递的球

当我们调用 runOnUiThread 时,系统会先看:

  1. 如果当前是主裁判(主线程)持球,就直接投篮(执行任务)
  2. 如果是其他球员(子线程)持球,就把球传给主裁判(通过 Handler 转发给主线程)

2. 源码分析

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() == mUiThread) {
        // 如果当前就是UI线程,直接执行
        action.run();
    } else {
        // 如果是其他线程,通过Handler发送到UI线程
        mHandler.post(action);
    }
}

3. 流程图

graph TD
    A[调用 runOnUiThread] --> B{是否在主线程?}
    B -->|是| C[直接执行 Runnable]
    B -->|否| D[通过 Handler 投递到消息队列]
    D --> E[主线程 Handler 处理消息]
    E --> F[执行 Runnable]

4. 详细执行流程

  1. 判断当前线程

    • 通过 Thread.currentThread() == mUiThread 判断
    • mUiThread 是 Activity 持有的主线程引用
  2. 主线程情况

    • 直接调用 action.run()
    • 不需要任何线程切换
    • 执行效率最高
  3. 非主线程情况

    • 使用 Activity 的 mHandler 发送消息
    • Handler 将 Runnable 包装成 Message
    • 投递到主线程的 MessageQueue
    • Looper 循环取出并执行

5. 使用示例

// 在任意线程中
activity.runOnUiThread(new Runnable() {
    @Override
    public void run() {
        // 这里的代码保证在主线程执行
        textView.setText("更新UI");
    }
});

6. 注意事项

  1. 生命周期安全

    • Activity 销毁时要注意取消未执行的 Runnable
    • 可能导致内存泄露
  2. 执行时机

    • 非主线程调用时,执行时机取决于主线程消息队列的情况
    • 不能保证立即执行
  3. 性能考虑

    • 主线程直接执行时性能最好
    • 通过 Handler 调度会有轻微的性能损耗

7. 实际应用建议

  1. 简单任务
runOnUiThread(() -> updateUI());
  1. 带延时的任务
runOnUiThread(new Runnable() {
    @Override
    public void run() {
        // 可以配合 Handler 实现延时
        mHandler.postDelayed(this, 1000);
    }
});
  1. 取消任务
private Runnable mUpdateUITask = new Runnable() {
    @Override
    public void run() {
        updateUI();
    }
};

// 在需要时取消
@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacks(mUpdateUITask);
}

这个机制看似简单,但实际上是 Android 线程切换的一个非常优雅的设计,让开发者可以在任意线程安全地更新 UI。

5. Android 子线程更新 UI

严格来说,Android 是允许子线程更新 UI 的,但需要满足两个条件:

  1. 这个 View 必须是你自己创建的
  2. 必须在 ViewRootImpl 创建之前

但是为了线程安全,我们还是应该在主线程更新 UI。

底层原理

一、基本原理讲解

想象一下,UI 就像一个画布,而 Android 系统就是这个画布的保安。一般情况下,保安只允许主线程(专业画家)在画布上作画,其他线程(业余画家)都会被拦住。

但是这个"拦截机制"是在 ViewRootImpl 被创建的时候才开始的。就像画布还没有被架设好的时候,任何人都可以在上面涂鸦。

二、源码分析

// ViewRootImpl.java
void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
            "Only the original thread that created a view hierarchy can touch its views.");
    }
}

这段代码就是那个"保安",它会检查当前操作 UI 的线程是否是创建 View 的原始线程。

三、具体流程

  1. View 的创建阶段
// Activity.java
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main); // 此时 ViewRootImpl 还未创建
}
  1. ViewRootImpl 的创建时机
// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    // ...
    ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
    // ...
}

四、流程图

graph TD
    A[Activity onCreate] --> B[setContentView]
    B --> C[View 创建]
    C --> D[第一次 ViewRootImpl 创建]
    D --> E{是否在主线程更新 UI}
    E -->|是| F[允许更新 UI]
    E -->|否| G[检查线程]
    G --> H{是否在 ViewRootImpl 创建前}
    H -->|是| F
    H -->|否| I[抛出异常]

五、实际演示

public class MainActivity extends AppCompatActivity {
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.text_view);

        // 这时候可以在子线程更新 UI(不推荐)
        new Thread(new Runnable() {
            @Override
            public void run() {
                textView.setText("Hello"); // 这里不会崩溃
            }
        }).start();

        // 等待一会,ViewRootImpl 创建后
        textView.postDelayed(new Runnable() {
            @Override
            public void run() {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            textView.setText("World"); // 这里会崩溃
                        } catch (Exception e) {
                            Log.e("MainActivity", "不能在子线程更新UI");
                        }
                    }
                }).start();
            }
        }, 1000);
    }
}

六、总结要点

  1. 时机很重要

    • ViewRootImpl 创建前:子线程可以更新 UI
    • ViewRootImpl 创建后:只能主线程更新 UI
  2. 实际应用建议

    • 始终在主线程更新 UI
    • 使用 Handler、runOnUiThread 等安全机制
    • 不要依赖这个"漏洞"特性
  3. 常用的线程切换方式

// 方式一:Handler
private Handler mHandler = new Handler(Looper.getMainLooper());
mHandler.post(() -> updateUI());

// 方式二:runOnUiThread
runOnUiThread(() -> updateUI());

// 方式三:View.post
view.post(() -> updateUI());

这个机制就像是一个特殊时期的"漏洞",虽然存在,但在实际开发中我们应该遵循 Android 的线程规范,始终在主线程更新 UI,这样才能保证应用的稳定性和可维护性。

6. Android 消息机制原理

核心组件:

  • Handler:发送和处理消息
  • Looper:消息循环
  • MessageQueue:消息队列
  • Message:消息本身
class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare();
        mHandler = new Handler();
        Looper.loop();
    }
}

底层原理

一、核心角色介绍

想象一个邮局系统:

  1. Handler(邮递员):负责投递和处理信件
  2. Message(信件):需要传递的消息
  3. MessageQueue(邮箱):存放消息的队列
  4. Looper(邮局分拣中心):不断循环处理消息

二、详细工作流程

1. 消息的发送过程
// Handler 发送消息
public boolean sendMessage(Message msg) {
    // 1. 给消息标记目标 Handler
    msg.target = this;
    // 2. 将消息放入队列
    return mQueue.enqueueMessage(msg, uptimeMillis);
}
2. 消息队列的维护
// MessageQueue 维护消息
public boolean enqueueMessage(Message msg, long when) {
    // 根据时间顺序将消息插入队列
    Message p = mMessages;
    if (p == null || when == 0 || when < p.when) {
        msg.next = p;
        mMessages = msg;
    }
    // ...
}
3. Looper 的循环处理
public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    
    for (;;) {
        // 1. 取出下一条消息
        Message msg = queue.next();
        if (msg == null) {
            return;
        }
        
        // 2. 分发消息给对应的 Handler 处理
        msg.target.dispatchMessage(msg);
    }
}

三、流程图

graph TD
    A[Handler] -->|1. sendMessage| B[Message]
    B -->|2. enqueueMessage| C[MessageQueue]
    D[Looper] -->|3. loop循环取消息| C
    D -->|4. dispatchMessage| A
    A -->|5. handleMessage| E[处理消息]

四、关键要点解析

  1. 消息池机制
// Message 复用机制
Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0;
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}
  1. 同步屏障机制
// 发送同步屏障
public int postSyncBarrier() {
    // 插入一个没有 target 的 Message
    // 用于拦截同步消息,优先处理异步消息
}

五、实际应用示例

// 典型使用场景
class MyActivity extends Activity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_UPDATE_UI:
                    updateUI();
                    break;
                case MSG_SHOW_TOAST:
                    showToast();
                    break;
            }
        }
    };
    
    private void doWork() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 耗时操作
                Message msg = Message.obtain();
                msg.what = MSG_UPDATE_UI;
                mHandler.sendMessage(msg);
            }
        }).start();
    }
}

六、完整流程总结

  1. 初始化阶段

    • 主线程创建时自动初始化 Looper
    • 创建 MessageQueue
    • 创建主线程 Handler
  2. 消息发送阶段

    • 创建 Message 对象
    • Handler 将消息加入 MessageQueue
    • 消息按时间顺序排队
  3. 消息处理阶段

    • Looper 不断从队列提取消息
    • 分发给对应的 Handler
    • Handler 处理消息
  4. 线程切换

    • 子线程通过 Handler 发送消息到主线程
    • 主线程 Looper 处理消息实现线程切换

七、性能优化建议

  1. 使用 Message.obtain() 而不是 new Message()
  2. 及时移除不需要的消息
  3. 避免在 Handler 中执行耗时操作
  4. 注意内存泄漏问题

这套机制的精妙之处在于:

  • 完美解决了 Android 的线程通信问题
  • 保证了 UI 操作的线程安全
  • 通过消息池和同步屏障提供了性能保障

7. 子线程创建 Handler 抛异常原因

因为 Handler 需要依附于 Looper,而子线程默认没有 Looper:

public Handler() {
    if (Looper.myLooper() == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
}

底层原理

子线程创建 Handler 抛异常的原理详解

想象一下,Handler 就像是一个邮递员,它需要依赖于一个邮局系统(Looper)来工作。

1. 创建过程解析
// 当我们在子线程中直接创建 Handler
public class MyThread extends Thread {
    private Handler handler;
    
    @Override
    public void run() {
        // 直接创建 Handler 会抛出异常
        handler = new Handler(); // RuntimeException!
    }
}
2. 源码分析
// Handler 构造函数
public Handler() {
    this(null, false);
}

public Handler(@Nullable Callback callback, boolean async) {
    // 获取当前线程的 Looper
    mLooper = Looper.myLooper();  
    // 关键点:如果 Looper 为空则抛出异常
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
3. 正确的使用方式
public class MyThread extends Thread {
    private Handler handler;
    
    @Override
    public void run() {
        // 1. 首先为当前线程创建 Looper
        Looper.prepare();
        
        // 2. 然后创建 Handler
        handler = new Handler();
        
        // 3. 开启消息循环
        Looper.loop();
    }
}
关键要点总结:
  1. 为什么会抛异常?

    • Handler 必须绑定一个 Looper
    • 主线程默认有 Looper(ActivityThread 会自动创建)
    • 子线程默认没有 Looper
  2. 如何解决?

    • 调用 Looper.prepare() 创建 Looper
    • 调用 Looper.loop() 开启消息循环
    • 使用 HandlerThread(它内部已经做好了这些工作)
  3. 实际应用建议:

// 推荐使用 HandlerThread
HandlerThread handlerThread = new HandlerThread("MyHandlerThread");
handlerThread.start();

Handler handler = new Handler(handlerThread.getLooper());
完整的消息处理流程:
sequenceDiagram
    participant Thread as 子线程
    participant Looper as Looper
    participant Handler as Handler
    participant MessageQueue as 消息队列
    
    Thread->>Looper: 1. Looper.prepare()
    Looper->>MessageQueue: 2. 创建消息队列
    Thread->>Handler: 3. 创建 Handler
    Handler->>Looper: 4. 获取 Looper
    Looper->>Handler: 5. 绑定消息队列
    Thread->>Looper: 6. Looper.loop()
    Looper->>MessageQueue: 7. 循环处理消息

这样设计的好处是保证了线程安全,因为每个 Handler 都必须绑定一个 Looper,而 Looper 又与特定线程绑定,这样就确保了消息的处理是在指定线程中进行的。

8. Handler 的 post 和 sendMessage 区别

post 方法:

public final boolean post(Runnable r) {
    return sendMessageDelayed(getPostMessage(r), 0);
}

sendMessage 方法:

public final boolean sendMessage(Message msg) {
    return sendMessageDelayed(msg, 0);
}

本质上 post 是对 sendMessage 的封装,post 更适合简单的任务,sendMessage 更适合复杂的消息传递。

底层原理

一、原理讲解

想象一下我们在餐厅点餐:

  1. post 方法 就像你直接告诉服务员:"我要一份炒饭"(Runnable)
  2. sendMessage 方法 就像你填写了一张详细的点餐单(Message)

二、源码分析

1. post 方法的实现:

public final boolean post(Runnable r) {
    // 将 Runnable 包装成 Message
    return sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;  // 将 Runnable 对象存储在 Message 的 callback 字段中
    return m;
}

2. sendMessage 方法的实现:

public final boolean sendMessage(Message msg) {
    return sendMessageDelayed(msg, 0);
}

public boolean sendMessageDelayed(Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

三、执行流程

当消息被处理时:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        // 如果是 post 方式,直接执行 Runnable
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            // 如果 Handler 有 Callback
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 调用 Handler 的 handleMessage 方法
        handleMessage(msg);
    }
}

四、流程图

graph TD
    A[开始] --> B{选择方式}
    
    B -->|post| C[创建 Runnable]
    B -->|sendMessage| D[创建 Message]
    
    C --> E[包装成 Message]
    E --> F[设置 callback]
    
    D --> G[直接使用 Message]
    
    F --> H[发送到 MessageQueue]
    G --> H
    
    H --> I[Looper 获取消息]
    I --> J{判断消息类型}
    
    J -->|有 callback| K[执行 Runnable]
    J -->|无 callback| L[执行 handleMessage]
    
    K --> M[结束]
    L --> M

五、区别总结

  1. 使用方式不同

    • post:适合简单任务,直接传入 Runnable
    • sendMessage:适合复杂消息,需要自己构建 Message
  2. 处理方式不同

    • post:直接执行 Runnable 的 run 方法
    • sendMessage:调用 Handler 的 handleMessage 方法
  3. 灵活性不同

    • post:封装程度高,使用简单
    • sendMessage:可以传递更多信息,更灵活
  4. 使用场景

    • post:适合单一任务执行
    • sendMessage:适合需要传递多个参数或者区分消息类型的场景

六、实际应用示例

// post 方式
handler.post(new Runnable() {
    @Override
    public void run() {
        // 直接执行的代码
        updateUI();
    }
});

// sendMessage 方式
Message msg = Message.obtain();
msg.what = MSG_UPDATE_UI;
msg.arg1 = 100;
msg.obj = data;
handler.sendMessage(msg);

// Handler 处理
@Override
public void handleMessage(Message msg) {
    switch (msg.what) {
        case MSG_UPDATE_UI:
            // 根据 Message 的参数处理不同逻辑
            updateUI(msg.arg1, msg.obj);
            break;
    }
}

这样的设计让 Handler 能够适应不同的使用场景,既可以简单直接,又能满足复杂的消息传递需求。

七、如何设置的callback

让我详细讲解 Handler 中 callback 的设置机制:

1 Message 中的 callback

1. Message 结构:

public final class Message implements Parcelable {
    // Runnable 类型的 callback
    /*package*/ Runnable callback;
    
    // 其他字段...
    /*package*/ Handler target;     // Handler
    /*package*/ int what;          // 消息标识
    public int arg1;               // 参数1
    public int arg2;               // 参数2
    public Object obj;             // 携带的对象
}

2. callback 的设置过程:

public final boolean post(Runnable r) {
    return sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();  // 获取消息对象
    m.callback = r;               // 将 Runnable 设置给 message 的 callback
    return m;
}
2 Handler 中的 Callback

Handler 还有另一种 callback 机制:

public interface Callback {
    boolean handleMessage(@NonNull Message msg);
}

// Handler 的 Callback 成员变量
final Callback mCallback;

// 构造函数中设置 Callback
public Handler(@Nullable Callback callback) {
    this(callback, false);
}
3、消息分发流程
public void dispatchMessage(@NonNull Message msg) {
    // 1. 优先处理 Message 的 callback
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        // 2. 其次处理 Handler 的 Callback
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 3. 最后调用 Handler 的 handleMessage
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}
4、流程图
graph TD
    A[消息分发开始] --> B{检查 Message.callback}
    B -->|存在| C[执行 Message.callback.run]
    B -->|不存在| D{检查 Handler.mCallback}
    D -->|存在| E[执行 Handler.mCallback.handleMessage]
    D -->|不存在| F[执行 Handler.handleMessage]
    E -->|返回 true| G[处理结束]
    E -->|返回 false| F
    C --> G
    F --> G
5、使用示例

1. Message callback 方式:

// 使用 post 方法
handler.post(new Runnable() {
    @Override
    public void run() {
        // 直接执行的代码
        System.out.println("通过 Message callback 执行");
    }
});

2. Handler callback 方式:

// 创建带有 Callback 的 Handler
Handler handler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(@NonNull Message msg) {
        switch (msg.what) {
            case 1:
                System.out.println("通过 Handler callback 处理");
                return true; // 返回 true 表示消息已处理完成
            default:
                return false; // 返回 false 继续执行 handleMessage
        }
    }
});

3. 常规 handleMessage 方式:

Handler handler = new Handler() {
    @Override
    public void handleMessage(@NonNull Message msg) {
        switch (msg.what) {
            case 1:
                System.out.println("通过 handleMessage 处理");
                break;
        }
    }
};
6、优先级总结

消息处理的优先级顺序:

  1. Message 的 callback(通过 post 方法设置的 Runnable)
  2. Handler 的 Callback(通过构造函数设置的 Callback)
  3. Handler 的 handleMessage 方法(重写 Handler 的方法)
7、使用建议
  1. 简单任务:使用 post 方法,代码简洁直观
handler.post(() -> updateUI());
  1. 通用消息处理:使用 Handler.Callback
Handler handler = new Handler(msg -> {
    // 处理所有消息
    return false; // 允许继续传递
});
  1. 特定消息处理:重写 handleMessage
Handler handler = new Handler() {
    @Override
    public void handleMessage(@NonNull Message msg) {
        // 处理特定消息
    }
};

这种多层次的 callback 机制使得 Handler 的消息处理更加灵活,能够适应不同的使用场景。

9. Handler Loop 为什么不阻塞主线程

这涉及到 Android 消息循环机制的底层实现:

  1. Loop 的死循环是在 native 层实现的
  2. 主线程的消息循环与 Linux 的 epoll 机制相关
  3. 当没有消息时,主线程会释放 CPU 资源,进入休眠状态
  4. 有新消息到来时,会被唤醒继续处理消息

这就是为什么即使有死循环,也不会导致 ANR 的原因。

底层原理

让我用生动的方式来解释 Handler Loop 不阻塞主线程的原理:

🌟 形象化解释

想象一个餐厅服务员(主线程)的工作方式:

  1. 🧑‍🍳 服务员不会一直站着干等客人点单(不会死等)
  2. 📱 当没有客人时,服务员会坐下休息(线程休眠)
  3. 🔔 有客人按铃时(消息到来),服务员立即醒来服务(线程唤醒)
  4. 👨‍💼 这个过程由餐厅经理(Linux 系统)智能调度

🔄 具体实现原理

// 简化的消息循环实现
public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    
    for (;;) {
        // 可能阻塞的地方
        Message msg = queue.next(); 
        
        // 处理消息
        msg.target.dispatchMessage(msg);
    }
}

在 native 层:

// frameworks/base/core/jni/android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(timeoutMillis);
}

🎯 核心原理

  1. 消息队列管理

    • MessageQueue 通过 native 层的 epoll 机制管理消息
    • 没有消息时,线程会通过 epoll_wait 进入休眠状态
  2. 系统调度

    • 主线程的 Looper 与 Binder 线程池配合工作
    • 通过 Linux pipe/epoll 机制实现高效的线程间通信
  3. 唤醒机制

    • 新消息到来时,通过 pipe 写端写入数据
    • epoll_wait 收到通知后唤醒线程

📊 流程图

graph TD
    A[主线程启动] --> B{检查消息队列}
    B -->|有消息| C[处理消息]
    B -->|无消息| D[调用 nativePollOnce]
    D --> E[线程休眠]
    E --> F{等待新消息}
    F -->|新消息到来| G[唤醒线程]
    G --> B
    C --> B

🎨 详细的消息处理流程

sequenceDiagram
    participant MT as 主线程
    participant MQ as MessageQueue
    participant EP as Epoll
    participant BP as Binder/其他线程

    MT->>MQ: 1. 查询消息
    MQ->>EP: 2. 无消息,调用 epoll_wait
    MT->>MT: 3. 主线程休眠
    BP->>MQ: 4. 发送新消息
    MQ->>EP: 5. 写入管道
    EP->>MT: 6. 唤醒主线程
    MT->>MQ: 7. 处理新消息

📝 总结要点

  1. 为什么不阻塞?

    • 使用 epoll 机制实现高效的 I/O 多路复用
    • 线程休眠时不占用 CPU 资源
    • 有消息时会立即被唤醒
  2. 效率保证

    • 使用 pipe/epoll 机制,而不是简单的忙等待
    • 系统级别的事件通知,性能很高
    • 完美配合 Android 的事件驱动模型
  3. 实际应用

    • UI 更新
    • 接收系统事件
    • 处理用户输入
    • Activity 生命周期回调

这种机制保证了 Android 系统的高效运行,既不会因为循环卡住主线程,也能及时响应各种事件。

pipe/epoll 机制

让我用生动的比喻来解释 pipe/epoll 机制:

🚰 Pipe(管道)机制

想象一个水管:

  • 一端负责往里面倒水(写端)
  • 一端负责接水(读端)
graph LR
    A[写端] -->|数据流| B((管道)) -->|数据流| C[读端]
    style B fill:#f9f,stroke:#333,stroke-width:4px

生活场景类比: 就像两个人用对讲机通话:

  • 说话的人 = 写端
  • 听的人 = 读端
  • 无线电波 = 管道
// 简化的 Pipe 示例代码
public class PipeExample {
    private static final int[] pipe = new int[2]; // pipe[0]=读端,pipe[1]=写端
    
    public static void main() {
        // 创建管道
        pipe(pipe);
        
        if (fork() == 0) {
            // 子进程:写数据
            write(pipe[1], "Hello!", 6);
        } else {
            // 父进程:读数据
            byte[] buffer = new byte[6];
            read(pipe[0], buffer, 6);
        }
    }
}
📡 Epoll 机制

想象一个超级前台服务员:

  • 不需要挨个问每位客人是否需要服务
  • 客人按铃时,立即知道是谁需要服务
  • 没人按铃时,可以休息
graph TD
    A[Epoll 服务员] --> B{监听多个铃}
    B --> C[1号桌铃]
    B --> D[2号桌铃]
    B --> E[3号桌铃]
    C -->|响铃| F[立即处理]
    D -->|响铃| F
    E -->|响铃| F

生活场景类比: 就像现代餐厅的智能服务系统:

  • 餐桌上的呼叫按钮 = 文件描述符
  • 服务员的接收器 = epoll
  • 客人按铃 = 事件发生
// 简化的 Epoll 示例代码
public class EpollExample {
    public static void main() {
        // 创建 epoll 实例
        int epfd = epoll_create();
        
        // 注册感兴趣的事件
        epoll_ctl(epfd, EPOLL_CTL_ADD, socketfd, EPOLLIN);
        
        while (true) {
            // 等待事件发生
            int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
            
            // 处理就绪的事件
            for (int i = 0; i < nfds; i++) {
                handleEvent(events[i]);
            }
        }
    }
}
🎯 在 Android 中的应用
sequenceDiagram
    participant Handler as Handler线程
    participant Pipe as 管道
    participant Epoll as Epoll监听器
    participant Looper as 主线程Looper

    Handler->>Pipe: 发送消息
    Pipe->>Epoll: 通知有新数据
    Epoll->>Looper: 唤醒主线程
    Looper->>Pipe: 读取消息
    Looper->>Looper: 处理消息
📝 关键特点对比

传统轮询方式:

while (true) {
    for (Socket socket : sockets) {
        if (socket.hasData()) {
            // 处理数据
        }
    }
}

Epoll 方式:

// 注册所有 socket
epoll.register(sockets);

while (true) {
    // 只处理有事件的 socket
    Socket[] activeSockets = epoll.wait();
    for (Socket socket : activeSockets) {
        // 处理数据
    }
}
💡 优势总结
  1. 高效性

    • 不需要轮询所有设备
    • 只在有事件时才会被唤醒
    • 支持同时监听大量文件描述符
  2. 性能表现

    • 传统方式:O(n)
    • Epoll方式:O(1)
  3. 资源占用

    • 休眠时几乎不占用 CPU
    • 内存使用效率高
    • 系统开销小
🌟 实际应用场景
  1. 网络服务器

    • 处理大量并发连接
    • 实现高效的 I/O 多路复用
  2. Android 消息机制

    • Handler 消息传递
    • 事件监听
    • 异步操作
  3. 系统监控

    • 文件变化监控
    • 设备状态监听
    • 进程间通信

这就是为什么 Android 的消息机制能够如此高效,既不会卡住主线程,又能及时处理各种事件的原因。

Binder 线程池

让我用生动的方式来解释 Binder 线程池:

🎯 形象化解释

想象一个快递公司的运作模式:

  • 快递公司 = Binder 驱动
  • 快递员团队 = Binder 线程池
  • 收发包裹 = 进程间通信
  • 派单系统 = Binder 调度器
📊 架构图
graph TD
    A[应用进程A] -->|请求| B[Binder驱动]
    C[应用进程B] -->|请求| B
    D[应用进程C] -->|请求| B
    B -->|分配| E[Binder线程池]
    E -->|线程1| F[处理请求]
    E -->|线程2| F
    E -->|线程3| F
    F -->|响应| A
    F -->|响应| C
    F -->|响应| D
🔄 工作原理
// Binder线程池的简化实现
public class BinderThreadPool {
    // 默认最大线程数
    private static final int MAX_THREADS = 15;
    
    // 最小线程数
    private static final int MIN_THREADS = 1;
    
    static void spawnPooledThread(boolean onlyOne) {
        if (onlyOne) {
            // 创建单个线程
            createThread();
        } else {
            // 根据需要创建多个线程
            while (needMoreThreads()) {
                createThread();
            }
        }
    }
}
🌟 具体流程
sequenceDiagram
    participant Client as 客户端进程
    participant Driver as Binder驱动
    participant Pool as Binder线程池
    participant Server as 服务端进程

    Client->>Driver: 1. 发起请求
    Driver->>Pool: 2. 唤醒空闲线程
    Pool->>Server: 3. 线程处理请求
    Server->>Pool: 4. 处理完成
    Pool->>Driver: 5. 返回结果
    Driver->>Client: 6. 接收响应
📝 重要特性
  1. 线程管理
public class BinderThread extends Thread {
    @Override
    public void run() {
        // 循环处理请求
        while (true) {
            // 等待新的事务
            waitForWork();
            
            // 处理事务
            doTransaction();
            
            // 完成后返回线程池
            returnToPool();
        }
    }
}
  1. 线程池特点
  • 动态调整线程数量
  • 默认最大 15 个线程
  • 按需创建和销毁
💡 使用场景
  1. 系统服务调用
// ActivityManager服务调用示例
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> processes = am.getRunningAppProcesses();
  1. AIDL通信
// AIDL接口示例
interface IMyService {
    void doSomething();
    String getData();
}
  1. ContentProvider访问
// ContentProvider查询示例
Cursor cursor = contentResolver.query(
    Uri.parse("content://com.example.provider/data"),
    null, null, null, null
);
🎨 优势分析
  1. 高效性
  • 线程复用
  • 避免频繁创建销毁
  • 统一调度管理
  1. 安全性
  • 身份验证
  • 权限控制
  • 调用监控
  1. 灵活性
  • 动态伸缩
  • 负载均衡
  • 优先级管理
📱 实际应用示例
// 服务端示例
public class MyService extends Service {
    private final IMyService.Stub mBinder = new IMyService.Stub() {
        @Override
        public void doSomething() {
            // 在Binder线程池中执行
            // 处理耗时操作
        }
    };
    
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}
// 客户端示例
public class MyClient {
    private IMyService service;
    
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            service = IMyService.Stub.asInterface(binder);
            // 现在可以调用服务方法了
            service.doSomething();
        }
    };
}
🔍 注意事项
  1. 线程安全
public class SafeBinderService extends Service {
    private final Object lock = new Object();
    
    private void safeOperation() {
        synchronized (lock) {
            // 线程安全的操作
        }
    }
}
  1. ANR预防
// 避免在Binder线程中执行耗时操作
private void handleRequest() {
    if (isTimeConsumingTask()) {
        // 转移到工作线程
        workHandler.post(() -> {
            // 执行耗时操作
        });
    }
}
  1. 资源管理
public void cleanup() {
    // 释放资源
    if (service != null) {
        unbindService(connection);
        service = null;
    }
}

Binder线程池是Android系统IPC(进程间通信)机制的核心组件,它通过高效的线程管理和调度,确保了系统服务和应用程序之间的通信既快速又可靠。理解Binder线程池的工作原理,对于开发高性能的Android应用程序非常重要。