Android异步消息处理机制之Handler

基本介绍

  • 当我们需要执行复杂的计算逻辑,网络请求等耗时操作时,服务器可能不会立即响应请求,如果不将这类操作放在子线程中运行,就会导致主线程被阻塞住,从而影响用户的使用体验
  • 如果想要更新应用程序中的UI控件,则必须在主线程中进行,否则就会出现android.view.ViewRootImpl$CalledFromWrongThreadException异常

代码实践

  • 有些时候,我们需要在子线程中执行一些耗时任务,再根据任务的执行结果来更新相应的UI控件,对此,Android提供了一套异步消息的处理机制,解决了在子线程中进行UI操作的问题,我们先来看看异步消息处理的使用方法,再来分析其中的原理
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.example.myapplication1.R;

import java.util.concurrent.TimeUnit;

public class EventHandlerActivity extends AppCompatActivity {

    private static final int CALCULATE_KEY = 2024;
    private Button calculateBtn;
    private TextView resultTv;

    // 创建Handler实例,用于在主线程中更新UI
    private Handler myHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            if (msg.what == CALCULATE_KEY) {
                resultTv.setText("Result: " + msg.obj);
            }

        }
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_handler);

        calculateBtn = findViewById(R.id.calculateBtn);
        resultTv = findViewById(R.id.resultTv);

        calculateBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 点击按钮后,开始执行复杂计算
                calculateFunc();
            }
        });
    }

    private void calculateFunc() {
        // 创建一个新线程来执行耗时操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                long result = factorial(5);
                // 模拟复杂的耗时计算
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 发送消息给Handler,以便在主线程中更新UI
                // 另外,为了避免频繁地创建和销毁 Message 对象,可以使用 Message.obtain() 方法从消息池中获取一个消息实例,以减少内存分配和垃圾回收的频率
                Message message = Message.obtain();
                message.what = CALCULATE_KEY;
                message.obj = "calculate result = " + result;
                myHandler.sendMessage(message);
            }
        }).start();

    }


    private long factorial(int num){
        if (num < 2) return 1;
        return num * factorial(num - 1);
    }
}
  • activity_event_handler.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".handle.EventHandlerActivity">

    <Button
        android:id="@+id/calculateBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Calculate Factorial"
        android:textAllCaps="false"/>

    <TextView
        android:id="@+id/resultTv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/calculateBtn"
        android:layout_marginTop="16dp"/>

</RelativeLayout>

原理实现

  • Android中的异步消息处理主要由四部分组成:Message、Handler、MessageQueue和Looper,下面对这四部分来进行详细介绍

Message

  • Message是在线程之间传递的消息,可以在内部携带少量的数据,用于在不同线程间交换,其实例包含what
  • Message的what字段是一个整数值,可用来分区不同的消息类型,可为不同的任务或事件分配不同的what值,当调用Handler实例handleMessage方法时,可检查Message对象的what字段来确定如何处理该消息
  • arg1和arg2字段:均为整数类型字段,可携带what之外其他的整数类型数据
  • obj字段:Object类型字段,可携带字符串、数组、对象、Bundle等类型数据

Handler

  • Handler:处理者,主要用于发送和处理消息,发送消息一般是调用Handler实例的sendMessage()方法,而而发出的消息经过一系列辗转处理后,最终会交由handleMessage()方法来处理
  • Handler是在主线程中创建的,handleMessage()方法也会在主线程中执行,故不存在UI操作引起的线程安全问题

MessageQueue

  • 消息队列,主要用于存放所有通过Handler发送的消息,这部分消息会一直存在于MessageQueue中,等待被处理;每个线程只有一个MessageQueue对象

Looper

  • Looper:每个线程中MessageQueue的管理者,调用loop()之类的方法后,就会进入到消息的循环监听中,每当发现MQ中存在消息,就会将其取出,传递到handler.handleMessage()方法中;每个线程中也只用一个Looper对象

异步处理流程

  • 1)在主线程中创建Handler对象,并重写handleMessage()方法
  • 2)当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将Message发送出去
  • 3)发送出的Message会被添加到MessageQueue中等待被处理
  • 4)Looper会一直监听MessageQueue中的消息,一旦发现待处理的消息就取出,再分发到Handler的handleMessage()方法中处理消息
    在这里插入图片描述
    如上,Message经过一系列辗转调用后,由子线程完成耗时操作的处理,再由主线程完成UI操作,通过消息的异步处理机制解决UI操作可能会导致的线程安全问题

扩展

前面我们知道了如何异步地处理消息,实现原理,现在再来全面地看看消息异步处理解决的问题:

  • 1)线程安全:Android UI 是非线程安全的,即所有的 UI 操作必须在主线程中执行;任何在工作线程中直接对 UI 进行操作都会导致不可预知的行为,甚至可能导致应用崩溃;消息异步处理机制确保了所有的 UI 更新都在主线程中执行,从而保证了线程安全
  • 2)避免ANR(Application Not Responding):如果主线程因为长时间运行的任务(如数据库操作,执行复杂计算,网络请求)而被阻塞,系统会认为应用无响应,可能会触发ANR;消息异步处理机制允许这类耗时任务在工作线程中执行,从而避免了主线程的阻塞,减少ANR的发生
  • 3)控制线程的生命周期:使用Handler和Looper,开发者可精细地控制线程的生命周期(如在线程完成所有任务后退出,或在线程空闲时清理资源)
  • 4)支持延时消息和定时任务:Handler提供了发送延时消息的功能,允许在将来的某个时间点执行任务
  • 5)提高用户体验:通过在工作线程中执行耗时任务,用户界面可以保持响应,提供流畅的用户体验;用户可以继续与应用交互,而不会因为后台任务的执行而感到延迟

相关推荐

  1. Android-消息机制Handler

    2024-06-15 14:20:04       31 阅读
  2. Android消息机制--Handler

    2024-06-15 14:20:04       29 阅读
  3. 【安卓基础】-- 消息机制 Handler

    2024-06-15 14:20:04       8 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-15 14:20:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-15 14:20:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-15 14:20:04       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-15 14:20:04       20 阅读

热门阅读

  1. Gobject tutorial 二

    2024-06-15 14:20:04       4 阅读
  2. 刷题理解JVM内部的数据存储

    2024-06-15 14:20:04       8 阅读
  3. C 语言实例 - 输出数组

    2024-06-15 14:20:04       7 阅读
  4. c语言中的gets()函数记录

    2024-06-15 14:20:04       9 阅读
  5. 八、BGP

    八、BGP

    2024-06-15 14:20:04      5 阅读
  6. TypeScript中的Symbol,确实唯一。。。

    2024-06-15 14:20:04       6 阅读
  7. 认识一些分布-关于极值点分布的一些知识

    2024-06-15 14:20:04       7 阅读
  8. 把本机的bash构建到docker镜像里面

    2024-06-15 14:20:04       5 阅读
  9. AbpVnext中的DDD指南之聚合根

    2024-06-15 14:20:04       9 阅读
  10. grep命令知多少

    2024-06-15 14:20:04       6 阅读
  11. prometheus relabel_configs 标签重写

    2024-06-15 14:20:04       7 阅读
  12. openresty/openresty离线镜像安装包

    2024-06-15 14:20:04       6 阅读