Android HandlerThread 基础

HandlerThread

    • **一、HandlerThread的基本概念和用途**
      • [1. **目的**](#1. 目的)
      • [2. **与普通线程的区别**](#2. 与普通线程的区别)
    • **二、HandlerThread的使用步骤**
      • [1. **创建HandlerThread对象并启动线程**](#1. 创建HandlerThread对象并启动线程)
      • [2. **创建Handler并关联到HandlerThread的消息队列**](#2. 创建Handler并关联到HandlerThread的消息队列)
      • [3. **发送消息到HandlerThread的消息队列**](#3. 发送消息到HandlerThread的消息队列)
    • **三、HandlerThread的生命周期和注意事项**
      • [1. **生命周期**](#1. 生命周期)
      • [2. **注意事项**](#2. 注意事项)
    • [四、使用 HandlerThread 和 线程池 举同一个例子](#四、使用 HandlerThread 和 线程池 举同一个例子)
      • [1. **使用HandlerThread的示例:下载文件并更新UI**](#1. 使用HandlerThread的示例:下载文件并更新UI)
      • [2. **使用线程池的示例:下载文件并更新UI(同样的功能)**](#2. 使用线程池的示例:下载文件并更新UI(同样的功能))
      • [3.例子1中的 handler 不在主线程了么](#3.例子1中的 handler 不在主线程了么)
  • 参考地址

HandlerThread是Android中的一个类,它继承自Thread,主要用于在一个单独的线程中处理消息队列(MessageQueue)。以下是关于它的详细内容:

一、HandlerThread的基本概念和用途

1. 目的

  • 在Android开发中,为了避免在主线程(UI线程)执行耗时操作而导致应用程序出现"ANR(Application Not Responding)"的情况,需要将一些耗时任务(如网络请求、文件读写等)放到后台线程中执行。
  • HandlerThread提供了一种方便的方式来创建一个带有消息队列的后台线程。(和handler一起配合使用达到)
  • 它允许通过Handler发送消息到该线程的消息队列中,然后在该线程中按照消息发送的顺序依次处理这些消息。这样就可以在一个单独的线程中有序地执行一系列任务。

2. 与普通线程的区别

  • 普通线程没有自带的消息队列机制。如果要在普通线程中处理多个任务,需要自己实现任务调度和排队等复杂的逻辑。而HandlerThread内部已经实现了消息队列,并且可以通过Handler方便地与其他线程进行通信。

  • 例如,在一个普通线程中,如果要处理多个不同类型的任务,可能需要使用复杂的状态机或者阻塞队列等方式来管理任务。但是HandlerThread通过消息机制(MessageMessageQueue),可以很方便地通过sendMessage等方法发送任务请求,并且在HandlerhandleMessage方法中处理这些任务。

二、HandlerThread的使用步骤

1. 创建HandlerThread对象并启动线程

  • 首先,需要创建一个HandlerThread对象。例如:
java 复制代码
HandlerThread handlerThread = new HandlerThread("MyHandlerThread");
handlerThread.start();
  • 这里创建了一个名为MyHandlerThreadHandlerThread,然后调用start方法来启动这个线程。启动后,该线程就会开始运行,并且创建一个与之关联的消息队列。

2. 创建Handler并关联到HandlerThread的消息队列

  • 接着,需要创建一个Handler对象,并将其与HandlerThread的消息队列关联起来。可以通过以下方式实现:
java 复制代码
Handler handler = new Handler(handlerThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        // 在这里处理消息
        switch (msg.what) {
            case 1:
                // 执行任务1
                break;
            case 2:
                // 执行任务2
                break;
        }
    }
};
  • 这里通过handlerThread.getLooper()获取HandlerThreadLooper对象。Looper是一个用于循环获取消息队列中的消息并分发给Handler的类。通过这种方式,创建的Handler就可以将消息发送到HandlerThread的消息队列中,并且在handleMessage方法中处理这些消息。

3. 发送消息到HandlerThread的消息队列

  • 最后,可以通过Handler发送消息到HandlerThread的消息队列中。例如:
java 复制代码
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
  • 这里创建了一个Message对象,设置了消息的what属性(用于区分不同类型的消息),然后通过handler.sendMessage方法将消息发送到HandlerThread的消息队列中。HandlerThread中的Looper会不断地从消息队列中获取消息,并将消息分发给关联的HandlerhandleMessage方法进行处理。

三、HandlerThread的生命周期和注意事项

1. 生命周期

  • HandlerThread对象被创建并调用start方法后,线程开始运行,消息队列被创建,Looper开始循环获取消息。
  • 只要还有未处理的消息在消息队列中,或者Looper没有被显式地退出,HandlerThread就会一直运行。可以通过调用HandlerThreadquit或者quitSafely方法来退出Looper,从而结束HandlerThread的运行。例如:
java 复制代码
handlerThread.quitSafely();
  • quitSafely方法会在处理完当前消息队列中的已有消息后退出Looper,而quit方法会立即退出Looper,可能会导致消息丢失。

2. 注意事项

  • 内存泄漏:如果Handler对象是一个内部类,并且它间接引用了外部类(例如Activity)的实例,而HandlerThread的生命周期又比外部类长,那么可能会导致外部类无法被垃圾回收,从而引起内存泄漏。为了避免这种情况,可以将Handler定义为静态内部类,并使用弱引用(WeakReference)来引用外部类实例。
  • 消息处理顺序:HandlerThread中的消息是按照发送的顺序依次处理的。如果有高优先级的任务,需要在消息机制的基础上进行适当的调整,例如可以通过设置消息的优先级或者在handleMessage方法中根据任务的紧急程度优先处理某些消息。

四、使用 HandlerThread 和 线程池 举同一个例子

1. 使用HandlerThread的示例:下载文件并更新UI

  • 布局文件(activity_main.xml)

    • 简单的布局包含一个按钮用于触发下载和一个文本视图用于显示下载状态。
    xml 复制代码
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <Button
            android:id="@+id/download_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下载文件"/>
        <TextView
            android:id="@+id/status_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>
  • Java代码(MainActivity.java)

    • MainActivity中实现下载文件的功能。
    java 复制代码
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.HandlerThread;
    import android.os.Message;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    import androidx.appcompat.app.AppCompatActivity;
    public class MainActivity extends AppCompatActivity {
        private HandlerThread handlerThread;
        private Handler handler;
        private TextView statusText;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Button downloadButton = findViewById(R.id.download_button);
            statusText = findViewById(R.id.status_text);
            // 创建HandlerThread并启动
            handlerThread = new HandlerThread("DownloadThread");
            handlerThread.start();
            // 创建Handler并关联到HandlerThread的消息队列
            handler = new Handler(handlerThread.getLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    if (msg.what == 1) {
                        // 模拟下载文件的过程
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // 下载完成后发送消息到主线程更新UI
                        Message uiMessage = new Message();
                        uiMessage.what = 2;
                        uiHandler.sendMessage(uiMessage);
                    }
                }
            };
            downloadButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // 发送下载文件的消息到HandlerThread
                    Message downloadMessage = new Message();
                    downloadMessage.what = 1;
                    handler.sendMessage(downloadMessage);
                    statusText.setText("正在下载...");
                }
            });
            // 创建用于更新UI的主线程Handler
            Handler uiHandler = new Handler(getMainLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    if (msg.what == 2) {
                        statusText.setText("下载完成");
                    }
                }
            };
        }
        @Override
        protected void onDestroy() {
            super.onDestroy();
            // 退出HandlerThread
            handlerThread.quitSafely();
        }
    }
    • 首先,在onCreate方法中创建HandlerThread并启动它,然后创建与HandlerThread消息队列关联的Handler。当用户点击下载按钮时,发送一个消息到HandlerThread的消息队列,在handleMessage方法中模拟文件下载过程(这里通过Thread.sleep来模拟耗时操作)。下载完成后,发送一个消息到主线程的Handler来更新UI,显示下载完成的状态。最后,在onDestroy方法中退出HandlerThread

2. 使用线程池的示例:下载文件并更新UI(同样的功能)

  • 布局文件(与上面相同,activity_main.xml)

  • Java代码(MainActivity.java)

    java 复制代码
    import android.os.Bundle;
    import android.os.Handler;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    import androidx.appcompat.app.AppCompatActivity;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    public class MainActivity extends AppCompatActivity {
        private ExecutorService executorService;
        private TextView statusText;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Button downloadButton = findViewById(R.id.download_button);
            statusText = findViewById(R.id.status_text);
            // 创建一个单线程的线程池(这里可以根据需要调整线程池大小)
            executorService = Executors.newSingleThreadExecutor();
            downloadButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    statusText.setText("正在下载...");
                    // 提交下载任务到线程池
                    executorService.submit(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                // 模拟下载文件的过程
                                Thread.sleep(3000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            // 下载完成后发送消息到主线程更新UI
                            Handler uiHandler = new Handler(getMainLooper()) {
                                @Override
                                public void handleMessage(Message msg) {
                                    if (msg.what == 1) {
                                        statusText.setText("下载完成");
                                    }
                                }
                            };
                            Message uiMessage = new Message();
                            uiMessage.what = 1;
                            uiHandler.sendMessage(uiMessage);
                        }
                    });
                }
            });
        }
        @Override
        protected void onDestroy() {
            super.onDestroy();
            // 关闭线程池
            executorService.shutdown();
        }
    }
    • 这里在onCreate方法中创建一个单线程的线程池ExecutorService。当用户点击下载按钮时,将下载任务以Runnable的形式提交到线程池。在Runnablerun方法中模拟文件下载过程,下载完成后,创建一个主线程的Handler,并发送消息到主线程来更新UI,显示下载完成的状态。最后,在onDestroy方法中关闭线程池。这两种方式都实现了在后台执行下载任务,避免阻塞主线程,并且在任务完成后更新UI的功能。

3.例子1中的 handler 不在主线程了么

  1. handler不在主线程中

    • 在第一个例子中,handler关联的是HandlerThreadLooper,而HandlerThread是一个单独的后台线程。当创建handler时,通过handlerThread.getLooper()获取的是HandlerThread线程中的Looper对象。
    • 例如,代码中的这部分:
    java 复制代码
    handler = new Handler(handlerThread.getLooper()) {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                // 模拟下载文件的过程
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 下载完成后发送消息到主线程更新UI
                Message uiMessage = new Message();
                uiMessage.what = 2;
                uiHandler.sendMessage(uiMessage);
            }
        }
    };
    • 这个handlerhandleMessage方法是在HandlerThread线程中执行的。HandlerThread提供了一个后台线程环境,这样就可以在这个线程中执行一些耗时的操作,比如模拟文件下载(通过Thread.sleep来模拟耗时),而不会阻塞主线程。
  2. 与主线程通信的uiHandler

    • 为了更新UI,又创建了一个uiHandler,它是关联到主线程(UI线程)的Looper的。
    • 代码如下:
    java 复制代码
    Handler uiHandler = new Handler(getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 2) {
                statusText.setText("下载完成");
            }
        }
    };
    • 当后台HandlerThread中的任务完成后,通过uiHandler发送消息到主线程的消息队列,然后在主线程中执行uiHandlerhandleMessage方法来更新UI。这是因为在Android中,只有主线程才能更新UI,所以需要这种跨线程通信的方式来在后台任务完成后更新界面显示。

参考地址

豆包 AI

相关推荐
长亭外的少年4 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿6 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神7 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛7 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法8 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter9 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快10 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl11 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
麦田里的守望者江11 小时前
KMP 中的 expect 和 actual 声明
android·ios·kotlin
Dnelic-11 小时前
解决 Android 单元测试 No tests found for given includes:
android·junit·单元测试·问题记录·自学笔记