Android HandlerThread 基础
HandlerThread
- **一、HandlerThread的基本概念和用途**
- 1. **目的**
- 2. **与普通线程的区别**
- **二、HandlerThread的使用步骤**
- 1. **创建HandlerThread对象并启动线程**
- 2. **创建Handler并关联到HandlerThread的消息队列**
- 3. **发送消息到HandlerThread的消息队列**
- **三、HandlerThread的生命周期和注意事项**
- 1. **生命周期**
- 2. **注意事项**
- 四、使用 HandlerThread 和 线程池 举同一个例子
- 1. **使用HandlerThread的示例:下载文件并更新UI**
- 2. **使用线程池的示例:下载文件并更新UI(同样的功能)**
- 3.例子1中的 handler 不在主线程了么
- 参考地址
HandlerThread
是Android中的一个类,它继承自Thread
,主要用于在一个单独的线程中处理消息队列(MessageQueue
)。以下是关于它的详细内容:
一、HandlerThread的基本概念和用途
1. 目的
- 在Android开发中,为了避免在主线程(UI线程)执行耗时操作而导致应用程序出现“ANR(Application Not Responding)”的情况,需要将一些耗时任务(如网络请求、文件读写等)放到后台线程中执行。
HandlerThread
提供了一种方便的方式来创建一个带有消息队列的后台线程。(和handler一起配合使用达到)- 它允许通过
Handler
发送消息到该线程的消息队列中,然后在该线程中按照消息发送的顺序依次处理这些消息。这样就可以在一个单独的线程中有序地执行一系列任务。
2. 与普通线程的区别
-
普通线程没有自带的消息队列机制。如果要在普通线程中处理多个任务,需要自己实现任务调度和排队等复杂的逻辑。而
HandlerThread
内部已经实现了消息队列,并且可以通过Handler
方便地与其他线程进行通信。 -
例如,在一个普通线程中,如果要处理多个不同类型的任务,可能需要使用复杂的状态机或者阻塞队列等方式来管理任务。但是
HandlerThread
通过消息机制(Message
和MessageQueue
),可以很方便地通过sendMessage
等方法发送任务请求,并且在Handler
的handleMessage
方法中处理这些任务。
二、HandlerThread的使用步骤
1. 创建HandlerThread对象并启动线程
- 首先,需要创建一个
HandlerThread
对象。例如:
HandlerThread handlerThread = new HandlerThread("MyHandlerThread");
handlerThread.start();
- 这里创建了一个名为
MyHandlerThread
的HandlerThread
,然后调用start
方法来启动这个线程。启动后,该线程就会开始运行,并且创建一个与之关联的消息队列。
2. 创建Handler并关联到HandlerThread的消息队列
- 接着,需要创建一个
Handler
对象,并将其与HandlerThread
的消息队列关联起来。可以通过以下方式实现:
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()
获取HandlerThread
的Looper
对象。Looper
是一个用于循环获取消息队列中的消息并分发给Handler
的类。通过这种方式,创建的Handler
就可以将消息发送到HandlerThread
的消息队列中,并且在handleMessage
方法中处理这些消息。
3. 发送消息到HandlerThread的消息队列
- 最后,可以通过
Handler
发送消息到HandlerThread
的消息队列中。例如:
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
- 这里创建了一个
Message
对象,设置了消息的what
属性(用于区分不同类型的消息),然后通过handler.sendMessage
方法将消息发送到HandlerThread
的消息队列中。HandlerThread
中的Looper
会不断地从消息队列中获取消息,并将消息分发给关联的Handler
的handleMessage
方法进行处理。
三、HandlerThread的生命周期和注意事项
1. 生命周期
- 当
HandlerThread
对象被创建并调用start
方法后,线程开始运行,消息队列被创建,Looper
开始循环获取消息。 - 只要还有未处理的消息在消息队列中,或者
Looper
没有被显式地退出,HandlerThread
就会一直运行。可以通过调用HandlerThread
的quit
或者quitSafely
方法来退出Looper
,从而结束HandlerThread
的运行。例如:
handlerThread.quitSafely();
quitSafely
方法会在处理完当前消息队列中的已有消息后退出Looper
,而quit
方法会立即退出Looper
,可能会导致消息丢失。
2. 注意事项
- 内存泄漏:如果
Handler
对象是一个内部类,并且它间接引用了外部类(例如Activity)的实例,而HandlerThread
的生命周期又比外部类长,那么可能会导致外部类无法被垃圾回收,从而引起内存泄漏。为了避免这种情况,可以将Handler
定义为静态内部类,并使用弱引用(WeakReference
)来引用外部类实例。 - 消息处理顺序:
HandlerThread
中的消息是按照发送的顺序依次处理的。如果有高优先级的任务,需要在消息机制的基础上进行适当的调整,例如可以通过设置消息的优先级或者在handleMessage
方法中根据任务的紧急程度优先处理某些消息。
四、使用 HandlerThread 和 线程池 举同一个例子
1. 使用HandlerThread的示例:下载文件并更新UI
- 布局文件(activity_main.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
中实现下载文件的功能。
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)
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
的形式提交到线程池。在Runnable
的run
方法中模拟文件下载过程,下载完成后,创建一个主线程的Handler
,并发送消息到主线程来更新UI,显示下载完成的状态。最后,在onDestroy
方法中关闭线程池。这两种方式都实现了在后台执行下载任务,避免阻塞主线程,并且在任务完成后更新UI的功能。
- 这里在
3.例子1中的 handler 不在主线程了么
handler
不在主线程中- 在第一个例子中,
handler
关联的是HandlerThread
的Looper
,而HandlerThread
是一个单独的后台线程。当创建handler
时,通过handlerThread.getLooper()
获取的是HandlerThread
线程中的Looper
对象。 - 例如,代码中的这部分:
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); } } };
- 这个
handler
的handleMessage
方法是在HandlerThread
线程中执行的。HandlerThread
提供了一个后台线程环境,这样就可以在这个线程中执行一些耗时的操作,比如模拟文件下载(通过Thread.sleep
来模拟耗时),而不会阻塞主线程。
- 在第一个例子中,
- 与主线程通信的
uiHandler
- 为了更新UI,又创建了一个
uiHandler
,它是关联到主线程(UI线程)的Looper
的。 - 代码如下:
Handler uiHandler = new Handler(getMainLooper()) { @Override public void handleMessage(Message msg) { if (msg.what == 2) { statusText.setText("下载完成"); } } };
- 当后台
HandlerThread
中的任务完成后,通过uiHandler
发送消息到主线程的消息队列,然后在主线程中执行uiHandler
的handleMessage
方法来更新UI。这是因为在Android中,只有主线程才能更新UI,所以需要这种跨线程通信的方式来在后台任务完成后更新界面显示。
- 为了更新UI,又创建了一个
参考地址
豆包 AI