Handler机制理解

148 阅读3分钟

、异步消息处理机制主要由以下部分组成:
Handler、Looper、MessageQueue、Message

二、Handler的用途
1.延时处理任务
2.线程间通信(包括在子线程中进行耗时操作,在主线程更新UI)

三、异步消息处理流程
1.Handler通过sendMessage()发送message到消息队列MessageQueue;
2.looper从MessageQueue中循环取出消息,并将message交给对应的target hanler;
3.hanler回调dispatchMessage方法收到消息后进行消息的处理。

image.png

PS:一个线程只有1个Looper(通过ThreadLocal来保证)、1个MessageQueue和多个message、但想 new 多少个 Handler 都行 ,一个进程有多个线程

在onCreate中创建个子线程更新UI不会发生异常,onResume时才会发生异常。 因为异常的检测处理在 ViewRootImpl 中,该实例的创建和检测在 onResume() 之后进行。 是否能在子线程更新UI取决于ViewRootImpl在哪个地方创建的

handler问题:

1.为什么Looper.loop()不会导致ANR?
ANR是消息处理超时,应用程序无响应。而Looper.loop()是循环从消息队列取消息,且在没有消息时会休眠,有消息时唤醒并处理消息。造成ANR的原因不在于循环,在于处理消息处理时间过长。 (MessageQueue的休眠和唤醒机制正是基于Linux的epoll机制实现的)

Handler如何实现线程切换?
- 发送消息的线程与处理消息的线程拥有不同的Looper
- 通过ThreadLocal保证线程隔离

MessageQueue的阻塞唤醒机制?
- 基于epoll机制实现
- nativePollOnce()和nativeWake()

2. 如何优化Handler使用?
- 复用Message对象
Message msg = handler.obtainMessage(MSG_UPDATE_UI, "新数据"); handler.sendMessage(msg);
- 及时移除不需要的消息
- 使用合适的作用域

3. Handler的post(Runnable)和sendMessage()有什么区别?

// 实际上post(Runnable)内部也是转换成Message
public final boolean post(Runnable r) {
    return sendMessageDelayed(getPostMessage(r), 0);
}
post(Runnable):简单的UI更新、延迟任务
sendMessage():复杂的消息传递,需要区分消息类型(可以携带what、arg等数据)

4. 如何保证在子线程中更新UI的安全性?

// 方案1:使用Activity的runOnUiThread
runOnUiThread(new Runnable() {
    @Override
    public void run() {
        // 这里在主线程执行,可以安全更新UI
        textView.setText("来自子线程的数据");
    }
});

// 方案2:使用View的post
textView.post(new Runnable() {
    @Override
    public void run() {
        textView.setText("安全更新UI");
    }
});

// 方案3:使用自定义Handler
private Handler mainHandler = new Handler(Looper.getMainLooper());

// 在子线程中
new Thread(new Runnable() {
    @Override
    public void run() {
        // 执行耗时操作
        final String result = doBackgroundWork();
        
        // 通过Handler切换到主线程
        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                textView.setText(result);
            }
        });
    }
}).start();
**************************************************
### 进阶方案:使用LiveData + ViewModel
public class MyViewModel extends ViewModel {
    private MutableLiveData<String> uiData = new MutableLiveData<>();
    
    public LiveData<String> getUiData() {
        return uiData;
    }
    
    public void loadData() {
        new Thread(() -> {
            String result = fetchDataFromNetwork();
            // LiveData会自动切换到主线程
            uiData.postValue(result);
        }).start();
    }
}

// Activity中观察
viewModel.getUiData().observe(this, new Observer<String>() {
    @Override
    public void onChanged(String data) {
        // 自动在主线程执行
        textView.setText(data);
    }
});

5.如何通过Handler优化页面启动速度?

6.主线程的looper和子线程looper有什么区别?

主线程 Looper 和子线程 Looper 在 源码层面只有两处差异,其余代码完全一样;差异 1 是“谁帮我 prepare”差异 2 是“谁帮我做死循环兜底”

主线程 Looper 由系统在 ActivityThread 里 prepare + loop,循环永不退出,异常即杀进程;子线程 Looper 必须手动 prepare/loop,可随时 quit,生命周期自己管。