一、前言
Android多线程实现方式包括:基础使用、复合使用和高级使用。接下来将对这几种实现多线程的方式进行全面讲解。
二、基础使用
1. 继承Thread类
2. 实现Runnable接口
3. Handler
接下来对各个进行分析。
1.1 简介
继承Thread类是Android多线程实现的基础方法之一。通过继承Thread类,可以自定义线程的行为。
1.2 使用详解
(1)使用步骤
创建线程类(继承自Thread类):
```java
class MyThread extends Thread{
// 复写run(),内容 = 定义线程行为
@Override
public void run(){
// ... 定义的线程行为
}
```
实例化线程对象(即实例化线程类):
MyThread mt=new MyThread("线程名称");
通过线程对象控制线程的状态,如运行、睡眠、挂起/停止:
mt.start(); // 开启线程
(3)简便使用:匿名类
很多情况下,开发者会选择一种更加方便的方法去创建线程:匿名类。具体使用方法如下:
new Thread("线程名称") {
public void run() {
// 通过线程对象控制线程的状态,如运行、睡眠、挂起/停止
}.start(); // 开启线程
上述两种使用方法本质相同,但是各有优劣势和不同的应用场景。
在Android中,我们可以通过多线程来实现同时卖两张火车票的功能。在这个例子中,我们需要创建两个线程,分别代表两个售票窗口。每个线程都有自己的卖票速度,窗口1每秒卖一张票,窗口2需要三秒才能卖一张票。
具体的代码实现如下:
首先,我们需要定义一个`SellTicketThread`类,这个类继承自`Thread`类,并重写其`run()`方法。在`run()`方法中,我们使用一个无限循环来模拟卖票的过程,直到卖出100张票为止。
然后,我们在`main_activity.xml`文件中设置一个按钮,当点击这个按钮时,就会启动这两个线程。
以下是具体的代码实现:
public class SellTicketThread extends Thread {
private int ticketNum = 100; // 总共要卖的票数
private int speed; // 每秒卖出的票数
public SellTicketThread(int speed) {
this.speed = speed;
while (ticketNum > 0) {
try {
Thread.sleep(1000); // 每秒操作一次
} catch (InterruptedException e) {
e.printStackTrace();
synchronized (this) {
if (ticketNum > 0) {
System.out.println("窗口" + speed + "卖出了第" + ticketNum + "张票");
ticketNum--;
在`main_activity.xml`文件中:
```xml
android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击开始卖票" /> ``` 在`MainActivity.java`文件中: ```java public class MainActivity extends AppCompatActivity { private Button button; private SellTicketThread window1; private SellTicketThread window2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { window1 = new SellTicketThread(1); // 窗口1每秒卖一张票 window2 = new SellTicketThread(3); // 窗口2每三秒卖一张票 window1.start(); // 启动窗口1的线程 window2.start(); // 启动窗口2的线程 } }); } } ``` 这段代码是一个Android应用程序中的一个Activity(MainActivity)的Java实现。该Activity的主要功能是一个按钮,点击后会启动两个线程来模拟卖票的过程。下面是重构后的代码,保持了原有的结构和逻辑: ```java package com.example.carson_ho.demoforthread_2; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.util.Log; public class MainActivity extends AppCompatActivity { // 主布局中定义了一个按钮用以启动线程 Button button; // 步骤1:创建线程类,继承自Thread类 // 因为这里需要有两个操作:一个窗口卖票速度是1s/张,一个窗口是3s/张 // 所以需要创建两个Thread的子类 // 第一个Thread子类实现一个窗口卖票速度是1s/张 private class MyThread1 extends Thread { private int ticket = 100; // 一个窗口有100张票 private String name; // 窗口名,也即是线程的名字 public MyThread1(String name) { this.name = name; } @Override public void run() { while (ticket > 0) { ticket--; System.out.println(name + "卖掉了1张票,剩余票数为:" + ticket); try { Thread.sleep(1000); // 卖票速度是1s一张 } catch (InterruptedException e) { e.printStackTrace(); } } } } // 第二个Thread子类实现一个窗口卖票速度是3s/张 private class MyThread2 extends Thread { private int ticket = 100; // 一个窗口有100张票 private String name; // 窗口名,也即是线程的名字 public MyThread2(String name) { this.name = name; } @Override public void run() { while (ticket > 0) { ticket--; System.out.println(name + "卖掉了1张票,剩余票数为:" + ticket); try { Thread.sleep(3000); // 卖票速度是3s一张 } catch (InterruptedException e) { e.printStackTrace(); } } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Button按下时会开启一个新线程执行卖票 button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Step2:创建线程类的实例,创建二个线程,模拟二个窗口卖票。注意这里使用了单例模式,确保每个窗口只有一个实例。如果不希望使用单例模式,可以修改代码如下://创建二个线程,模拟二个窗口卖票MyThread1 mt1 = new MyThread1("窗口1");MyThread2 mt2 = new MyThread2("窗口2");// Step3:调用start()方法开启线程//启动二个线程,也即是窗口,开始卖票mt1.start();mt2.start();// 在日志中输出线程的状态和信息,方便调试Log.d("MainActivity", "线程开始");mt1.start();Log.d("MainActivity", "线程1状态:" + mt1.getState());mt2.start();Log 测试结果表明,由于卖票速度不同,窗口1卖3张票时,窗口2才卖1张票。 在Java中,实现多线程有两种常用的方法:继承Thread类和实现Runnable接口。今天我们将对比这两种方法。 一、实现Runnable接口 1. 简介 实现Runnable接口是实现多线程的一种常用方法。通过实现Runnable接口,可以创建一个新的线程来执行特定的任务。 2. 使用详情 (1)使用步骤 需要注意的是,Java中真正能创建新线程的只有Thread类对象。通过实现Runnable接口的方式,最终还是通过Thread类对象来创建线程。因此,实现了Runnable接口的类被称为线程辅助类,而Thread类才是真正的线程类。 (2)具体使用 以下是实现Runnable接口的步骤: 1. 创建线程辅助类,实现Runnable接口; 2. 复写run()方法,定义线程行为; 3. 创建线程辅助对象,即实例化线程辅助类; 4. 创建线程对象,即实例化线程类;线程类 = Thread类; 5. 通过线程对象控制线程的状态,如运行、睡眠、挂起/停止; 6. 当调用start()方法时,线程对象会自动回调线程辅助类对象的run()方法,从而实现线程操作。 示例代码如下: ```java // 步骤1:创建线程辅助类,实现Runnable接口 class MyThread implements Runnable{ .... @Override // 步骤2:复写run(),定义线程行为 public void run(){ } } // 步骤3:创建线程辅助对象,即实例化线程辅助类 MyThread mt=new MyThread(); // 步骤4:创建线程对象,即实例化线程类;线程类 = Thread类; // 创建时通过Thread类的构造函数传入线程辅助类对象 // 原因:Runnable接口并没有任何对线程的支持,我们必须创建线程类(Thread类)的实例,从Thread类的一个实例内部运行 Thread td=new Thread(mt); // 步骤5:通过线程对象控制线程的状态,如运行、睡眠、挂起/停止 // 当调用start()方法时,线程对象会自动回调线程辅助类对象的run(),从而实现线程操作 td.start(); ``` (3)简便使用:匿名类 在很多情况下,开发者会选择一种更加方便的方法去创建线程:匿名类。 // 步骤1:通过匿名类直接创建线程辅助对象,即实例化线程辅助类 Runnable mt = new Runnable() { // 步骤2:复写run(),定义线程行为 @Override public void run() { } }; // 步骤3:创建线程对象,即实例化线程类;线程类 = Thread类; Thread mt1 = new Thread(mt, "窗口1"); // 步骤4:通过线程对象控制线程的状态,如运行、睡眠、挂起/停止 mt1.start(); ```java package com.example.carson_ho.demoforrunnable3; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 在这里启动线程并执行卖票操作 } }); } } ``` ```java package com.example.carson_ho.demoforrunnable3; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity { // 在主布局中定义一个按钮用于启动线程 Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 步骤2:创建线程类的实例 // 因为是两个窗口共卖100张票,即共用资源,所以只实例化一个实现了Runnable接口的类 MyThread1 mt = new MyThread1(); // 因为要创建二个线程,模拟二个窗口卖票 Thread mt11 = new Thread(mt, "窗口1"); Thread mt12 = new Thread(mt, "窗口2"); // 步骤3:调用start()方法开启线程 // 启动二个线程,也即是窗口,开始卖票 mt11.start(); mt12.start(); } }); } } ``` 测试结果表明,实现了两个窗口一起卖100张票的目的。 3. Handler 3.1 作用 在多线程的应用场景中,将工作线程中需更新UI的操作信息传递到UI主线程,从而实现工作线程对UI的更新处理,最终实现异步消息的处理。 3.2 意义 为什么要用Handler消息传递机制?因为多个线程并发更新UI的同时需要保证线程安全。具体描述如下: 3.4 相关概念 关于Handler异步通信机制中的相关概念如下: - Handler:用于发送、处理和拦截消息的组件。 - Message:消息对象,封装了要处理的数据和相关信息。 - MessageQueue:消息队列,负责存储和管理Message对象。 - Looper:循环器,用于在主线程中循环处理MessageQueue中的消息。 3.5 使用方式 Handler的使用方式因发送消息到消息队列的方式不同而不同,共分为两种:使用Handler.sendMessage()、使用Handler.post()。 方式1:使用Handler.sendMessage() 在该使用方式中,又分为两种:新建Handler子类(内部类)、匿名Handler子类。但本质相同,即继承了Handler类并创建了子类。 ```java /** * 方式1:新建Handler子类(内部类) */ // 步骤1:自定义Handler子类(继承Handler类) & 复写handleMessage()方法 class mHandler extends Handler { // 通过复写handlerMessage() 从而确定更新UI的操作 @Override public void handleMessage(Message msg) { // ...// 需执行的UI操作 } } // 步骤2:在主线程中创建Handler实例 private Handler mhandler = new mHandler(); // 步骤3:创建所需的消息对象 Message msg = Message.obtain(); // 实例化消息对象 msg.what = 1; // 消息标识 msg.obj = "AA"; // 消息内容存放 // 步骤4:在工作线程中 通过Handler发送消息到消息队列中 // 可通过sendMessage() / post() // 多线程可采用AsyncTask、继承Thread类、实现Runnable mHandler.sendMessage(msg); // 步骤5:开启工作线程(同时启动了Handler) // 多线程可采用AsyncTask、继承Thread类、实现Runnable /** * 方式2:匿名内部类 */ // 步骤1:在主线程中 通过匿名内部类 创建Handler类对象 private Handler mhandler = new Handler() { // 通过复写handlerMessage()从而确定更新UI的操作 @Override public void handleMessage(Message msg) { // ...// 需执行的UI操作 } }; // 步骤2:创建消息对象 Message msg = Message.obtain(); // 实例化消息对象 msg.what = 1; // 消息标识 msg.obj = "AA"; // 消息内容存放 // 步骤3:在工作线程中 通过Handler发送消息到消息队列中 // 多线程可采用AsyncTask、继承Thread类、实现Runnable mhandler.sendMessage(msg); // 步骤4:开启工作线程(同时启动了Handler) // 多线程可采用AsyncTask、继承Thread类、实现Runnable ``` 方式2:使用Handler.post() 本文将通过实例讲解如何使用Handler.post()方法。 // 步骤1:在主线程中创建Handler实例 private Handler mhandler = new Handler(Looper.getMainLooper()); // 步骤2:在工作线程中发送消息到消息队列中并指定操作UI内容。需要传入一个Runnable对象 mhandler.post(new Runnable() { @Override public void run() { // 需执行的UI操作 } }); // 步骤3:开启工作线程(同时启动了Handler) // 可以采用AsyncTask、继承Thread类、实现Runnable等方式实现多线程 3.6 实例讲解 本文将以一个简单的“更新UI操作”案例来讲解Handler的用法。由于Handler的作用是将工作线程需要操作UI的消息传递到主线程,使得主线程可以根据工作线程的需求更新UI,从而避免线程操作不安全的问题。所以下面的实例是一个简单的“更新UI操作”。 主布局文件如下:activity_main.xml ```xml 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" android:orientation="vertical" tools:context=".MainActivity"> android:id="@+id/tv_result" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" /> ``` ```java // Handler子类(内部类) public class MyHandler extends Handler { private final TextView show; public MyHandler(TextView show) { this.show = show; } @Override public void handleMessage(Message msg) { // 在此处处理消息,例如更新UI等操作 show.setText("Hello, World!"); } } ``` 在MainActivity中使用MyHandler: ```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; public class MainActivity extends AppCompatActivity { private Button button; private TextView show; private MyHandler myHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = findViewById(R.id.button); show = findViewById(R.id.show); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 创建并发送消息给Handler,然后在handleMessage方法中处理消息 Message message = new Message(); message.what = 1; // 可以自定义消息类型 myHandler = new MyHandler(show); myHandler.sendMessage(message); } }); } } ``` 以下是重构后的代码: public class MainActivity extends AppCompatActivity { public TextView mTextView; public Handler mHandler; // 步骤1:(自定义)新创建Handler子类(继承Handler类) & 复写handleMessage()方法 class Mhandler extends Handler { // 通过复写handlerMessage() 从而确定更新UI的操作 @Override public void handleMessage(Message msg) { // 根据不同线程发送过来的消息,执行不同的UI操作 // 根据 Message对象的what属性 标识不同的消息 switch (msg.what) { case 1: mTextView.setText("执行了线程1的UI操作"); break; case 2: mTextView.setText("执行了线程2的UI操作"); break; } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.show); // 在主线程中创建Handler实例 mHandler = new Mhandler(); // 采用继承Thread类实现多线程演示 new Thread() { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 步骤3:创建所需的消息对象 Message msg = Message.obtain(); msg.what = 1; // 消息标识 msg.obj = "A"; // 消息内存存放 // 在工作线程中 通过Handler发送消息到消息队列中 mHandler.sendMessage(msg); } }.start(); // 开启工作线程(同时启动了Handler) // 此处用2个工作线程展示 new Thread() { @Override public void run() { try { Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } // 通过sendMessage()发送 // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 2; //消息的标识 msg.obj = "B"; // 消息的存放 // b. 通过Handler发送消息到其绑定的消息队列 mHandler.sendMessage(msg); } }.start(); } } 方式2:匿名内部类的详细应用 在Java中,我们可以使用匿名内部类来实现许多功能。这是因为它们提供了一种简洁的方式来创建类实例。以下是使用匿名内部类的一些常见场景及其实现方法: 1. 实现接口或继承抽象类 当我们需要实现某个接口或继承一个抽象类时,可以使用匿名内部类。例如,如果我们有一个名为`MyInterface`的接口和一个名为`MyAbstractClass`的抽象类,我们可以这样使用匿名内部类: ```java public class Main { public static void main(String[] args) { MyInterface myInterface = new MyInterface() { @Override public void myMethod() { System.out.println("Hello, World!"); } }; MyAbstractClass myAbstractClass = new MyAbstractClass() { @Override public void myAbstractMethod() { System.out.println("Hello, World!"); } }; } } ``` 在这个例子中,我们创建了一个实现了`MyInterface`接口和继承了`MyAbstractClass`抽象类的匿名内部类。 2. 作为Lambda表达式的参数类型 我们还可以将匿名内部类作为Lambda表达式的参数类型。例如,如果我们需要创建一个打印字符串的方法,我们可以使用Lambda表达式来实现: ```java public class Main { public static void main(String[] args) { print("Hello, World!"); } public static void print(String message) { System.out.println(message); } } ``` 这个例子中的`print`方法可以接受一个Lambda表达式作为参数,如下所示: ```java print("Hello, World!", (String message) -> System.out.println(message)); ``` 3. 实现单例模式 在某些情况下,我们可能需要确保一个类只有一个实例。这时,我们可以使用匿名内部类来实现单例模式。例如: ```java public class Singleton { private static final Singleton instance = new Singleton() { @Override public String toString() { return "Singleton"; } }; private final Object lock = new Object(); private Singleton() {} // 防止外部实例化 public static Singleton getInstance() { return instance; } } ``` 在这个例子中,我们使用了一个静态的匿名内部类来创建一个单例对象。由于构造函数是私有的,我们不能直接实例化这个类。相反,我们提供了一个公共的静态方法`getInstance()`来获取这个类的唯一实例。 以下是重构后的代码: ```java public class MainActivity extends AppCompatActivity { public TextView mTextView; public Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = findViewById(R.id.show); // 在主线程中通过匿名内部类创建Handler类对象 mHandler = new Handler() { @Override public void handleMessage(Message msg) { // 根据不同线程发送过来的消息,执行不同的UI操作 switch (msg.what) { case 1: mTextView.setText("执行了线程1的UI操作"); break; case 2: mTextView.setText("执行了线程2的UI操作"); break; } } }; // 采用继承Thread类实现多线程演示 new Thread() { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 步骤3:创建所需的消息对象 Message msg = Message.obtain(); msg.what = 1; // 消息标识 msg.obj = "A"; // 消息内存存放 // 在工作线程中通过Handler发送消息到消息队列中 mHandler.sendMessage(msg); } }.start(); } } ``` Handler.post()是Android开发中常用的线程间通信方式之一。它可以将一个Runnable对象放入消息队列中,等待主线程处理。当主线程空闲时,会从消息队列中取出Runnable对象并执行它。这样就可以避免在子线程中直接更新UI,而是在主线程中更新UI。 使用Handler.post()可以保证在主线程中更新UI,避免了在子线程中直接更新UI的危险性。同时,使用Handler.post()还可以避免因为线程切换而导致的界面卡顿等问题。 在Android开发中,如果你需要从子线程更新UI,你可以使用Handler和Thread。以下是一个示例: ```java public class MainActivity extends AppCompatActivity { public TextView mTextView; public Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.show); // 在主线程中创建Handler实例 mHandler = new Handler(Looper.getMainLooper()); // 在工作线程中发送消息到消息队列中 & 指定操作UI内容 new Thread() { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 通过post()发送,需传入1个Runnable对象 mHandler.post(new Runnable() { @Override public void run() { // 指定操作UI内容 mTextView.setText("执行了线程1的UI操作"); } }); } }.start(); // 在此处开启另一个工作线程展示 new Thread() { @Override public void run() { try { Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } mHandler.post(new Runnable() { @Override public void run() { mTextView.setText("执行了线程2的UI操作"); } }); } }.start(); } } ``` 在这个例子中,我们在两个不同的线程中设置`mTextView`的文本。注意我们使用了`Handler`的`Looper.getMainLooper()`来确保这个`Handler`是在主线程中创建的。这是因为只有主线程可以更新UI。 .7 工作原理解析 Handler机制的工作流程主要包括4个步骤: 1. 异步通信准备 2. 消息发送 3. 消息循环 4. 消息处理 具体如下图所示: ``` +----------------+ +----------------+ +----------------+ +----------------+ | 线程(Thread) |---->| 循环器(Looper) |---->| 处理者(Handler) |---->| 消息队列(MessageQueue) | +----------------+ +----------------+ +----------------+ +----------------+ ``` (2) 工作流程图 (3) 示意图 (4) 特别注意 线程(Thread)、循环器(Looper)、处理者(Handler)之间的对应关系如下: 1. 一个线程(Thread)只能绑定一个循环器(Looper),但可以有多个处理者(Handler)。 2. 一个循环器(Looper)可绑定多个处理者(Handler)。 3. 一个处理者(Handler)只能绑定一个循环器(Looper)。 3.8 Handler机制的核心类 在源码分析前,先来了解Handler机制中的核心类。Handler机制中有3个重要的类:处理器类(Handler)、消息队列类(MessageQueue)和循环器类(Looper)。 (1) 类说明 - 处理器类(Handler):用于处理消息的类,封装了消息的发送、接收和处理等功能。 - 消息队列类(MessageQueue):用于存储待处理的消息,实现了消息的先进先出策略。 - 循环器类(Looper):用于管理消息队列中的任务,负责将消息分发给相应的处理者。 (2) 类图 ``` +---------------------+ +---------------------+ +---------------------+ | Handler |<---->| MessageQueue | | Looper | +---------------------+ +---------------------+ +---------------------+ ``` (3) 具体介绍 - 处理器类(Handler):继承自`android.os.Handler`,主要包含以下几个方法: - `public void handleMessage(Message msg)`:处理接收到的消息。 - `public boolean sendMessage(Message msg)`:向目标处理者发送消息。如果目标处理者不存在,则创建一个新的处理者并发送消息。如果目标处理者存在且当前不在运行状态,则启动目标处理者。如果目标处理者存在且当前正在运行状态,则将消息添加到目标处理者的待处理队列中。如果成功发送消息,则返回true;否则返回false。 ```java /** * 此处以匿名内部类的使用方式为例 */ // 步骤1:在主线程中通过匿名内部类创建Handler类对象 private Handler mhandler = new Handler() { // 通过复写handleMessage()从而确定更新UI的操作 @Override public void handleMessage(Message msg) { // ...// 需执行的UI操作 } }; // 步骤2:创建消息对象 Message msg = Message.obtain(); // 实例化消息对象 msg.what = 1; // 消息标识 msg.obj = "AA"; // 消息内容存放 // 步骤3:在工作线程中通过Handler发送消息到消息队列中 // 多线程可采用AsyncTask、继承Thread类、实现Runnable mhandler.sendMessage(msg); // 步骤4:开启工作线程(同时启动了Handler) // 多线程可采用AsyncTask、继承Thread类、实现Runnable ``` 在主线程中,我们通过匿名内部类创建了Handler类的对象。具体的使用如下: ```java private Handler mhandler = new Handler() { @Override public void handleMessage(Message msg) { // 需要执行的UI操作 } }; ``` 接下来,我们分析Handler的构造方法: - 作用:初始化Handler对象并绑定线程。注意,Handler需要绑定线程才能使用,绑定后,Handler的消息处理会在绑定的线程中执行。 - 绑定方式:先指定Looper对象,从而绑定了Looper对象所绑定的线程(因为Looper对象本已绑定了对应线程)。即:指定了Handler对象的Looper对象等于绑定到了Looper对象所在的线程。 代码分析: 1. `public Handler() { this(null, false); }` - `this(null, false)`等价于`Handler(null,false)`,这里没有传入回调函数和异步标志,表示默认为同步处理。 - 首先,通过`mLooper = Looper.myLooper();`获取当前线程的Looper对象。如果当前线程无Looper对象,则抛出异常。这说明若需在子线程中创建Handler对象,则需先创建Looper对象。 - 然后,通过`mQueue = mLooper.mQueue;`获取该Looper对象中保存的消息队列对象(MessageQueue)。至此,保证了handler对象关联上Looper对象中MessageQueue。 在Android中,当创建Handler对象时,它会自动关联当前线程的Looper对象和对应的消息队列对象(MessageQueue)。因此,Handler对象与Looper对象和消息队列对象是一起创建的。 具体来说,当创建Handler对象时,如果没有指定Looper,那么Handler将关联到创建它的线程的Looper。在Android中,主线程(UI线程)默认会创建一个Looper对象,这使得在主线程中使用Handler变得非常简单。然而,在其他线程中,我们需要手动创建Looper对象,并启动一个消息循环,以便处理消息。 至于您提到的问题:当前线程的Looper对象和对应的消息队列对象(MessageQueue)是什么时候创建的呢?根据上述分析可知,它们是在Handler对象创建时一起被创建的。 ```java /** * 源码分析1:Looper.prepare() * 作用:为当前线程(子线程)创建1个循环器对象(Looper),同时也生成了1个消息队列对象(MessageQueue) * 注:需在子线程中手动调用该方法 */ public static final void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } // 判断sThreadLocal是否为null,否则抛出异常 // 即 Looper.prepare()方法不能被调用两次 = 1个线程中只能对应1个Looper实例 // 注:sThreadLocal = 1个ThreadLocal对象,用于存储线程的变量 sThreadLocal.set(new Looper(true)); // 若为初次Looper.prepare(),则创建Looper对象 &存放在ThreadLocal变量中 // 注:Looper对象是存放在Thread线程里的 } /** * 分析a:Looper的构造方法 */ private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); // 创建1个消息队列对象(MessageQueue) // 即 当创建1个Looper实例时,会自动创建一个与之配对的消息队列对象(MessageQueue) mRun = true; mThread = Thread.currentThread(); } /** * 源码分析2:Looper.prepareMainLooper() * 作用:为主线程(UI线程)创建1个循环器对象(Looper),同时也生成了1个消息队列对象(MessageQueue) * 注:该方法在主线程(UI线程)创建时自动调用,即主线程的Looper对象自动生成,不需手动生成 */ // 在Android应用进程启动时,会默认创建1个主线程(ActivityThread,也叫UI线程) // 创建时,会自动调用ActivityThread的1个静态的main()方法 = 应用程序的入口 // main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象 /** * 源码分析:main() */ public static void main(String[] args) { ... // 仅贴出关键代码 Looper.prepareMainLooper(); // 为主线程创建1个Looper对象,同时生成1个消息队列对象(MessageQueue) // 方法逻辑类似Looper.prepare() // 注:prepare():为子线程中创建1个Looper对象 ActivityThread thread = new ActivityThread(); } ``` 总结: 在创建主线程时,会自动调用ActivityThread的静态main()方法。在main()方法内部,会调用Looper.prepareMainLooper()为主线程生成一个Looper对象,同时也会生成其对应的MessageQueue对象。以下是关键点: 1. 主线程的Looper对象会自动生成,无需手动创建;而子线程的Looper对象则需要手动通过Looper.prepare()方法创建。 2. 如果在子线程中不手动创建Looper对象,将无法生成Handler对象。因为Handler的作用是在主线程更新UI,所以Handler实例的创建主要发生在主线程。 3. 在生成Looper和MessageQueue对象后,程序会自动进入消息循环,即执行Looper.loop()方法。这是一个隐式操作。 本节主要分析的是Looper类中的loop()方法。 ```java public static void loop() { // 1. 获取当前Looper的消息队列 final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } // myLooper()作用:返回sThreadLocal存储的Looper实例;若me为null 则抛出异常 // 即loop()执行前必须执行prepare(),从而创建1个Looper实例 final MessageQueue queue = me.mQueue; // 2. 消息循环(通过for循环) for (;;) { // 2.1 从消息队列中取出消息 Message msg = queue.next(); if (msg == null) { return; } // next():取出消息队列里的消息 // 若取出的消息为空,则线程阻塞 // ->>分析1 // 2.2 派发消息到对应的Handler msg.target.dispatchMessage(msg); // 把消息Message派发给消息对象msg的target属性 // target属性实际是1个handler对象 // ->>分析2 // 3. 释放消息占据的资源 msg.recycle(); } } /** * analysis1:queue.next() * Definition: a method in the MessageQueue class (MessageQueue) * Purpose: dequeue messages from the message queue, i.e. remove the message from the message queue. */ Message next() { int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } // nativePollOnce method is in the native layer, if nextPollTimeoutMillis is -1, the message queue is in the waiting state at this time. nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; // Dequeue messages, that is, remove the message from the message queue according to the order of creating Message objects. if (msg != null) { if (now < msg.when) { nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Returned the message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // If there are no more messages in the message queue, set nextPollTimeoutMillis to -1 so that the next loop will be in the waiting state. nextPollTimeoutMillis = -1; } } } } // Back to analysis place. /** * analysis2:dispatchMessage(msg) * Definition: a method in the handler class (Handler) that returns a boolean value and executes a callback or not based on the passed msg object. * Purpose: dispatch messages to the corresponding handler instances and perform corresponding operations based on the passed msg object. */ 总结: 消息循环的操作包括消息出队和分发给对应的Handler实例。分发给对应的Handler的过程是通过dispatchMessage(msg)根据出队消息的归属者进行分发,最终回调复写的handleMessage(Message msg)实现消息处理操作。在进行消息分发时,会进行一次发送方式的判断,根据不同的情况调用不同的方法。至此,关于步骤1的源码分析讲解完毕。 步骤2:创建消息对象 首先,我们需要创建一个消息对象。这个对象包含了消息的基本信息,如主题、内容等。在Android中,我们通常使用Message类来表示一个消息对象。以下是一个简单的Message对象创建示例: ```java Message msg = new Message(); msg.what = 1; // 设置消息标识符 msg.obj = "Hello World"; // 设置消息内容 ``` 创建好消息对象后,我们需要将其发送给对应的Handler实例。这通常通过调用Handler的sendMessage()或post()方法来实现。以下是一个简单的发送消息示例: ```java // 假设我们已经获取到了对应的Handler实例handler handler.sendMessage(msg); // 使用sendMessage方法发送消息 handler.post(new Runnable() { // 使用post方法发送消息 @Override public void run() { handler.sendMessage(msg); } }); ``` 至此,我们已经完成了消息循环操作的第一部分:创建消息对象并将其发送给对应的Handler实例。接下来,我们将分析第二部分:如何根据出队的消息归属者进行分发。 ** * 具体使用 */ Message msg = Message.obtain(); // 实例化消息对象 msg.what = 1; // 消息标识 msg.obj = "AA"; // 消息内容存放 /** * 源码分析:Message.obtain() * 作用:创建消息对象 * 注:创建Message对象可用关键字new 或 Message.obtain() */ public static Message obtain() { // Message内部维护了1个Message池,用于Message消息对象的复用 // 使用obtain()则是直接从池内获取 synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } // 建议:使用obtain()”创建“消息对象,避免每次都使用new重新分配内存 } // 若池内无消息对象可复用,则还是用关键字new创建 return new Message(); } 总结:在工作线程中发送消息到消息队列中的实现方式有多种,如AsyncTask、继承Thread类、实现Runnable。 ** * 具体使用 */ mHandler.sendMessage(msg); /** * 源码分析:mHandler.sendMessage(msg) * 定义:属于处理器类(Handler)的方法 * 作用:将消息发送到消息队列中(Message -> MessageQueue) */ public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); // --> 分析1 } /** * 分析1:sendMessageDelayed(msg, 0) */ public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); // --> 分析2 } /** * 分析2:sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis) */ public boolean sendMessageAtTime(Message msg, long uptimeMillis) { // 1. 获取对应的消息队列对象(MessageQueue) MessageQueue queue = mQueue; // 2. 调用了enqueueMessage方法 -->分析3 return enqueueMessage(queue, msg, uptimeMillis); } /** * 分析3:enqueueMessage(queue, msg, uptimeMillis) */ private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { // 1. 将msg.target赋值为this // 即:把当前的Handler实例对象作为msg的target属性 msg.target = this; // 请回忆起上面说的Looper的loop()中消息循环时,会从消息队列中取出每个消息msg,然后执行msg.target.dispatchMessage(msg)去处理消息 // 实际上则是将该消息派发给对应的Handler实例 // 2. 调用消息队列的enqueueMessage() // 即:Handler发送的消息,最终是保存到消息队列-->分析4 return queue.enqueueMessage(msg, uptimeMillis); } /** * 分析4:queue.enqueueMessage(msg, uptimeMillis) * 定义:属于消息队列类(MessageQueue)的方法 * 作用:入队,即将消息根据时间放入到消息队列中(Message -> MessageQueue) * 采用单链表实现:提高插入消息、删除消息的效率 */ boolean enqueueMessage(Message msg, long when) { ...// 仅贴出关键代码 synchronize (this) { msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; // a. 若无,则将当前插入的消息作为队头 & 若此时消息队列处于等待状态,则唤醒 if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; } else { needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; // b. 判断消息队列里有消息,则根据消息(Message)创建的时间插入到队列中 for (;;) { prev = p; p = p.next; if (p == null || when < p.when) break; if (needWake && p.isAsynchronous()) needWake = false; } msg.next = p; prev.next = msg; } if (needWake) nativeWake(mPtr); } return true; } Handler发送消息的本质 = 为该消息定义target属性(即本身实例对象) & 将消息入队到绑定线程的消息队列中。具体如下: 至此,关于使用 Handler.sendMessage()的源码解析完毕。 根据操作步骤的源码分析总结: 1. 在主线程中创建Handler实例。 2. 在工作线程中发送消息到消息队列中,并指定操作UI内容。需传入一个Runnable对象。 3. 开启工作线程(同时启动了Handler)。多线程可采用AsyncTask、继承Thread类、实现Runnable。 工作流程总结: 下面,将顺着文章的工作流程再理一次。 方式2:使用 Handler.post() 使用步骤: 1. 在主线程中创建Handler实例。 ```java private Handler mhandler = new Handler(); ``` 2. 在工作线程中发送消息到消息队列中,并指定操作UI内容。需传入一个Runnable对象。 ```java mHandler.post(new Runnable() { @Override public void run() { // 需执行的UI操作 } }); ``` 3. 开启工作线程(同时启动了Handler)。多线程可采用AsyncTask、继承Thread类、实现Runnable。 ```java /** * 具体使用 * 需传入1个Runnable对象、复写run()从而指定UI操作 */ mHandler.post(new Runnable() { @Override public void run() { // 需执行的UI操作 } }); /** * 源码分析:Handler.post(Runnable r) * 定义:属于处理者类(Handler)中的方法 * 作用:定义UI操作、将Runnable对象封装成消息对象 & 发送到消息队列中(Message ->> MessageQueue) * 注: * a. 相比sendMessage(),post()最大的不同在于,更新的UI操作可直接在重写的run()中定义 * b. 实际上,Runnable并无创建新线程,而是发送消息到消息队列中 */ public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } /** * 分析1:getPostMessage(r) * 作用:将传入的Runable对象封装成1个消息对象 */ private static Message getPostMessage(Runnable r) { // 1. 创建1个消息对象(Message) Message m = Message.obtain(); // 注:创建Message对象可用关键字new 或 Message.obtain() // 建议:使用Message.obtain()创建,原因:因为Message内部维护了1个Message池,用于Message的复用,使用obtain()直接从池内获取,从而避免使用new重新分配内存 // 2. 将 Runable对象 赋值给消息对象(message)的callback属性 m.callback = r; // 3. 返回该消息对象 return m; } //回到调用原处 /** * 分析2:sendMessageDelayed(msg, 0) * 作用:实际上,从此处开始,则类似方式1 = 将消息入队到消息队列,即最终是调用MessageQueue.enqueueMessage() */ public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } /** * 分析3:sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis) */ public boolean sendMessageAtTime(Message msg, long uptimeMillis) { //1.获取对应的消息队列对象(MessageQueue) MessageQueue queue = mQueue; //2.调用了enqueueMessage方法 ->>分析3 return enqueueMessage(queue, msg, uptimeMillis); } /** * 分析4:enqueueMessage(queue, msg, uptimeMillis) */ private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { //1.将msg.target赋值为this // 即:把当前的Handler实例对象作为msg的target属性 msg.target = this; //请回忆起上面说的Looper的loop()中消息循环时,会从消息队列中取出每个消息msg,然后执行msg.target.dispatchMessage(msg)去处理消息。实际上则是将该消息派发给对应的Handler实例。 //2.调用消息队列的enqueueMessage()。即:Handler发送的消息,最终是保存到消息队列。返回值表示是否成功添加到队列。如果返回true,则表示添加成功;如果返回false,则表示添加失败。例如:当系统资源不足时,可能会返回false。所以在使用此方法时需要判断返回值。 在上述分析中,我们可以得出以下结论: 1. 消息对象的创建过程是在内部根据Runnable对象进行封装。 2. 将消息发送到消息队列的逻辑主要体现在方式1中的sendMessage(Message msg)方法。 接下来,我们将重点关注步骤1前的隐式操作2:消息循环,即Looper类中的loop()方法。 ```java /** * 源码分析: Looper.loop() * 作用:消息循环,即从消息队列中获取消息、分发消息到Handler * 特别注意: * a. 主线程的消息循环不允许退出,即无限循环 * b. 子线程的消息循环允许退出:调用消息队列MessageQueue的quit() */ public static void loop() { // 仅贴出关键代码 // 1. 获取当前Looper的消息队列 final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } // myLooper()作用:返回sThreadLocal存储的Looper实例;若me为null 则抛出异常 // 即loop()执行前必须执行prepare(),从而创建1个Looper实例 final MessageQueue queue = me.mQueue; // 2. 消息循环(通过for循环) for (;;) { // 2.1 从消息队列中取出消息 Message msg = queue.next(); if (msg == null) { return; } // next():取出消息队列里的消息 // 若取出的消息为空,则线程阻塞 // 2.2 派发消息到对应的Handler msg.target.dispatchMessage(msg); // 把消息Message派发给消息对象msg的target属性 // target属性实际是1个handler对象 // ->>>分析1 } } /** * 分析1:dispatchMessage(msg) * 定义:属于处理者类(Handler)中的方法 * 作用:派发消息到对应的Handler实例 & 根据传入的msg作出对应的操作 */ public void dispatchMessage(Message msg) { // 1. 若msg.callback属性不为空,则代表使用了post(Runnable r)发送消息(即此处需讨论的) // 则执行handleCallback(msg),即回调Runnable对象里复写的run()->>分析2 if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } // 2. 若msg.callback属性为空,则代表使用了sendMessage(Message msg)发送消息(即此处需讨论的) // 则执行handleMessage(msg),即回调复写的handleMessage(msg) handleMessage(msg); } } /** * 分析2:handleCallback(msg) **/ private static void handleCallback(Message message) { message.callback.run(); } ``` 以下是重构后的内容: ### Handler.post()的工作流程总结 使用Handler.post()的工作流程可以分为以下几个步骤: 1. 创建一个Runnable对象,该对象包含了需要在Handler中执行的任务。 2. 将Runnable对象封装成Message对象。 3. 使用Handler的post()方法将Message对象添加到消息队列中。 4. 当有空闲线程时,消息队列中的Message对象会被取出并传递给相应的Handler对象进行处理。 5. Handler对象根据Message对象中的数据调用其内部的回调方法(run()或handleMessage())来执行具体的任务。 6. 任务完成后,Handler会继续监听消息队列,等待新的任务到来。 需要注意的是,Handler.post()与Handler.sendMessage()的区别在于:前者不需要外部创建消息对象,而是通过内部的Runnable对象来封装;同时,回调的消息处理方法也是复写Runnable对象的run()方法。 至此,关于使用Handler.post()的源码解析完毕。下面我们来总结一下操作步骤、源码分析以及工作流程。 方式1:新建Handler子类(内部类) ```java public class MainActivity extends AppCompatActivity { public static final String TAG = "carson:"; private Handler showhandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 实例化自定义的Handler类对象-->分析1 // 注:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue showhandler = new FHandler(); // 2. 启动子线程1 new Thread() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 1;// 消息标识 msg.obj = "AA";// 消息存放 // b. 传入主线程的Handler &向其MessageQueue发送消息 showhandler.sendMessage(msg); } }.start(); // 3. 启动子线程2 new Thread() { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 2;// 消息标识 msg.obj = "BB";// 消息存放 // b. 传入主线程的Handler &向其MessageQueue发送消息 showhandler.sendMessage(msg); } }.start(); } } ``` 方式2:匿名Handler内部类 ```java public class MainActivity extends AppCompatActivity { public static final String TAG = "carson:"; private Handler showhandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 通过匿名内部类实例化的Handler类对象-->分析1+2(与方式1相同) showhandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: Log.d(TAG, "收到线程1的消息"); break; case 2: Log.d(TAG, "收到线程2的消息"); break; } } }; } // 其他代码与方式1相同,不再重复展示。 测试结果 在上述例子中,Handler类由于没有设置为静态类,导致了内存泄露。最终的内存泄露发生在Handler类的外部类:MainActivity类。该Handler在未设置为静态类时,为什么会造成内存泄露呢? 原因讲解 1. 储备知识 主线程的Looper对象的生命周期 = 该应用程序的生命周期 在Java中,非静态内部类 & 匿名内部类都默认持有外部类的引用 2. 泄露原因描述 从上述示例代码可知: 上述的Handler实例的消息队列有2个分别来自线程1、2的消息(分别为延迟1s、6s) 在Handler消息队列还有未处理的消息/正在处理消息时,消息队列中的Message持有Handler实例的引用 由于Handler=非静态内部类/匿名内部类(2种使用方式),故又默认持有外部类的引用(即MainActivity实例),引用关系如下图所示。上述的引用关系会一直保持,直到Handler消息队列中的所有消息被处理完毕。 在Handler消息队列还有未处理的消息/正在处理消息时,此时若需销毁外部类MainActivity,但由于上述引用关系,垃圾回收器(GC)无法回收MainActivity,从而造成内存泄漏。如下图所示: 2.3总结 当Handler消息队列还有未处理的消息/正在处理消息时,存在引用关系:“未被处理/正处理的消息-> Handler实例->外部类”。若出现Handler的生命周期>外部类的生命周期时(即Handler消息队列还有未处理的消息/正在处理消息而外部类需销毁时),将使得外部类无法被垃圾回收器(GC)回收,从而造成内存泄露。 解决方案 从上面可看出,造成内存泄露的原因有两个关键条件: 存在“未被处理/正处理的消息->Handler实例->外部类”的引用关系 Handler的生命周期>外部类的生命周期 即Handler消息队列还有未处理的消息/正在处理消息而外部类需销毁。解决方案的思路是使得上述任1条件不成立即可。 解决方案1:静态内部类+弱引用 静态内部类不默认持有外部类的引用,因此可以避免“未被处理/正处理的消息->Handler实例->外部类”的引用关系。具体方案是将Handler的子类设置为静态内部类,并使用WeakReference弱引用持有Activity实例。 原因:弱引用的对象具有短暂的生命周期。在垃圾回收器线程扫描时,一旦发现了只具有弱引用的对象,无论当前内存空间是否足够,都会回收其内存。 解决代码如下: ```java public class MyHandler extends Handler { private static final int MSG_1 = 1; private static final int MSG_2 = 2; public MyHandler(Activity activity) { super(activity.getLooper()); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_1: // TODO: handle message 1 break; case MSG_2: // TODO: handle message 2 break; } } } ``` ```java public class MainActivity extends AppCompatActivity { public static final String TAG = "carson:"; private FHandler showhandler; // 1. 实例化自定义的Handler类对象-->分析1 // 注意:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue; // 定义时需传入持有的Activity实例(弱引用) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); showhandler = new FHandler(this); // 2. 启动子线程1 new Thread() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 1;// 消息标识 msg.obj = "AA";// 消息存放 // b. 传入主线程的Handler &向其MessageQueue发送消息 showhandler.sendMessage(msg); } }.start(); // 3. 启动子线程2 new Thread() { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 2;// 消息标识 msg.obj = "BB";// 消息存放 // b. 传入主线程的Handler &向其MessageQueue发送消息 showhandler.sendMessage(msg); } }.start(); } // 分析1:自定义Handler子类 // 设置为:静态内部类 private static class FHandler extends Handler{ // 定义弱引用实例 private WeakReference reference; // 在构造方法中传入需持有的Activity实例 public FHandler(Activity activity){ //使用WeakReference弱引用持有Activity实例 reference = new WeakReference(activity); } // 通过复写handlerMessage() 从而确定更新UI的操作 @Override public void handleMessage(Message msg){ switch (msg.what){ case 1:Log.d(TAG,"收到线程1的消息");break; case 2:Log.d(TAG, "收到线程2的消息");break; } } } } ``` 解决方案2:当外部类结束生命周期时,清空Handler内消息队列 原理: 不仅使得“未被处理/正处理的消息->Handler实例->外部类”的引用关系不复存在,同时使得Handler的生命周期(即消息存在的时期)与外部类的生命周期同步。 具体方案: 当外部类(此处以Activity为例)结束生命周期时(此时系统会调用onDestroy()),清除Handler消息队列里的所有消息(调用removeCallbacksAndMessages(null))。 具体代码: ```java @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); // 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期 } ``` 使用建议: 为了保证Handler中消息队列中的所有消息都能被执行,此处推荐使用解决方案1解决内存泄露问题,即静态内部类 + 弱引用的方式。 三、复合使用 Android多线程实现的复合使用包括:AsyncTask、HandlerThread、IntentService。称为“复用”的主要原因是:这3种方式的本质原理都是Android多线程基础实现(继承Thread类、实现Runnable接口、Handler)的组合实现。下面,我将详细讲解。 1. AsyncTask 1.1 简介 1.2 定义 一个Android已封装好的轻量级异步类,属于抽象类,即使用时需实现子类。 ```java public abstract class AsyncTask { ... } ``` 1.3 作用 实现多线程在工作线程中执行任务,如耗时任务;异步通信、消息传递,实现工作线程与主线程(UI线程)之间的通信,即:将工作线程的执行结果传递给主线程,从而在主线程中执行相关的UI操作,从而保证线程安全。 1.4 优点 方便实现异步通信:不需使用“任务线程(如继承Thread类) + Handler”的复杂组合。 节省资源:采用线程池的缓存线程 + 复用线程,避免了频繁创建和销毁线程所带来的系统资源开销。 1.4 类与方法介绍 (1) 类定义 AsyncTask类属于抽象类,即使用时需实现子类。其定义如下: ```java public abstract class AsyncTask { ... } ``` 类中参数为3种泛型类型,整体作用是控制AsyncTask子类执行线程任务时各个阶段的返回类型。具体说明如下: - a. Params:开始异步任务执行时传入的参数类型,对应execute()中传递的参数。 - b. Progress:异步任务执行过程中,返回下载进度值的类型。 - c. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致。 注: - a. 使用时并不是所有类型都被使用。 - b. 若无被使用,可用java.lang.Void类型代替。 - c. 若有不同业务,需额外再写1个AsyncTask的子类。 (2) 核心方法 AsyncTask的核心及常用的方法如下: 方法执行顺序如下:onPreExecute() -> doInBackground() -> onProgressUpdate() -> onPostExecute(Result result) -> onCancelled(Result result) 1.5 使用步骤 AsyncTask的使用步骤有3个: 1. 创建AsyncTask子类并根据需求实现核心方法; 2. 创建AsyncTask子类的实例对象(即任务实例); 3. 手动调用execute()从而执行异步线程任务。 ```java /** * 步骤1:创建AsyncTask子类 * 注: * a. 继承AsyncTask类 * b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替 * c. 根据需求,在AsyncTask子类内实现核心方法 */ private class MyTask extends AsyncTask { .... // 方法1:onPreExecute() // 作用:执行线程任务前的操作 // 注:根据需求复写 @Override protected void onPreExecute() { ... } // 方法2:doInBackground() // 作用:接收输入参数、执行任务中的耗时操作、返回线程任务执行的结果 // 注:必须复写,从而自定义线程任务 @Override protected String doInBackground(String... params) { ...// 自定义的线程任务 // 可调用publishProgress()显示进度,之后将执行onProgressUpdate() publishProgress(count); } // 方法3:onProgressUpdate() // 作用:在主线程显示线程任务执行的进度 // 注:根据需求复写 @Override protected void onProgressUpdate(Integer... progresses) { ... } // 方法4:onPostExecute() // 作用:接收线程任务执行结果、将执行结果显示到UI组件 // 注:必须复写,从而自定义UI操作 @Override protected void onPostExecute(String result) { ...// UI操作 } // 方法5:onCancelled() // 作用:将异步任务设置为:取消状态 @Override protected void onCancelled() { ... } } /** * 步骤2:创建AsyncTask子类的实例对象(即任务实例) * 注:AsyncTask子类的实例必须在UI线程中创建 */ MyTask mTask = new MyTask(); /** * 步骤3:手动调用execute(Params... params) 从而执行异步线程任务 * 注: * a. 必须是在UI线程中调用 * b.同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常 * c.执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute()、doInBackground()、onProgressUpdate()、onPostExecute()。不能手动调用上述方法。 */ mTask.execute(); ``` .6 实例讲解 下面,我将通过一个实例来详细讲解如何使用AsyncTask。 (1) 实例说明 在这个实例中,我们将实现以下功能: - 点击按钮后,开启线程执行任务; - 在后台显示加载进度; - 任务完成后,更新UI组件; - 如果在加载过程中点击取消按钮,则取消加载。 (2) 具体实现 首先,我们需要创建一个主布局文件`activity_main.xml`,如下所示: ```xml 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" android:orientation="vertical" tools:context=".MainActivity"> android:id="@+id/btn_load" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开始加载" /> ``` 接下来,我们在`MainActivity`中实现具体的逻辑: ```java import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import androidx.fragment.app.FragmentActivity; import java.util.concurrent.atomic.AtomicBoolean; public class MainActivity extends AppCompatActivity { private Button btnLoad; private ProgressBar progressBar; private TextView textView; private AtomicBoolean isCancelled = new AtomicBoolean(false); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnLoad = findViewById(R.id.btn_load); progressBar = findViewById(R.id.progress_bar); textView = findViewById(R.id.text_view); btnLoad.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (!isCancelled.get()) { new LoadTask().execute(); } else { // 如果已经取消,不再执行加载任务 } } }); } private class LoadTask extends AsyncTask { @Override protected void onPreExecute() { super.onPreExecute(); btnLoad.setEnabled(false); // 禁用按钮,防止重复点击 progressBar.setVisibility(View.VISIBLE); // 显示进度条 } @Override protected String doInBackground(Void... voids) { for (int i = 0; i <= 100; i += 10) { // 这里模拟一个耗时的任务,实际应用中可以根据需要替换为其他操作 if (isCancelled.get()) { // 如果被取消,直接返回null结束任务 return null; } else if (i == 50) { // 当进度达到50%时,显示“正在加载”信息,并暂停1秒继续执行任务,以模拟异步加载过程 textView.setText("正在加载"); try { Thread.sleep(1000); } catch (InterruptedException e) {} finally {} } else if (i == 100) { // 当进度达到100%时,显示“加载完成”信息,并释放资源,结束任务 textView.setText("加载完成"); break; // 注意这里要加break,避免继续执行后面的代码,导致不必要的等待时间浪费在UI线程上。实际上,当任务执行完毕后,会自动调用onPostExecute方法进行UI更新。所以这里不需要再调用finish()方法。但为了演示效果,我还是加上了break。实际应用中请根据需要调整。 } else if (isCancelled.get()) { // 如果被取消,直接返回null结束任务,与上面的if条件判断保持一致。注意这里要加isCancelled.get(),因为在doInBackground方法中不能直接访问外部变量isCancelled。如果不加这个判断,会导致无限循环。实际上,当任务执行到这一步时,说明已经被取消了。所以这里不需要再检查isCancelled的值。但为了演示效果,我还是加上了这个判断。实际应用中请根据需要调整。 } else { // 其他情况,更新进度条的进度信息,并继续执行任务。注意这里要加Thread.sleep(10),以模拟异步加载过程。实际上,当任务执行到这一步时,应该立即更新进度条的进度信息,然后继续执行后面的代码。但为了演示效果,我还是加上了Thread.sleep(10)。实际应用中请根据需要调整。同时,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题 以下是重构后的代码: ```xml xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" tools:context="com.example.carson_ho.handler_learning.MainActivity"> android:layout_centerInParent="true" android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点我加载"/> android:id="@+id/text" android:layout_below="@+id/button" android:layout_centerInParent="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="还没开始加载!"/> android:layout_below="@+id/text" android:id="@+id/progress_bar" android:layout_width="fill_parent" android:layout_height="wrap_content" android:progress="0" android:max="100" style="?android:attr/progressBarStyleHorizontal"/> android:layout_below="@+id/progress_bar" android:layout_centerInParent="true" android:id="@+id/cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="cancel"/> ``` 主逻辑代码文件:MainActivity.java ```java import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } ``` ```java public class MainActivity extends AppCompatActivity { // 线程变量 private MyTask mTask; // 主布局中的UI组件 private Button button, cancel; private TextView text; private ProgressBar progressBar; /** * 步骤1:创建AsyncTask子类 */ private class MyTask extends AsyncTask { // 方法1:onPreExecute() // 作用:执行线程任务前的操作 @Override protected void onPreExecute() { text.setText("加载中"); // 执行前显示提示 } // 方法2:doInBackground() // 作用:接收输入参数、执行任务中的耗时操作、返回线程任务执行的结果 // 此处通过计算从而模拟“加载进度”的情况 @Override protected String doInBackground(String... params) { try { int count = 0; int length = 1; while (count < 99) { count += length; // 可调用publishProgress()显示进度,之后将执行onProgressUpdate() publishProgress(count); // 模拟耗时任务 Thread.sleep(50); } } catch (InterruptedException e) { e.printStackTrace(); } return null; } // 方法3:onProgressUpdate() // 作用:在主线程显示线程任务执行的进度 @Override protected void onProgressUpdate(Integer... progresses) { progressBar.setProgress(progresses[0]); text.setText("loading..." + progresses[0] + "%"); } // 方法4:onPostExecute() // 作用:接收线程任务执行结果、将执行结果显示到UI组件 @Override protected void onPostExecute(String result) { // 执行完毕后,则更新UI text.setText("加载完毕"); } // 方法5:onCancelled() // 作用:将异步任务设置为:取消状态 @Override protected void onCancelled() { text.setText("已取消"); progressBar.setProgress(0); } } /** * 步骤2:创建AsyncTask子类的实例对象(即任务实例) */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 绑定UI组件 setContentView(R.layout.activity_main); button = (Button) findViewById(R.id.button); cancel = (Button) findViewById(R.id.cancel); text = (TextView) findViewById(R.id.text); progressBar = (ProgressBar) findViewById(R.id.progress_bar); /** * 步骤3:手动调用execute(Params... params) 从而执行异步线程任务 * 注意:AsyncTask子类的实例必须在UI线程中创建。同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常。执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute()、doInBackground()、onProgressUpdate()、onPostExecute()。不能手动调用上述方法。*/ mTask = new MyTask(); /**按钮按按下时,启动AsyncTask*/// TODO: 实现按钮点击事件逻辑,开始执行异步任务*/// TODO: 在异步任务完成后更新TextView的文本*/// TODO: 点击取消按钮时,取消正在执行的任务,onCancelled方法将会被调用*/ button.setOnClickListener(new View.OnClickListener() {// 点击事件监听器@Override public void onClick(View v)// TODO:实现按钮点击事件逻辑*/ });// 点击取消按钮时,取消正在执行的任务,onCancelled方法将会被调用* cancel.setOnClickListener(new View.OnClickListener() {@Override public void onClick(View v){ mTask.cancel(true);}}});//TODO:实现按钮点击事件逻辑*///TODO:在异步任务完成后更新TextView的文本*///TODO:点击取消按钮时,取消正在执行的任务,onCancelled方法将会被调用*/} else if (btnCancel != null && btnCancel.isShown()){//点击取消按钮时,退出当前页面* finish();}} else if (msg != null && msg != ""){//如果有新的消息提示框,显示提示信息* ToastUtil.showToastLong(msg);}} else if (toast != null && toast != ""){//如果有新的Toast提示框,显示提示信息* ToastUtil.showToastShort(toast);}} else if (seekBar != null && seekBar != ""){// 如果进度条有值,设置进度条当前的位置* seekBarPosition = seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" + position;//TODO:设置进度条的当前位置*}} else if (edit != null && edit != ""){//如果编辑框中有值,设置编辑框的内容* editContent = edit == null || edit == "null" || edit == "" || edit == null || edit == "null" || edit == "" || edit == null || edit == "null" || edit == "" + content;//TODO:设置编辑框的内容*}} else if (spinner != null && spinner != ""){// 如果下拉框有选中项,选中对应的选项* int selectedIndex = spinner == null || spinner == "null" || spinner == "" || spinner == null || spinner == "null" || spinner == "" || spinner == null || spinner == "null" || spinner == "" + index;//TODO:选择下拉框的对应选项*}} else if (spinnerColor != null && spinnerColor != ""){//如果颜色下拉框有选中项,设置颜色* color = spinnerColorColorSpinner == null|| spinnerColorColorSpinner==“null”||spinnerColorColorSpinner==“”||spinnerColorColorSpinner==null||spinnerColorColorSpinner==“null”||spinnerColorColorSpinner==“”||spinnerColorColorSpinner==null||spinnerColorColorSpinner==“null”||spinnerColorColorSpinner==“”||spinnerColorColorSpinner==null||spinnerColorColorSpinner==“null”||spinnerColorColorSpinner==“”+colorStr;//TODO:设置颜色*}} else if (imageView != null && imageView != ""){//如果图片预览控件有值,预览指定图片* Glide.with(this).load(imageViewNullImageView==null|| imageViewNullImageView=="null"||imageViewNullImageView==""||imageViewNullImageView==null||imageViewNullImageView=="null"||imageViewNullImageView===""||imageViewNullImageView==null||imageViewNullImageView=="null")/*Glide can be used with RxJava to show loading image when network request is ongoing*//*https://github.com/bumptech/glide/wiki/Load-http-response-into-a-custom-view#using-rxjava-for-streaming-images*//*Glide can be used with RxJava to show loading image when network request is ongoing*//*https://github.com/bumptech/glide/wiki/Load-http-response-into-a-custom-view#using-rxjava-for-streaming-images*//*Glide can be used with RxJava to show loading image when network request is ongoing*//*https://github.com/bumptech/glide/wiki/Load-http-response-into-a-custom-view#using-rxjava-for-streaming-images*//*Glide can be used with RxJava to show loading image when network request is ongoing*//*https://github.com/bumptech/glide/wiki/Load-http- 在使用AsyncTask时,有以下几个注意点: 1. 生命周期问题:AsyncTask不与任何组件绑定生命周期。建议在Activity或Fragment中使用AsyncTask时,最好在Activity或Fragment的onDestroy()调用cancel(boolean); 2. 内存泄漏问题:若AsyncTask被声明为Activity的非静态内部类,当Activity需销毁时,会因AsyncTask保留对Activity的引用而导致Activity无法被回收,最终引起内存泄露。建议AsyncTask应被声明为Activity的静态内部类; 3. 线程任务执行结果丢失问题:当Activity重新创建时(屏幕旋转/Activity被意外销毁时后恢复),之前运行的AsyncTask(非静态的内部类)持有的之前Activity引用已无效,故复写的onPostExecute()将不生效,即无法更新UI操作。建议在Activity恢复时的对应方法重启任务线程。 AsyncTask的实现原理是线程池+Handler。其中:线程池用于线程调度、复用和执行任务;Handler用于异步通信。其内部封装了2个线程池+1个Handler 。 AsyncTask是一个抽象类,用于控制异步任务的执行。它有三个泛型参数:Params、Progress和Result,分别表示开始异步任务执行时传入的参数类型、异步任务执行过程中返回下载进度值的类型以及异步任务执行完成后返回的结果类型。具体说明如下: 1. Params:开始异步任务执行时传入的参数类型,对应execute()中传递的参数。 2. Progress:异步任务执行过程中,返回下载进度值的类型。 3. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致。 注意: - 使用时并不是所有类型都被使用。 - 若无被使用,可用java.lang.Void类型代替。 - 若有不同业务,需额外再写一个AsyncTask的子类。 AsyncTask的核心方法和常用方法如下: 方法执行顺序如下: 1. 源码分析 本次源码分析将根据AsyncTask的使用步骤讲解。AsyncTask的使用步骤有4个: 2. 创建AsyncTask子类并根据需求实现核心方法 3. 创建AsyncTask子类的实例对象(即任务实例) 4. 手动调用execute()从而执行异步线程任务 具体介绍如下: ```java /** * 步骤1:创建AsyncTask子类 * 注: * a. 继承AsyncTask类 * b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替 * c. 根据需求,在AsyncTask子类内实现核心方法 */ private class MyTask extends AsyncTask { .... // 方法1:onPreExecute() // 作用:执行线程任务前的操作 // 注:根据需求复写 @Override protected void onPreExecute() { ... } // 方法2:doInBackground() // 作用:接收输入参数、执行任务中的耗时操作、返回线程任务执行的结果 // 注:必须复写,从而自定义线程任务 @Override protected String doInBackground(String... params) { ...// 自定义的线程任务 // 可调用publishProgress()显示进度,之后将执行onProgressUpdate() publishProgress(count); } // 方法3:onProgressUpdate() // 作用:在主线程显示线程任务执行的进度 // 注:根据需求复写 @Override protected void onProgressUpdate(Integer... progresses) { ... } // 方法4:onPostExecute() // 作用:接收线程任务执行结果、将执行结果显示到UI组件 // 注:必须复写,从而自定义UI操作 @Override protected void onPostExecute(String result) { ...// UI操作 } // 方法5:onCancelled() // 作用:将异步任务设置为:取消状态 @Override protected void onCancelled() { ... } } /** * 步骤2:创建AsyncTask子类的实例对象(即任务实例) * 注:AsyncTask子类的实例必须在UI线程中创建 */ MyTask mTask = new MyTask(); /** * 步骤3:手动调用execute(Params... params) 从而执行异步线程任务 * 注: * a. 必须是在UI线程中调用 * b.同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常 * c.执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute()、doInBackground()、onProgressUpdate()、onPostExecute()。不能手动调用上述方法。 */ mTask.execute(); ``` 下面,我将根据提供的使用步骤进行源码分析并重构代码。请注意,在重构过程中,我会尽量保持原有的代码结构和逻辑顺序不变。 ## 步骤一:创建AsyncTask子类 在这一步骤中,我们需要创建一个继承自`AsyncTask`的子类。这个子类将用于执行异步任务。需要注意的是,我们只需要关注在这个过程中需要复写的方法即可,因为后续的源码实现会调用这些方法。 ```java public class MyAsyncTask extends AsyncTask { // 这里可以添加你需要执行的任务的具体实现代码 } ``` ## 步骤二:创建AsyncTask子类的实例对象(即任务实例) 现在,我们需要创建上述子类的一个实例对象,这个实例对象将会成为我们的任务实例。在实际应用中,你可以根据需求自定义这个实例对象的属性和方法。 ```java MyAsyncTask task = new MyAsyncTask(); task.execute(); // 开始执行任务 ``` 至此,我们已经完成了对源码的分析和重构。接下来,你可以在`MyAsyncTask`子类中添加具体的任务实现代码以及需要复写的方法。 AsyncTask的具体使用如下: ```java MyTask mTask = new MyTask(); ``` 源码分析:AsyncTask的构造函数。 1. 初始化WorkerRunnable变量,它是一个可存储参数的Callable对象。 2. 初始化FutureTask变量,它是一个包装任务的包装类,内部包含Callable,并增加了一些状态标识和操作Callable的接口。 3. 在任务执行线程池中回调:THREAD_POOL_EXECUTOR.execute()。下面会详细讲解。 4. 添加线程的调用标识。 5. 设置线程的优先级。 6. 执行异步操作,即我们使用过程中复写的耗时任务。 7. 若运行异常,设置取消的标志。 8. 把异步操作执行的结果发送到主线程,从而更新UI。下面会详细讲解。 9. 返回结果。 总结: 我们创建了一个WorkerRunnable类的实例对象,并复写了call()方法。接下来,我们还创建了一个FutureTask类的实例对象,并重写了done()方法。最后,我们在步骤3中手动调用execute(Params... params)方法。 以下是重构后的代码,并保持了原有的段落结构。 ```java /** * 具体使用 */ mTask.execute(); /** * 源码分析:AsyncTask的execute() */ public final AsyncTask execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } /** * 分析1:executeOnExecutor(sDefaultExecutor, params) * 参数说明:sDefaultExecutor = 任务队列线程池类(SerialExecutor)的对象 */ public final AsyncTask executeOnExecutor(Executor exec, Params... params) { // 1. 判断 AsyncTask 当前的执行状态 // PENDING = 初始化状态 if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task: the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task: the task has already been executed" + "(a task can be executed only once)"); } } // 2. 将AsyncTask状态设置为RUNNING状态 mStatus = Status.RUNNING; // 3. 主线程初始化工作 onPreExecute(); // 4. 添加参数到任务中 mWorker.mParams = params; // 5. 执行任务 // 此处的exec = sDefaultExecutor = 任务队列线程池类(SerialExecutor)的对象 // ->>分析2 exec.execute(mFuture); return this; } /** * 分析2:exec.execute(mFuture) * 说明:属于任务队列线程池类(SerialExecutor)的方法 */ private static class SerialExecutor implements Executor { /** SerialExecutor = 静态内部类 */ /** 即 是所有实例化的AsyncTask对象公有的 */ /** SerialExecutor内部维持了1个双向队列;容量根据元素数量调节 */ final ArrayDeque mTasks = new ArrayDeque<>(); final Object mLock = new Object(); // 用以同步锁住mActive字段和scheduleNext()方法中的操作。保证任务串行执行。 Thread mActiveThread = null; // 该线程用于运行队列中的任务。当该线程空闲时,它会自动去执行队列中的新任务。因此,该变量用来标记哪个线程正在运行任务。如果队列为空,那么这个变量就为null。这也意味着没有正在执行的任务。如果有正在执行的任务,那么这个变量就会被一个不同的线程占用。这样可以保证在任何时候都只有一个正在执行的任务。同时通过synchronized关键字对run方法进行同步控制,确保同一时刻只能有一个线程访问mRunQueue。从而达到线程安全的效果。即多个任务需一个一个加到该队列中;然后执行完队列头部的再执行下一个,以此类推的效果。因此,这个变量必须声明为volatile类型。因为Java虚拟机不保证所有涉及该变量的操作都是原子性的,所以不能将volatile写成volatile=true。只有将其显式地声明为volatile类型才能得到正确的结果。此外,这个变量还应该被声明为volatile类型的final类型,以便JVM生成读写代码时不会创建额外的对象。因此,最终的声明应该是volatile volatile final Thread。这样一来,无论什么时候都能获取到最新的值。同时还要注意的是,如果这个变量被赋值为null,那就意味着没有正在执行的任务了。因此,在调用startNewTask()方法时要特别小心,确保这个条件成立后再执行相关操作。否则会导致死循环的出现。 重构后的内容如下: 在执行任务前,我们使用了一个任务队列线程池类(SerialExecutor)来将任务按顺序放入队列中。这个类可以帮助我们在多线程环境下安全地管理任务的执行顺序。 为了保证AsyncTask中的任务是串行执行的,我们使用了同步锁来修饰execute()方法。这样可以确保在同一时刻只有一个任务在执行,避免了并发执行导致的数据不一致问题。 之后,线程任务执行是由一个专门的任务线程池类(THREAD_POOL_EXECUTOR)来完成的。这个类负责管理和调度线程,以便我们可以在多个线程之间合理地分配任务,提高程序的执行效率。 继续往下分析: THREAD_POOL_EXECUTORexecute() /**源码分析:THREAD_POOL_EXECUTOR.execute()说明:a. THREAD_POOL_EXECUTOR实际上是一个已配置好的可执行并行任务的线程池。b. 调用THREAD_POOL_EXECUTOR.execute()实际上是调用线程池的execute()去执行具体耗时任务。c. 而该耗时任务则是步骤2中初始化WorkerRunnable实例对象时复写的call()注:下面先看任务执行线程池的线程配置过程,看完后请回到步骤2中的源码分析call() */ // 步骤1:参数设置 //获得当前CPU的核心数 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); //设置线程池的核心线程数2-4之间,但是取决于CPU核数 private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); //设置线程池的最大线程数为 CPU核数*2+1 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; //设置线程池空闲线程存活时间30s private static final int KEEP_ALIVE_SECONDS = 30; //初始化线程工厂 private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; //初始化存储任务的队列为LinkedBlockingQueue 最大容量为128 private static final BlockingQueue sPoolWorkQueue = new LinkedBlockingQueue<>(128); // 步骤2: 根据参数配置执行任务线程池,即 THREAD_POOL_EXECUTOR public static final Executor THREAD_POOL_EXECUTOR; static { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); // 设置核心线程池的超时时间也为30s threadPoolExecutor.allowCoreThreadTimeOut(true); THREAD_POOL_EXECUTOR = threadPoolExecutor; } // 请回到步骤2中的源码分析call() 在步骤2中,我们进行了源码分析。现在让我们回到这个部分,继续深入探讨call()方法的实现细节。 首先,我们需要了解call()方法的作用和功能。call()方法是Python中的一个内置函数,它允许我们调用一个函数,并传递一些参数给这个函数。通过使用call()方法,我们可以在不显式地创建函数对象的情况下,直接调用一个函数。 接下来,我们将详细分析call()方法的内部实现。在Python中,call()方法实际上是一个高阶函数,它接受一个可调用对象(如函数、类或实例方法)作为第一个参数,以及一系列其他参数。这些参数将被传递给可调用对象的__call__()方法。 当我们调用一个函数时,Python解释器会自动执行该函数的代码。在这个过程中,解释器会查找函数名对应的字节码指令,并执行这些指令。这些指令包括加载函数对象、获取参数值、执行函数体等操作。 在call()方法的内部实现中,Python解释器会首先检查传入的第一个参数是否是一个可调用对象。如果是,解释器会继续查找该对象的__call__()方法,并将其作为实际要调用的函数。然后,解释器会将剩余的参数传递给__call__()方法。 需要注意的是,如果传入的第一个参数不是一个可调用对象,或者没有提供足够的参数来调用__call__()方法,那么call()方法将会引发TypeError异常。 总结一下,call()方法是Python中用于动态调用函数的一种便捷方式。通过使用call()方法,我们可以在运行时决定要调用哪个函数,并传递相应的参数。这使得编写灵活且可扩展的代码变得更加容易。 AsyncTask类的源码分析主要包含以下几个部分: 1. `AsyncTask()`构造函数:这个方法初始化了一个`WorkerRunnable`对象,该对象存储了需要在后台线程执行的任务参数。同时,它也设置了线程的优先级为`Process.THREAD_PRIORITY_BACKGROUND`,然后调用`doInBackground(mParams)`方法来执行后台任务。如果任务执行成功,结果会被发送到主线程;如果执行失败,会抛出异常。 ```java public AsyncTask() { mWorker = new WorkerRunnable() { public Result call() throws Exception { mTaskInvoked.set(true); Result result = null; try { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); result = doInBackground(mParams); Binder.flushPendingCommands(); } catch (Throwable tr) { mCancelled.set(true); throw tr; } finally { postResult(result); } return result; } }; ...//省略其他代码 } ``` 2. `postResult(Result result)`方法:这个方法通过Handler将执行结果发送回主线程,并更新UI。这个过程是通过`getHandler().obtainMessage()`获取一个消息,然后使用`sendToTarget()`方法将消息发送到Handler。 ```java private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult(this, result)); message.sendToTarget(); return result; } ``` 3. `InternalHandler()`类:这是一个内部类,继承自`Handler`。它的构造函数需要在主线程中使用。在这个类中,`handleMessage()`方法用于处理从子线程发送过来的消息。当收到的消息是`MESSAGE_POST_RESULT`时,它会调用`finish()`方法结束任务;当收到的消息是`MESSAGE_POST_PROGRESS`时,它会调用`onProgressUpdate()`方法通知主线程更新进度。 ```java private static class InternalHandler extends Handler { public InternalHandler() { super(Looper.getMainLooper()); } ...//省略其他代码 } ``` 4. `finish(Result result)`方法:这个方法首先判断任务是否已经被取消。如果任务被取消,就执行`onCancelled(result)`方法;如果任务没有被取消,就执行`onPostExecute(result)`方法更新UI。无论任务是否被取消,最后都会将AsyncTask的状态设置为已完成。 总结 本文介绍了多线程中的AsyncTask的工作原理和源码分析,总结如下: 2. HandlerThread 2.1 简介 HandlerThread是Android中用于处理消息队列的一个类,它继承自Thread类并封装了Handler类。HandlerThread主要用于在子线程中执行耗时操作,然后通过Handler将结果传递回主线程进行UI更新。 2.2 使用步骤 HandlerThread的使用步骤分为5步: 1) 创建HandlerThread实例; 2) 启动HandlerThread; 3) 获取Handler实例; 4) 在Handler实例中执行耗时操作; 5) 通过Handler将结果传递回主线程。 任务线程池类(THREAD_POOL_EXECUTOR)实际上是一个已配置好的可执行并行任务的线程池。调用THREAD_POOL_EXECUTOR.execute()实际上是调用线程池的execute()去执行具体耗时任务。而该耗时任务则是步骤2中初始化WorkerRunnable实例对象时复写的call()内容。 在call()方法里,先调用我们复写的doInBackground(mParams)执行耗时操作,再调用postResult(result),通过InternalHandler类将任务消息传递到主线程。根据消息标识(MESSAGE_POST_RESULT),最终通过finish()调用我们复写的onPostExecute(result),从而实现UI更新操作。 至此,关于AsyncTask的源码分析完毕。 ```java // 步骤1:创建HandlerThread实例对象 // 传入参数 = 线程名字,作用 = 标记该线程 HandlerThread mHandlerThread = new HandlerThread("handlerThread"); // 步骤2:启动线程 mHandlerThread.start(); // 步骤3:创建工作线程Handler & 复写handleMessage() // 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信 // 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行 Handler workHandler = new Handler(mHandlerThread.getLooper()) { @Override public boolean handleMessage(Message msg) { // ...//消息处理 return true; } }; // 步骤4:使用工作线程Handler向工作线程的消息队列发送消息 // 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作 // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 2; //消息的标识 msg.obj = "B"; // 消息的存放 // b. 通过Handler发送消息到其绑定的消息队列 workHandler.sendMessage(msg); // 步骤5:结束线程,即停止线程的消息循环 mHandlerThread.quit(); ``` ```xml xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" tools:context="com.example.carson_ho.handler_learning.MainActivity"> android:id="@+id/text1" android:layout_centerInParent="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="测试结果" /> android:id="@+id/button1" android:layout_centerInParent="true" android:layout_below="@+id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击延迟1s + 显示我爱学习" /> android:id="@+id/button2" android:layout_centerInParent="true" android:layout_below="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击延迟3s + 显示我不爱学习" /> android:id="@+id/button3" android:layout_centerInParent="true" android:layout_below="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="结束线程的消息循环" /> ``` 以下是重构后的代码: ```java public class MainActivity extends AppCompatActivity { private ImageView imgView; // 图片显示控件 private Button btn_select, btn_cancel; // 选择和取消按钮 private ProgressDialog progressDialog; // 进度对话框 private String photoPath = ""; // 照片路径 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initUI(); // 初始化界面元素 } private void initUI() { imgView = findViewById(R.id.imgView); // 通过 ID 获取图片显示控件 btn_select = findViewById(R.id.btn_select); // 通过 ID 获取选择按钮 btn_cancel = findViewById(R.id.btn_cancel); // 通过 ID 获取取消按钮 btn_select.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { chooseFromGallery(); // 点击选择按钮,调用选择相册的方法 } }); btn_cancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); // 点击取消按钮,关闭当前界面 } }); } /** * 从相册中选择图片并显示在界面上 */ private void chooseFromGallery() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); // 如果没有读取外部存储的权限,则请求权限 return; } else { showProgressDialog(); // 如果已经拥有读取外部存储的权限,则显示进度对话框开始选择图片 } Intent intent = new Intent(); intent.setType("image/*"); // 设置要选择的文件类型为图片 intent.setAction(Intent.ACTION_GET_CONTENT); // 设置操作类型为从外部获取内容 startActivityForResult(Intent.createChooser(intent, "Select Picture"), SELECT_PICTURE); // 通过 Intent 启动选择器,并传递选择结果回调的方法名和返回值给 onActivityResult() 方法处理结果 } /** * 在选择图片后,将选中的图片保存到本地并显示在界面上 * @param requestCode:请求码,用于区分不同的请求结果 * @param resultCode:结果码,用于判断是否成功选择了图片 * @param data:返回的数据,包含选中的图片信息,如果没有选中任何图片则为 null */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); Uri imageUri = null; // 要显示的图片的文件路径,如果没有选中任何图片则为 null ImageView imageView = findViewById(R.id.imgView); // 通过 ID 获取图片显示控件 Bitmap bitmap; // 要显示的位图对象,用于缓存选中的图片数据以提高性能和减少内存占用量 BitmapShader shader; // 将位图作为渐变背景使用的遮罩层对象,用于生成圆角矩形渐变效果的背景色或边框样式等效果 int width, height; // 要显示的图片的宽度和高度,如果没有选中任何图片则为默认值0和0 RectF rectF; // 要显示图片的圆形区域的位置和大小范围,用于限制图片的位置和大小范围以避免超出边界或出现变形的情况。该参数也可以省略不设置,此时默认显示整个视图区域。如果需要限制其他形状区域的范围,可以根据需求自行定义该参数的相关属性值。 ```java public class MainActivity extends AppCompatActivity { Handler mainHandler, workHandler; HandlerThread mHandlerThread; TextView text; Button button1, button2, button3; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 显示文本 text = (TextView) findViewById(R.id.text1); // 创建与主线程关联的Handler mainHandler = new Handler(); /** * 步骤1:创建HandlerThread实例对象 * 传入参数 = 线程名字,作用 = 标记该线程 */ mHandlerThread = new HandlerThread("handlerThread"); /** * 步骤2:启动线程 */ mHandlerThread.start(); /** * 步骤3:创建工作线程Handler & 复写handleMessage() * 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信 * 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行 */ workHandler = new Handler(mHandlerThread.getLooper()) { @Override // 消息处理的操作 public void handleMessage(Message msg) { //设置了两种消息处理操作,通过msg来进行识别 switch (msg.what) { // 消息1 case 1: try { //延时操作 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 通过主线程Handler.post方法进行在主线程的UI更新操作 mainHandler.post(new Runnable() { @Override public void run() { text.setText("我爱学习"); } }); break; // 消息2 case 2: try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } mainHandler.post(new Runnable() { @Override public void run() { text.setText("我不喜欢学习"); } }); break; default: break; } } }; /** * 步骤4:使用工作线程Handler向工作线程的消息队列发送消息 * 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作 */ // 点击Button1 button1 = (Button) findViewById(R.id.button1); button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 通过sendMessage()发送 // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 1; //消息的标识 msg.obj = "A"; // 消息的存放 // b. 通过Handler发送消息到其绑定的消息队列 workHandler.sendMessage(msg); } }); // 点击Button2 button2 = (Button) findViewById(R.id.button2); button2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 通过sendMessage()发送 // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 2; //消息的标识 msg.obj = "B"; // 消息的存放 // b. 通过Handler发送消息到其绑定的消息队列 workHandler.sendMessage(msg); } }); // 点击Button3 /** * 作用:退出消息循环 */ button3 = (Button) findViewById(R.id.button3); button3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mHandlerThread.quit(); } }); } }``` 在HandlerThread中,有2个问题需注意的:内存泄漏 & 连续发送消息。在上面的例子中,出现了严重的警告:In Android, Handler classes should be static or leaks might occur. 即造成了严重的内存泄漏。当你连续点击3下时,发现并无按照最新点击的按钮操作显示,而是按顺序的一个个显示出来。原因:使用HandlerThread时只是开了一个工作线程,当你点击了n下后,只是将n个消息发送到消息队列MessageQueue里排队,等候派发消息给Handler再进行对应的操作。内部原理 = Thread类 + Handler类机制,即:通过继承Thread类,快速地创建1个带有Looper对象的新工作线程。通过封装Handler类,快速创建Handler & 与其他线程进行通信。本次源码分析将根据 HandlerThread的使用步骤讲解。HandlerThread的使用步骤有5个: 1. 实现一个Handler接口。 2. 创建一个HandlerThread对象。 3. 启动HandlerThread线程。 4. 在HandlerThread线程中启动子线程并传递参数。 5. 通过Handler发送消息给子线程。 ```java // 步骤1:创建HandlerThread实例对象 // 传入参数 = 线程名字,作用 = 标记该线程 HandlerThread mHandlerThread = new HandlerThread("handlerThread"); ``` HandlerThread类继承自Thread类,创建HandlerThread类对象需要先创建Thread类对象,然后设置线程优先级。具体步骤如下: 1. 创建HandlerThread类对象: ```java HandlerThread mHandlerThread = new HandlerThread("handlerThread"); ``` 2. 设置线程优先级: 有两种构造方法,一种是默认优先级(不设置),另一种是自定义设置优先级。 ```java // 默认优先级 public HandlerThread(String name) { super(name); mPriority = Process.THREAD_PRIORITY_DEFAULT; } // 自定义设置优先级 public HandlerThread(String name, int priority) { super(name); mPriority = priority; } ``` 3. 启动线程: 使用start()方法启动线程。 这段代码是一个自定义Thread类的实现,具体使用了HandlerThread。在运行时,会先调用父类(Thread类)的start()方法,最终回调HandlerThread的run()方法。下面是代码的重构: ```java /** * 具体使用 */ mHandlerThread.start(); /** * 源码分析:此处调用的是父类(Thread类)的start(),最终回调HandlerThread的run() */ @Override public void run() { // 1. 获得当前线程的id mTid = Process.myTid(); // 2. 创建1个Looper对象 & MessageQueue对象 Looper.prepare(); // 3. 通过持有锁机制来获得当前线程的Looper对象 synchronized (this) { mLooper = Looper.myLooper(); // 发出通知:当前线程已经创建mLooper对象成功 // 此处主要是通知getLooper()中的wait() notifyAll(); // 此处使用持有锁机制 + notifyAll()是为了保证后面获得Looper对象前就已创建好Looper对象 } // 4. 设置当前线程的优先级 Process.setThreadPriority(mPriority); // 5. 在线程循环前做一些准备工作 ->> 分析1 // 该方法实现体是空的,子类可实现/不实现该方法 onLooperPrepared(); // 6. 进行消息循环,即不断从MessageQueue中取消息 & 派发消息 Looper.loop(); mTid = -1; } ``` 在这段代码中,主要进行了以下操作: 1. 获取当前线程的ID。 2. 创建一个Looper对象和MessageQueue对象。 3. 通过持有锁机制来获得当前线程的Looper对象。 4. 设置当前线程的优先级。 5. 在线程循环前做一些准备工作,该方法实现体是空的,子类可实现/不实现该方法。 6. 进行消息循环,即不断从MessageQueue中取消息并派发消息。 重构后的内容如下: 在当前工作线程中,我们需要为步骤1创建的线程创建一个Looper对象和MessageQueue对象。首先,通过持有锁机制来获得当前线程的Looper对象。然后,发出通知表示当前线程已经成功创建了mLooper对象。接下来,工作线程将进行消息循环,即不断从MessageQueue中获取消息并派发消息。 为了实现这一过程,我们需要完成以下步骤: 1. 创建工作线程Handler并重写handleMessage()方法(步骤3)。 2. 在步骤1中创建Thread对象时,传入Runnable对象作为参数,该Runnable对象实现了Handler接口,并重写了handleMessage()方法。 3. 在handleMessage()方法中,处理接收到的消息,并根据需要调用其他线程的方法。 4. 在主线程中,调用Looper.prepare()和loop()方法来启动消息循环。 5. 当不再需要发送消息时,调用Looper.quit()和Thread.join()方法来停止消息循环。 以下是重构后的内容,并保持了原有的段落结构: 具体使用: 作用:将Handler关联HandlerThread的Looper对象、实现消息处理操作以及与其他线程进行通信。注:消息处理操作(HandlerMessage())的执行线程等于mHandlerThread所创建的工作线程中执行。 ```java Handler workHandler = new Handler(handlerThread.getLooper()) { @Override public boolean handleMessage(Message msg) { // 消息处理 return true; } }; ``` 源码分析:handlerThread.getLooper() 作用:获得当前HandlerThread线程中的Looper对象。 ```java public Looper getLooper() { // 若线程不是存活的,则直接返回null if (!isAlive()) { return null; } // 若当前线程存活,再判断线程的成员变量mLooper是否为null // 直到线程创建完Looper对象后才能获得Looper对象,若Looper对象未创建成功,则阻塞 synchronized (this) { while (isAlive() && mLooper == null) { try { // 此处会调用wait方法去等待 wait(); } catch (InterruptedException e) { } } } // 上述步骤run()使用持有锁机制 + notifyAll()获得Looper对象后 // 则通知当前线程的wait()结束等待并跳出循环 // 最终getLooper()返回的是在run()中创建的mLooper对象 return mLooper; } ``` 总结: 在获取HandlerThread工作线程的Looper对象时,需要解决同步问题。只有在线程创建成功并且对应的Looper对象也创建成功后,才能获得Looper的值,并将创建的Handler与工作线程的Looper对象绑定。解决方案是使用同步锁、wait()和notifyAll()来保证同步。具体步骤如下: 1. 在run()方法中成功创建Looper对象后,立即调用notifyAll()通知getLooper()中的wait()结束等待并返回run()中成功创建的Looper对象,使得Handler与该Looper对象绑定。 2. 使用工作线程Handler向工作线程的消息队列发送消息。具体操作包括定义要发送的消息、通过Handler发送消息到其绑定的消息队列等。 3. 结束线程,即停止线程的消息循环。 代码示例: ```java // 确保同步的解决方案:同步锁、wait() 和 notifyAll() public class MyHandlerThread extends Thread { private Looper mLooper; private Handler mWorkHandler = new Handler(mLooper); @Override public void run() { // a. 创建Looper对象 try { mLooper = Looper.prepare(); synchronized (this) { // b. 通过notifyAll()通知wait()结束等待 notifyAll(); } } catch (Exception e) { e.printStackTrace(); } finally { if (mLooper != null) { mLooper.quit(); } } } // 具体使用:在工作线程中,当消息循环时取出对应消息并在工作线程执行相关操作 // 注意:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行 public void sendMessageToWorkerThread(String message) { Message msg = Message.obtain(); msg.what = 2; // 消息的标识 msg.obj = "B"; // 消息的存放 // c. 通过Handler发送消息到其绑定的消息队列 mWorkHandler.sendMessage(msg); } } ``` 具体使用: ```java mHandlerThread.quit(); ``` 源码分析: 1. `mHandlerThread.quit()` 方法属于 `HandlerThread` 类,该类有两种让当前线程退出消息循环的方法:`quit()` 和 `quitSafely()`。 2. 方式1:`quit()` 的特点是效率高,但线程不安全。方法内部首先获取 `Looper`,如果 `Looper` 不为空,则调用 `looper.quit()` 退出循环并返回 `true`,否则返回 `false`。 3. 方式2:`quitSafely()` 的特点是效率低,但线程安全。方法内部同样首先获取 `Looper`,如果 `Looper` 不为空,则调用 `looper.quitSafely()` 退出循环并返回 `true`,否则返回 `false`。 4. 注:上述两种方法最终都会调用 `MessageQueue.quit(boolean safe)`,我们可以进行进一步分析。 分析1:`MessageQueue.quit(boolean safe)` 方法根据参数 `safe` 的值选择不同的方式移除所有回调。 5. 若 `safe` 为 `true`,则调用 `removeAllFutureMessagesLocked()` 方法,该方法会遍历消息链表并移除所有回调,然后将消息链表重置为空。这种方式不安全,因为它可能会在消息处理过程中被调用。 6. 若 `safe` 为 `false`,则调用 `removeAllMessagesLocked()` 方法,该方法会遍历消息链表并移除所有回调,然后将消息链表重置为空。这种方式是安全的,因为它会在消息处理完毕后再执行。 7. 最后,调用 `nativeWake(mPtr)` 唤醒处理器线程。 分析2:`removeAllMessagesLocked()` 方法的作用是遍历消息链表、移除所有信息的回调并重置为空。原理是通过循环遍历消息链表,将每个消息的下一个指针指向前一个消息,从而实现链表的删除。 8. 分析3:`removeAllFutureMessagesLocked()` 方法的作用是在判断当前消息队列是否正在处理消息后,选择相应的方式移除消息和退出循环。结论:退出方法的安全与否(`quitSafe()` 或 `quit()`),在于该方法移除消息、退出循环时是否在意当前队列是否正在处理消息。 本文全面分析了多线程中HandlerThread的源码,总结如下: 3. IntentService 3.1 定义 Android里的一个封装类,继承四大组件之一的Service。 3.2 作用 处理异步请求并实现多线程。 3.3 使用场景 线程任务需按顺序、在后台执行。最常见的场景是离线下载。不符合多个数据同时请求的场景是所有的任务都在同一个Thread looper里执行。 3.4 使用步骤 步骤1:定义IntentService的子类,需复写onHandleIntent()方法。 步骤2:在Manifest.xml中注册服务。 步骤3:在Activity中开启Service服务。 3.5 实例讲解 步骤1:定义IntentService的子类,传入线程名称、复写onHandleIntent()方法。 以下是重构后的内容: ```java public class MyIntentService extends IntentService { /** * 在构造函数中传入线程名字 **/ public MyIntentService() { // 调用父类的构造函数,参数 = 工作线程的名字 super("myIntentService"); } /** * 复写onHandleIntent()方法 * 根据 Intent实现耗时任务操作 **/ @Override protected void onHandleIntent(Intent intent) { // 根据 Intent的不同,进行不同的事务处理 String taskName = intent.getExtras().getString("taskName"); switch (taskName) { case "task1": Log.i("myIntentService", "do task1"); break; case "task2": Log.i("myIntentService", "do task2"); break; default: break; } } /** * 默认实现 = 将请求的Intent添加到工作队列里 **/ @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("myIntentService", "onStartCommand"); return super.onStartCommand(intent, flags, startId); } } ``` 在Android开发中,我们通常会创建一个Service以便在我们的应用后台执行一些任务。以下是如何在Manifest.xml中注册服务以及如何在Activity中开启服务的步骤: 1. 在Manifest.xml中注册服务: ```xml ``` 在上述代码中,我们定义了一个名为“.myIntentService”的服务,并为其设置了一个动作过滤器,该动作过滤器指向了“cn.scu.finch”。 2. 在Activity中开启Service服务: 首先,我们需要获取到这个服务实例。这通常通过使用上下文的getApplicationContext()方法和调用bindService()方法来完成。然后,我们可以调用startService()方法来启动服务。当不再需要服务时,我们可以调用unbindService()方法来解除服务的绑定。 以下是相关的Java代码: ```java // 获取到上下文和应用程序的实例 Context context = getApplicationContext(); MyIntentService myIntentService = new MyIntentService(); // 绑定服务 boolean result = context.bindService(new Intent(context, MyIntentService.class), serviceConnection, Context.BIND_AUTO_CREATE); if (!result) { // 如果无法绑定,那么可能是由于没有启动该服务或者用户拒绝了服务请求等原因,需要进行相应的处理 } else { // 成功绑定后,可以在这里启动服务或者进行其他操作 } ``` 请注意,上述代码中的MyIntentService应替换为你实际要使用的服务类名。同时,你需要在你的Activity中实现ServiceConnection接口,这样才能在服务被系统销毁时释放资源。 您好,IntentService是继承于Service并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作。与普通Service相比,IntentService适用于需要在后台执行短时间的、一次性的任务,而不适合长时间运行或需要与UI交互的后台任务。IntentService处理完所有请求后自动停止,而普通Service需要手动调用stopSelf()或stopService()来停止服务 。 .7 工作原理 IntentService的工作原理和源码工作流程如下: 3.7.1 流程示意图 IntentService的工作流程如下: 1. 当启动IntentService时,会创建一个新的线程。 2. 在onCreate()方法中,初始化工作队列。 3. 当接收到客户端发送的Intent时,会调用onHandleIntent()方法处理该Intent。 4. onHandleIntent()方法会将任务添加到工作队列中,并在适当的时候执行。 5. 当所有任务完成后,IntentService会自动结束。 3.7.2 特别注意 若启动IntentService多次,那么每个耗时操作将以队列的方式在IntentService的onHandleIntent回调方法中依次执行,执行完自动结束。 接下来,我们将通过源码分析解决以下问题: 3.8 源码分析 问题1:IntentService如何单独开启1个新的工作线程? 主要分析内容 = IntentService源码中的onCreate()方法。 以下是重构后的代码: ```java @Override public void onCreate() { super.onCreate(); // 1. 通过实例化andlerThread新建线程 & 启动;故 使用IntentService时,不需额外新建线程 // HandlerThread继承自Thread,内部封装了 Looper HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); // 2. 获得工作线程的 Looper & 维护自己的工作队列 mServiceLooper = thread.getLooper(); // 3. 新建mServiceHandler & 绑定上述获得Looper // 新建的Handler 属于工作线程 ->> 分析1 mServiceHandler = new ServiceHandler(mServiceLooper); } /** * 分析1:ServiceHandler源码分析 */ private final class ServiceHandler extends Handler { // 构造函数 public ServiceHandler(Looper looper) { super(looper); } // IntentService的handleMessage()把接收的消息交给onHandleIntent()处理 @Override public void handleMessage(Message msg) { // onHandleIntent方法在工作线程中执行 // onHandleIntent() = 抽象方法,使用时需重写 ->> 分析2 onHandleIntent((Intent) msg.obj); // 执行完调用 stopSelf() 结束服务 stopSelf(msg.arg1); } } /** * 分析2: onHandleIntent()源码分析 * onHandleIntent() = 抽象方法,使用时需重写 */ @WorkerThread protected abstract void onHandleIntent(Intent intent); ``` IntentService是Android中的一种服务,它可以在一个单独的工作线程中执行任务。通过HandlerThread单独开启一个工作线程,创建一个内部Handler:ServiceHandler,然后将ServiceHandler绑定到IntentService上。当接收到启动服务的请求时,会调用onStartCommand()方法,并将服务intent传递给ServiceHandler。接着,将Intent插入到工作队列中,并逐个发送给onHandleIntent()方法。在onHandleIntent()方法中,依次处理所有Intent对象所对应的任务。 需要注意的是,工作任务队列是顺序执行的。如果一个任务正在IntentService中执行,此时再发送一个新的任务请求,那么这个新的任务会一直等待直到前面一个任务执行完毕后才开始执行。这是因为onCreate()只会被调用一次,只会创建一个工作线程;当多次调用startService(Intent)时(即onStartCommand()也会调用多次),实际上不会创建新的工作线程,只是把消息加入消息队列中并等待执行。所以,多次启动IntentService会按顺序执行事件。如果服务停止了,则会清除消息队列中的消息,后续的事件不执行。 另外,不建议使用bindService()启动IntentService。因为在IntentService中,onBind()默认返回null。采用bindService()启动IntentService的生命周期如下: onCreate() 是 Android Service 生命周期的第一个回调方法。当 Service 实例被创建时,系统会调用此方法。在这个方法中,我们可以进行一些初始化操作,例如绑定到一个 Activity 或者设置监听器等。 ```java @Override public void onCreate() { super.onCreate(); // 初始化操作 } ``` onBind() 是 Service 生命周期的第二个回调方法。当 Service 需要与客户端(通常是 Activity)进行通信时,系统会调用此方法。在这个方法中,我们可以将 Service 实例绑定到一个 Intent,以便其他组件可以调用它。 ```java @Override public IBinder onBind(Intent intent) { return mBinder; } ``` onUnbind() 是 Service 生命周期的第三个回调方法。当客户端(通常是 Activity)解除绑定时,系统会调用此方法。在这个方法中,我们可以进行一些清理操作,例如取消正在执行的任务等。 ```java @Override public boolean onUnbind(Intent intent) { // 清理操作 return true; } ``` onDestroy() 是 Service 生命周期的第四个回调方法。当 Service 不再需要时,系统会调用此方法。在这个方法中,我们可以进行一些资源回收操作,例如关闭线程池等。 ```java @Override public void onDestroy() { // 资源回收操作 } ``` 如果使用 IntentService,那么 onStart()、onStop()、onBind()、onUnbind()、onDestroy() 这些生命周期方法将不会被调用。这是因为 IntentService 是在一个单独的进程中运行的,而不是在应用程序的主线程中运行的。因此,无法实现多线程的操作。在这种情况下,你应该使用 Service 而不是 IntentService。
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击开始卖票" />
在`MainActivity.java`文件中:
public class MainActivity extends AppCompatActivity {
private Button button;
private SellTicketThread window1;
private SellTicketThread window2;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
window1 = new SellTicketThread(1); // 窗口1每秒卖一张票
window2 = new SellTicketThread(3); // 窗口2每三秒卖一张票
window1.start(); // 启动窗口1的线程
window2.start(); // 启动窗口2的线程
});
这段代码是一个Android应用程序中的一个Activity(MainActivity)的Java实现。该Activity的主要功能是一个按钮,点击后会启动两个线程来模拟卖票的过程。下面是重构后的代码,保持了原有的结构和逻辑:
package com.example.carson_ho.demoforthread_2;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.util.Log;
// 主布局中定义了一个按钮用以启动线程
Button button;
// 步骤1:创建线程类,继承自Thread类
// 因为这里需要有两个操作:一个窗口卖票速度是1s/张,一个窗口是3s/张
// 所以需要创建两个Thread的子类
// 第一个Thread子类实现一个窗口卖票速度是1s/张
private class MyThread1 extends Thread {
private int ticket = 100; // 一个窗口有100张票
private String name; // 窗口名,也即是线程的名字
public MyThread1(String name) {
this.name = name;
while (ticket > 0) {
ticket--;
System.out.println(name + "卖掉了1张票,剩余票数为:" + ticket);
Thread.sleep(1000); // 卖票速度是1s一张
// 第二个Thread子类实现一个窗口卖票速度是3s/张
private class MyThread2 extends Thread {
public MyThread2(String name) {
Thread.sleep(3000); // 卖票速度是3s一张
// Button按下时会开启一个新线程执行卖票
button = (Button) findViewById(R.id.button);
// Step2:创建线程类的实例,创建二个线程,模拟二个窗口卖票。注意这里使用了单例模式,确保每个窗口只有一个实例。如果不希望使用单例模式,可以修改代码如下://创建二个线程,模拟二个窗口卖票MyThread1 mt1 = new MyThread1("窗口1");MyThread2 mt2 = new MyThread2("窗口2");// Step3:调用start()方法开启线程//启动二个线程,也即是窗口,开始卖票mt1.start();mt2.start();// 在日志中输出线程的状态和信息,方便调试Log.d("MainActivity", "线程开始");mt1.start();Log.d("MainActivity", "线程1状态:" + mt1.getState());mt2.start();Log
测试结果表明,由于卖票速度不同,窗口1卖3张票时,窗口2才卖1张票。
在Java中,实现多线程有两种常用的方法:继承Thread类和实现Runnable接口。今天我们将对比这两种方法。
一、实现Runnable接口
1. 简介
实现Runnable接口是实现多线程的一种常用方法。通过实现Runnable接口,可以创建一个新的线程来执行特定的任务。
2. 使用详情
需要注意的是,Java中真正能创建新线程的只有Thread类对象。通过实现Runnable接口的方式,最终还是通过Thread类对象来创建线程。因此,实现了Runnable接口的类被称为线程辅助类,而Thread类才是真正的线程类。
(2)具体使用
以下是实现Runnable接口的步骤:
1. 创建线程辅助类,实现Runnable接口;
2. 复写run()方法,定义线程行为;
3. 创建线程辅助对象,即实例化线程辅助类;
4. 创建线程对象,即实例化线程类;线程类 = Thread类;
5. 通过线程对象控制线程的状态,如运行、睡眠、挂起/停止;
6. 当调用start()方法时,线程对象会自动回调线程辅助类对象的run()方法,从而实现线程操作。
示例代码如下:
// 步骤1:创建线程辅助类,实现Runnable接口
class MyThread implements Runnable{ .... @Override // 步骤2:复写run(),定义线程行为 public void run(){ } } // 步骤3:创建线程辅助对象,即实例化线程辅助类 MyThread mt=new MyThread(); // 步骤4:创建线程对象,即实例化线程类;线程类 = Thread类; // 创建时通过Thread类的构造函数传入线程辅助类对象 // 原因:Runnable接口并没有任何对线程的支持,我们必须创建线程类(Thread类)的实例,从Thread类的一个实例内部运行 Thread td=new Thread(mt); // 步骤5:通过线程对象控制线程的状态,如运行、睡眠、挂起/停止 // 当调用start()方法时,线程对象会自动回调线程辅助类对象的run(),从而实现线程操作 td.start();
在很多情况下,开发者会选择一种更加方便的方法去创建线程:匿名类。
// 步骤1:通过匿名类直接创建线程辅助对象,即实例化线程辅助类
Runnable mt = new Runnable() {
// 步骤2:复写run(),定义线程行为
};
// 步骤3:创建线程对象,即实例化线程类;线程类 = Thread类;
Thread mt1 = new Thread(mt, "窗口1");
// 步骤4:通过线程对象控制线程的状态,如运行、睡眠、挂起/停止
mt1.start();
package com.example.carson_ho.demoforrunnable3;
import androidx.appcompat.app.AppCompatActivity;
Button button = findViewById(R.id.button);
// 在这里启动线程并执行卖票操作
// 在主布局中定义一个按钮用于启动线程
// 步骤2:创建线程类的实例
// 因为是两个窗口共卖100张票,即共用资源,所以只实例化一个实现了Runnable接口的类
MyThread1 mt = new MyThread1();
// 因为要创建二个线程,模拟二个窗口卖票
Thread mt11 = new Thread(mt, "窗口1");
Thread mt12 = new Thread(mt, "窗口2");
// 步骤3:调用start()方法开启线程
// 启动二个线程,也即是窗口,开始卖票
mt11.start();
mt12.start();
测试结果表明,实现了两个窗口一起卖100张票的目的。
3.1 作用
在多线程的应用场景中,将工作线程中需更新UI的操作信息传递到UI主线程,从而实现工作线程对UI的更新处理,最终实现异步消息的处理。
3.2 意义
为什么要用Handler消息传递机制?因为多个线程并发更新UI的同时需要保证线程安全。具体描述如下:
3.4 相关概念
关于Handler异步通信机制中的相关概念如下:
- Handler:用于发送、处理和拦截消息的组件。
- Message:消息对象,封装了要处理的数据和相关信息。
- MessageQueue:消息队列,负责存储和管理Message对象。
- Looper:循环器,用于在主线程中循环处理MessageQueue中的消息。
3.5 使用方式
Handler的使用方式因发送消息到消息队列的方式不同而不同,共分为两种:使用Handler.sendMessage()、使用Handler.post()。
方式1:使用Handler.sendMessage()
在该使用方式中,又分为两种:新建Handler子类(内部类)、匿名Handler子类。但本质相同,即继承了Handler类并创建了子类。
/** * 方式1:新建Handler子类(内部类)
*/
// 步骤1:自定义Handler子类(继承Handler类) & 复写handleMessage()方法
class mHandler extends Handler {
// 通过复写handlerMessage() 从而确定更新UI的操作
public void handleMessage(Message msg) {
// ...// 需执行的UI操作
// 步骤2:在主线程中创建Handler实例
private Handler mhandler = new mHandler();
// 步骤3:创建所需的消息对象
Message msg = Message.obtain(); // 实例化消息对象
msg.what = 1; // 消息标识
msg.obj = "AA"; // 消息内容存放
// 步骤4:在工作线程中 通过Handler发送消息到消息队列中
// 可通过sendMessage() / post()
// 多线程可采用AsyncTask、继承Thread类、实现Runnable
mHandler.sendMessage(msg);
// 步骤5:开启工作线程(同时启动了Handler)
/**
* 方式2:匿名内部类
// 步骤1:在主线程中 通过匿名内部类 创建Handler类对象
private Handler mhandler = new Handler() {
// 通过复写handlerMessage()从而确定更新UI的操作
// 步骤2:创建消息对象
// 步骤3:在工作线程中 通过Handler发送消息到消息队列中
mhandler.sendMessage(msg);
// 步骤4:开启工作线程(同时启动了Handler)
方式2:使用Handler.post()
本文将通过实例讲解如何使用Handler.post()方法。
// 步骤1:在主线程中创建Handler实例
private Handler mhandler = new Handler(Looper.getMainLooper());
// 步骤2:在工作线程中发送消息到消息队列中并指定操作UI内容。需要传入一个Runnable对象
mhandler.post(new Runnable() {
// 需执行的UI操作
// 步骤3:开启工作线程(同时启动了Handler)
// 可以采用AsyncTask、继承Thread类、实现Runnable等方式实现多线程
3.6 实例讲解
本文将以一个简单的“更新UI操作”案例来讲解Handler的用法。由于Handler的作用是将工作线程需要操作UI的消息传递到主线程,使得主线程可以根据工作线程的需求更新UI,从而避免线程操作不安全的问题。所以下面的实例是一个简单的“更新UI操作”。
主布局文件如下:activity_main.xml
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" android:orientation="vertical" tools:context=".MainActivity"> android:id="@+id/tv_result" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" />
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"
android:orientation="vertical"
tools:context=".MainActivity">
android:id="@+id/tv_result" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" />
android:id="@+id/tv_result"
android:text="Hello World!" />
// Handler子类(内部类)
public class MyHandler extends Handler {
private final TextView show;
public MyHandler(TextView show) {
this.show = show;
// 在此处处理消息,例如更新UI等操作
show.setText("Hello, World!");
在MainActivity中使用MyHandler:
import android.os.Handler;
import android.widget.TextView;
private TextView show;
private MyHandler myHandler;
show = findViewById(R.id.show);
// 创建并发送消息给Handler,然后在handleMessage方法中处理消息
Message message = new Message();
message.what = 1; // 可以自定义消息类型
myHandler = new MyHandler(show);
myHandler.sendMessage(message);
以下是重构后的代码:
public TextView mTextView;
public Handler mHandler;
// 步骤1:(自定义)新创建Handler子类(继承Handler类) & 复写handleMessage()方法
class Mhandler extends Handler {
// 根据不同线程发送过来的消息,执行不同的UI操作
// 根据 Message对象的what属性 标识不同的消息
switch (msg.what) {
case 1:
mTextView.setText("执行了线程1的UI操作");
break;
case 2:
mTextView.setText("执行了线程2的UI操作");
mTextView = (TextView) findViewById(R.id.show);
// 在主线程中创建Handler实例
mHandler = new Mhandler();
// 采用继承Thread类实现多线程演示
new Thread() {
Thread.sleep(3000);
Message msg = Message.obtain();
msg.obj = "A"; // 消息内存存放
// 在工作线程中 通过Handler发送消息到消息队列中
}.start(); // 开启工作线程(同时启动了Handler)
// 此处用2个工作线程展示
Thread.sleep(6000);
// 通过sendMessage()发送
// a. 定义要发送的消息
msg.what = 2; //消息的标识
msg.obj = "B"; // 消息的存放
// b. 通过Handler发送消息到其绑定的消息队列
}.start();
方式2:匿名内部类的详细应用
在Java中,我们可以使用匿名内部类来实现许多功能。这是因为它们提供了一种简洁的方式来创建类实例。以下是使用匿名内部类的一些常见场景及其实现方法:
1. 实现接口或继承抽象类
当我们需要实现某个接口或继承一个抽象类时,可以使用匿名内部类。例如,如果我们有一个名为`MyInterface`的接口和一个名为`MyAbstractClass`的抽象类,我们可以这样使用匿名内部类:
public class Main {
public static void main(String[] args) {
MyInterface myInterface = new MyInterface() {
public void myMethod() {
System.out.println("Hello, World!");
MyAbstractClass myAbstractClass = new MyAbstractClass() {
public void myAbstractMethod() {
在这个例子中,我们创建了一个实现了`MyInterface`接口和继承了`MyAbstractClass`抽象类的匿名内部类。
2. 作为Lambda表达式的参数类型
我们还可以将匿名内部类作为Lambda表达式的参数类型。例如,如果我们需要创建一个打印字符串的方法,我们可以使用Lambda表达式来实现:
print("Hello, World!");
public static void print(String message) {
System.out.println(message);
这个例子中的`print`方法可以接受一个Lambda表达式作为参数,如下所示:
print("Hello, World!", (String message) -> System.out.println(message));
3. 实现单例模式
在某些情况下,我们可能需要确保一个类只有一个实例。这时,我们可以使用匿名内部类来实现单例模式。例如:
public class Singleton {
private static final Singleton instance = new Singleton() {
public String toString() {
return "Singleton";
private final Object lock = new Object();
private Singleton() {} // 防止外部实例化
public static Singleton getInstance() {
return instance;
在这个例子中,我们使用了一个静态的匿名内部类来创建一个单例对象。由于构造函数是私有的,我们不能直接实例化这个类。相反,我们提供了一个公共的静态方法`getInstance()`来获取这个类的唯一实例。
mTextView = findViewById(R.id.show);
// 在主线程中通过匿名内部类创建Handler类对象
mHandler = new Handler() {
// 在工作线程中通过Handler发送消息到消息队列中
Handler.post()是Android开发中常用的线程间通信方式之一。它可以将一个Runnable对象放入消息队列中,等待主线程处理。当主线程空闲时,会从消息队列中取出Runnable对象并执行它。这样就可以避免在子线程中直接更新UI,而是在主线程中更新UI。
使用Handler.post()可以保证在主线程中更新UI,避免了在子线程中直接更新UI的危险性。同时,使用Handler.post()还可以避免因为线程切换而导致的界面卡顿等问题。
在Android开发中,如果你需要从子线程更新UI,你可以使用Handler和Thread。以下是一个示例:
mHandler = new Handler(Looper.getMainLooper());
// 在工作线程中发送消息到消息队列中 & 指定操作UI内容
// 通过post()发送,需传入1个Runnable对象
mHandler.post(new Runnable() {
// 指定操作UI内容
// 在此处开启另一个工作线程展示
在这个例子中,我们在两个不同的线程中设置`mTextView`的文本。注意我们使用了`Handler`的`Looper.getMainLooper()`来确保这个`Handler`是在主线程中创建的。这是因为只有主线程可以更新UI。
.7 工作原理解析
Handler机制的工作流程主要包括4个步骤:
1. 异步通信准备
2. 消息发送
3. 消息循环
4. 消息处理
具体如下图所示:
+----------------+ +----------------+ +----------------+ +----------------+
| 线程(Thread) |---->| 循环器(Looper) |---->| 处理者(Handler) |---->| 消息队列(MessageQueue) |
(2) 工作流程图
(3) 示意图
(4) 特别注意
线程(Thread)、循环器(Looper)、处理者(Handler)之间的对应关系如下:
1. 一个线程(Thread)只能绑定一个循环器(Looper),但可以有多个处理者(Handler)。
2. 一个循环器(Looper)可绑定多个处理者(Handler)。
3. 一个处理者(Handler)只能绑定一个循环器(Looper)。
3.8 Handler机制的核心类
在源码分析前,先来了解Handler机制中的核心类。Handler机制中有3个重要的类:处理器类(Handler)、消息队列类(MessageQueue)和循环器类(Looper)。
(1) 类说明
- 处理器类(Handler):用于处理消息的类,封装了消息的发送、接收和处理等功能。
- 消息队列类(MessageQueue):用于存储待处理的消息,实现了消息的先进先出策略。
- 循环器类(Looper):用于管理消息队列中的任务,负责将消息分发给相应的处理者。
(2) 类图
+---------------------+ +---------------------+ +---------------------+
| Handler |<---->| MessageQueue | | Looper |
(3) 具体介绍
- 处理器类(Handler):继承自`android.os.Handler`,主要包含以下几个方法:
- `public void handleMessage(Message msg)`:处理接收到的消息。
- `public boolean sendMessage(Message msg)`:向目标处理者发送消息。如果目标处理者不存在,则创建一个新的处理者并发送消息。如果目标处理者存在且当前不在运行状态,则启动目标处理者。如果目标处理者存在且当前正在运行状态,则将消息添加到目标处理者的待处理队列中。如果成功发送消息,则返回true;否则返回false。
/** * 此处以匿名内部类的使用方式为例
// 步骤1:在主线程中通过匿名内部类创建Handler类对象
// 通过复写handleMessage()从而确定更新UI的操作
// 步骤3:在工作线程中通过Handler发送消息到消息队列中
在主线程中,我们通过匿名内部类创建了Handler类的对象。具体的使用如下:
// 需要执行的UI操作
接下来,我们分析Handler的构造方法:
- 作用:初始化Handler对象并绑定线程。注意,Handler需要绑定线程才能使用,绑定后,Handler的消息处理会在绑定的线程中执行。
- 绑定方式:先指定Looper对象,从而绑定了Looper对象所绑定的线程(因为Looper对象本已绑定了对应线程)。即:指定了Handler对象的Looper对象等于绑定到了Looper对象所在的线程。
代码分析:
1. `public Handler() { this(null, false); }`
- `this(null, false)`等价于`Handler(null,false)`,这里没有传入回调函数和异步标志,表示默认为同步处理。
- 首先,通过`mLooper = Looper.myLooper();`获取当前线程的Looper对象。如果当前线程无Looper对象,则抛出异常。这说明若需在子线程中创建Handler对象,则需先创建Looper对象。
- 然后,通过`mQueue = mLooper.mQueue;`获取该Looper对象中保存的消息队列对象(MessageQueue)。至此,保证了handler对象关联上Looper对象中MessageQueue。
在Android中,当创建Handler对象时,它会自动关联当前线程的Looper对象和对应的消息队列对象(MessageQueue)。因此,Handler对象与Looper对象和消息队列对象是一起创建的。
具体来说,当创建Handler对象时,如果没有指定Looper,那么Handler将关联到创建它的线程的Looper。在Android中,主线程(UI线程)默认会创建一个Looper对象,这使得在主线程中使用Handler变得非常简单。然而,在其他线程中,我们需要手动创建Looper对象,并启动一个消息循环,以便处理消息。
至于您提到的问题:当前线程的Looper对象和对应的消息队列对象(MessageQueue)是什么时候创建的呢?根据上述分析可知,它们是在Handler对象创建时一起被创建的。
/** * 源码分析1:Looper.prepare()
* 作用:为当前线程(子线程)创建1个循环器对象(Looper),同时也生成了1个消息队列对象(MessageQueue)
* 注:需在子线程中手动调用该方法
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
// 判断sThreadLocal是否为null,否则抛出异常
// 即 Looper.prepare()方法不能被调用两次 = 1个线程中只能对应1个Looper实例
// 注:sThreadLocal = 1个ThreadLocal对象,用于存储线程的变量
sThreadLocal.set(new Looper(true));
// 若为初次Looper.prepare(),则创建Looper对象 &存放在ThreadLocal变量中
// 注:Looper对象是存放在Thread线程里的
* 分析a:Looper的构造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
// 创建1个消息队列对象(MessageQueue)
// 即 当创建1个Looper实例时,会自动创建一个与之配对的消息队列对象(MessageQueue)
mRun = true;
mThread = Thread.currentThread();
* 源码分析2:Looper.prepareMainLooper()
* 作用:为主线程(UI线程)创建1个循环器对象(Looper),同时也生成了1个消息队列对象(MessageQueue)
* 注:该方法在主线程(UI线程)创建时自动调用,即主线程的Looper对象自动生成,不需手动生成
// 在Android应用进程启动时,会默认创建1个主线程(ActivityThread,也叫UI线程)
// 创建时,会自动调用ActivityThread的1个静态的main()方法 = 应用程序的入口
// main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象
* 源码分析:main()
... // 仅贴出关键代码
Looper.prepareMainLooper();
// 为主线程创建1个Looper对象,同时生成1个消息队列对象(MessageQueue)
// 方法逻辑类似Looper.prepare()
// 注:prepare():为子线程中创建1个Looper对象
ActivityThread thread = new ActivityThread();
总结:
在创建主线程时,会自动调用ActivityThread的静态main()方法。在main()方法内部,会调用Looper.prepareMainLooper()为主线程生成一个Looper对象,同时也会生成其对应的MessageQueue对象。以下是关键点:
1. 主线程的Looper对象会自动生成,无需手动创建;而子线程的Looper对象则需要手动通过Looper.prepare()方法创建。
2. 如果在子线程中不手动创建Looper对象,将无法生成Handler对象。因为Handler的作用是在主线程更新UI,所以Handler实例的创建主要发生在主线程。
3. 在生成Looper和MessageQueue对象后,程序会自动进入消息循环,即执行Looper.loop()方法。这是一个隐式操作。
本节主要分析的是Looper类中的loop()方法。
public static void loop() {
// 1. 获取当前Looper的消息队列
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
// myLooper()作用:返回sThreadLocal存储的Looper实例;若me为null 则抛出异常
// 即loop()执行前必须执行prepare(),从而创建1个Looper实例
final MessageQueue queue = me.mQueue;
// 2. 消息循环(通过for循环)
for (;;) {
// 2.1 从消息队列中取出消息
Message msg = queue.next();
if (msg == null) {
return;
// next():取出消息队列里的消息
// 若取出的消息为空,则线程阻塞
// ->>分析1
// 2.2 派发消息到对应的Handler
msg.target.dispatchMessage(msg);
// 把消息Message派发给消息对象msg的target属性
// target属性实际是1个handler对象
// ->>分析2
// 3. 释放消息占据的资源
msg.recycle();
* analysis1:queue.next()
* Definition: a method in the MessageQueue class (MessageQueue)
* Purpose: dequeue messages from the message queue, i.e. remove the message from the message queue.
Message next() {
int nextPollTimeoutMillis = 0;
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
// nativePollOnce method is in the native layer, if nextPollTimeoutMillis is -1, the message queue is in the waiting state at this time.
nativePollOnce(ptr, nextPollTimeoutMillis);
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// Dequeue messages, that is, remove the message from the message queue according to the order of creating Message objects.
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Returned the message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
mMessages = msg.next;
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
// If there are no more messages in the message queue, set nextPollTimeoutMillis to -1 so that the next loop will be in the waiting state.
nextPollTimeoutMillis = -1;
// Back to analysis place.
* analysis2:dispatchMessage(msg)
* Definition: a method in the handler class (Handler) that returns a boolean value and executes a callback or not based on the passed msg object.
* Purpose: dispatch messages to the corresponding handler instances and perform corresponding operations based on the passed msg object.
消息循环的操作包括消息出队和分发给对应的Handler实例。分发给对应的Handler的过程是通过dispatchMessage(msg)根据出队消息的归属者进行分发,最终回调复写的handleMessage(Message msg)实现消息处理操作。在进行消息分发时,会进行一次发送方式的判断,根据不同的情况调用不同的方法。至此,关于步骤1的源码分析讲解完毕。
步骤2:创建消息对象
首先,我们需要创建一个消息对象。这个对象包含了消息的基本信息,如主题、内容等。在Android中,我们通常使用Message类来表示一个消息对象。以下是一个简单的Message对象创建示例:
Message msg = new Message();
msg.what = 1; // 设置消息标识符
msg.obj = "Hello World"; // 设置消息内容
创建好消息对象后,我们需要将其发送给对应的Handler实例。这通常通过调用Handler的sendMessage()或post()方法来实现。以下是一个简单的发送消息示例:
// 假设我们已经获取到了对应的Handler实例handler
handler.sendMessage(msg); // 使用sendMessage方法发送消息
handler.post(new Runnable() { // 使用post方法发送消息
handler.sendMessage(msg);
至此,我们已经完成了消息循环操作的第一部分:创建消息对象并将其发送给对应的Handler实例。接下来,我们将分析第二部分:如何根据出队的消息归属者进行分发。
** * 具体使用 */ Message msg = Message.obtain(); // 实例化消息对象
* 源码分析:Message.obtain()
* 作用:创建消息对象
* 注:创建Message对象可用关键字new 或 Message.obtain()
public static Message obtain() {
// Message内部维护了1个Message池,用于Message消息对象的复用
// 使用obtain()则是直接从池内获取
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
// 建议:使用obtain()”创建“消息对象,避免每次都使用new重新分配内存
// 若池内无消息对象可复用,则还是用关键字new创建
return new Message();
总结:在工作线程中发送消息到消息队列中的实现方式有多种,如AsyncTask、继承Thread类、实现Runnable。
**
* 具体使用
* 源码分析:mHandler.sendMessage(msg)
* 定义:属于处理器类(Handler)的方法
* 作用:将消息发送到消息队列中(Message -> MessageQueue)
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0); // --> 分析1
* 分析1:sendMessageDelayed(msg, 0)
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); // --> 分析2
* 分析2:sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// 1. 获取对应的消息队列对象(MessageQueue)
MessageQueue queue = mQueue;
// 2. 调用了enqueueMessage方法 -->分析3
return enqueueMessage(queue, msg, uptimeMillis);
* 分析3:enqueueMessage(queue, msg, uptimeMillis)
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 1. 将msg.target赋值为this
// 即:把当前的Handler实例对象作为msg的target属性
msg.target = this;
// 请回忆起上面说的Looper的loop()中消息循环时,会从消息队列中取出每个消息msg,然后执行msg.target.dispatchMessage(msg)去处理消息
// 实际上则是将该消息派发给对应的Handler实例
// 2. 调用消息队列的enqueueMessage()
// 即:Handler发送的消息,最终是保存到消息队列-->分析4
return queue.enqueueMessage(msg, uptimeMillis);
* 分析4:queue.enqueueMessage(msg, uptimeMillis)
* 定义:属于消息队列类(MessageQueue)的方法
* 作用:入队,即将消息根据时间放入到消息队列中(Message -> MessageQueue)
* 采用单链表实现:提高插入消息、删除消息的效率
boolean enqueueMessage(Message msg, long when) {
...// 仅贴出关键代码
synchronize (this) {
msg.when = when;
Message p = mMessages;
boolean needWake;
// a. 若无,则将当前插入的消息作为队头 & 若此时消息队列处于等待状态,则唤醒
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// b. 判断消息队列里有消息,则根据消息(Message)创建的时间插入到队列中
prev = p;
p = p.next;
if (p == null || when < p.when) break;
if (needWake && p.isAsynchronous()) needWake = false;
prev.next = msg;
if (needWake) nativeWake(mPtr);
return true;
Handler发送消息的本质 = 为该消息定义target属性(即本身实例对象) & 将消息入队到绑定线程的消息队列中。具体如下:
至此,关于使用 Handler.sendMessage()的源码解析完毕。
根据操作步骤的源码分析总结:
1. 在主线程中创建Handler实例。
2. 在工作线程中发送消息到消息队列中,并指定操作UI内容。需传入一个Runnable对象。
3. 开启工作线程(同时启动了Handler)。多线程可采用AsyncTask、继承Thread类、实现Runnable。
工作流程总结:
下面,将顺着文章的工作流程再理一次。
方式2:使用 Handler.post()
使用步骤:
private Handler mhandler = new Handler();
/** * 具体使用
* 需传入1个Runnable对象、复写run()从而指定UI操作
* 源码分析:Handler.post(Runnable r)
* 定义:属于处理者类(Handler)中的方法
* 作用:定义UI操作、将Runnable对象封装成消息对象 & 发送到消息队列中(Message ->> MessageQueue)
* 注:
* a. 相比sendMessage(),post()最大的不同在于,更新的UI操作可直接在重写的run()中定义
* b. 实际上,Runnable并无创建新线程,而是发送消息到消息队列中
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
* 分析1:getPostMessage(r)
* 作用:将传入的Runable对象封装成1个消息对象
private static Message getPostMessage(Runnable r) {
// 1. 创建1个消息对象(Message)
Message m = Message.obtain();
// 注:创建Message对象可用关键字new 或 Message.obtain()
// 建议:使用Message.obtain()创建,原因:因为Message内部维护了1个Message池,用于Message的复用,使用obtain()直接从池内获取,从而避免使用new重新分配内存
// 2. 将 Runable对象 赋值给消息对象(message)的callback属性
m.callback = r;
// 3. 返回该消息对象
//回到调用原处
* 分析2:sendMessageDelayed(msg, 0)
* 作用:实际上,从此处开始,则类似方式1 = 将消息入队到消息队列,即最终是调用MessageQueue.enqueueMessage()
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
* 分析3:sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
//1.获取对应的消息队列对象(MessageQueue)
//2.调用了enqueueMessage方法 ->>分析3
* 分析4:enqueueMessage(queue, msg, uptimeMillis)
//1.将msg.target赋值为this
//请回忆起上面说的Looper的loop()中消息循环时,会从消息队列中取出每个消息msg,然后执行msg.target.dispatchMessage(msg)去处理消息。实际上则是将该消息派发给对应的Handler实例。
//2.调用消息队列的enqueueMessage()。即:Handler发送的消息,最终是保存到消息队列。返回值表示是否成功添加到队列。如果返回true,则表示添加成功;如果返回false,则表示添加失败。例如:当系统资源不足时,可能会返回false。所以在使用此方法时需要判断返回值。
在上述分析中,我们可以得出以下结论:
1. 消息对象的创建过程是在内部根据Runnable对象进行封装。
2. 将消息发送到消息队列的逻辑主要体现在方式1中的sendMessage(Message msg)方法。
接下来,我们将重点关注步骤1前的隐式操作2:消息循环,即Looper类中的loop()方法。
/** * 源码分析: Looper.loop()
* 作用:消息循环,即从消息队列中获取消息、分发消息到Handler
* 特别注意:
* a. 主线程的消息循环不允许退出,即无限循环
* b. 子线程的消息循环允许退出:调用消息队列MessageQueue的quit()
// 仅贴出关键代码
// ->>>分析1
* 分析1:dispatchMessage(msg)
* 作用:派发消息到对应的Handler实例 & 根据传入的msg作出对应的操作
public void dispatchMessage(Message msg) {
// 1. 若msg.callback属性不为空,则代表使用了post(Runnable r)发送消息(即此处需讨论的)
// 则执行handleCallback(msg),即回调Runnable对象里复写的run()->>分析2
if (msg.callback != null) {
handleCallback(msg);
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
// 2. 若msg.callback属性为空,则代表使用了sendMessage(Message msg)发送消息(即此处需讨论的)
// 则执行handleMessage(msg),即回调复写的handleMessage(msg)
handleMessage(msg);
/** * 分析2:handleCallback(msg) **/
private static void handleCallback(Message message) {
message.callback.run();
以下是重构后的内容:
### Handler.post()的工作流程总结
使用Handler.post()的工作流程可以分为以下几个步骤:
1. 创建一个Runnable对象,该对象包含了需要在Handler中执行的任务。
2. 将Runnable对象封装成Message对象。
3. 使用Handler的post()方法将Message对象添加到消息队列中。
4. 当有空闲线程时,消息队列中的Message对象会被取出并传递给相应的Handler对象进行处理。
5. Handler对象根据Message对象中的数据调用其内部的回调方法(run()或handleMessage())来执行具体的任务。
6. 任务完成后,Handler会继续监听消息队列,等待新的任务到来。
需要注意的是,Handler.post()与Handler.sendMessage()的区别在于:前者不需要外部创建消息对象,而是通过内部的Runnable对象来封装;同时,回调的消息处理方法也是复写Runnable对象的run()方法。
至此,关于使用Handler.post()的源码解析完毕。下面我们来总结一下操作步骤、源码分析以及工作流程。
方式1:新建Handler子类(内部类)
public static final String TAG = "carson:";
private Handler showhandler;
// 实例化自定义的Handler类对象-->分析1
// 注:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue
showhandler = new FHandler();
// 2. 启动子线程1
Thread.sleep(1000);
msg.what = 1;// 消息标识
msg.obj = "AA";// 消息存放
// b. 传入主线程的Handler &向其MessageQueue发送消息
showhandler.sendMessage(msg);
// 3. 启动子线程2
Thread.sleep(5000);
msg.what = 2;// 消息标识
msg.obj = "BB";// 消息存放
方式2:匿名Handler内部类
// 通过匿名内部类实例化的Handler类对象-->分析1+2(与方式1相同)
showhandler = new Handler() {
Log.d(TAG, "收到线程1的消息");
Log.d(TAG, "收到线程2的消息");
} // 其他代码与方式1相同,不再重复展示。
测试结果
在上述例子中,Handler类由于没有设置为静态类,导致了内存泄露。最终的内存泄露发生在Handler类的外部类:MainActivity类。该Handler在未设置为静态类时,为什么会造成内存泄露呢?
原因讲解
1. 储备知识
主线程的Looper对象的生命周期 = 该应用程序的生命周期
在Java中,非静态内部类 & 匿名内部类都默认持有外部类的引用
2. 泄露原因描述
从上述示例代码可知:
上述的Handler实例的消息队列有2个分别来自线程1、2的消息(分别为延迟1s、6s)
在Handler消息队列还有未处理的消息/正在处理消息时,消息队列中的Message持有Handler实例的引用
由于Handler=非静态内部类/匿名内部类(2种使用方式),故又默认持有外部类的引用(即MainActivity实例),引用关系如下图所示。上述的引用关系会一直保持,直到Handler消息队列中的所有消息被处理完毕。
在Handler消息队列还有未处理的消息/正在处理消息时,此时若需销毁外部类MainActivity,但由于上述引用关系,垃圾回收器(GC)无法回收MainActivity,从而造成内存泄漏。如下图所示:
2.3总结
当Handler消息队列还有未处理的消息/正在处理消息时,存在引用关系:“未被处理/正处理的消息-> Handler实例->外部类”。若出现Handler的生命周期>外部类的生命周期时(即Handler消息队列还有未处理的消息/正在处理消息而外部类需销毁时),将使得外部类无法被垃圾回收器(GC)回收,从而造成内存泄露。
解决方案
从上面可看出,造成内存泄露的原因有两个关键条件:
存在“未被处理/正处理的消息->Handler实例->外部类”的引用关系
Handler的生命周期>外部类的生命周期
即Handler消息队列还有未处理的消息/正在处理消息而外部类需销毁。解决方案的思路是使得上述任1条件不成立即可。
解决方案1:静态内部类+弱引用
静态内部类不默认持有外部类的引用,因此可以避免“未被处理/正处理的消息->Handler实例->外部类”的引用关系。具体方案是将Handler的子类设置为静态内部类,并使用WeakReference弱引用持有Activity实例。
原因:弱引用的对象具有短暂的生命周期。在垃圾回收器线程扫描时,一旦发现了只具有弱引用的对象,无论当前内存空间是否足够,都会回收其内存。
解决代码如下:
private static final int MSG_1 = 1;
private static final int MSG_2 = 2;
public MyHandler(Activity activity) {
super(activity.getLooper());
case MSG_1:
// TODO: handle message 1
case MSG_2:
// TODO: handle message 2
private FHandler showhandler;
// 1. 实例化自定义的Handler类对象-->分析1
// 注意:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue;
// 定义时需传入持有的Activity实例(弱引用)
showhandler = new FHandler(this);
// 分析1:自定义Handler子类
// 设置为:静态内部类
private static class FHandler extends Handler{
// 定义弱引用实例
private WeakReference reference;
// 在构造方法中传入需持有的Activity实例
public FHandler(Activity activity){
//使用WeakReference弱引用持有Activity实例
reference = new WeakReference(activity);
public void handleMessage(Message msg){
switch (msg.what){
case 1:Log.d(TAG,"收到线程1的消息");break;
case 2:Log.d(TAG, "收到线程2的消息");break;
解决方案2:当外部类结束生命周期时,清空Handler内消息队列
原理:
不仅使得“未被处理/正处理的消息->Handler实例->外部类”的引用关系不复存在,同时使得Handler的生命周期(即消息存在的时期)与外部类的生命周期同步。
具体方案:
当外部类(此处以Activity为例)结束生命周期时(此时系统会调用onDestroy()),清除Handler消息队列里的所有消息(调用removeCallbacksAndMessages(null))。
具体代码:
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
// 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期
使用建议:
为了保证Handler中消息队列中的所有消息都能被执行,此处推荐使用解决方案1解决内存泄露问题,即静态内部类 + 弱引用的方式。
三、复合使用
Android多线程实现的复合使用包括:AsyncTask、HandlerThread、IntentService。称为“复用”的主要原因是:这3种方式的本质原理都是Android多线程基础实现(继承Thread类、实现Runnable接口、Handler)的组合实现。下面,我将详细讲解。
1. AsyncTask
1.2 定义
一个Android已封装好的轻量级异步类,属于抽象类,即使用时需实现子类。
public abstract class AsyncTask { ... }
1.3 作用
实现多线程在工作线程中执行任务,如耗时任务;异步通信、消息传递,实现工作线程与主线程(UI线程)之间的通信,即:将工作线程的执行结果传递给主线程,从而在主线程中执行相关的UI操作,从而保证线程安全。
1.4 优点
方便实现异步通信:不需使用“任务线程(如继承Thread类) + Handler”的复杂组合。
节省资源:采用线程池的缓存线程 + 复用线程,避免了频繁创建和销毁线程所带来的系统资源开销。
1.4 类与方法介绍
(1) 类定义
AsyncTask类属于抽象类,即使用时需实现子类。其定义如下:
public abstract class AsyncTask {
...
类中参数为3种泛型类型,整体作用是控制AsyncTask子类执行线程任务时各个阶段的返回类型。具体说明如下:
- a. Params:开始异步任务执行时传入的参数类型,对应execute()中传递的参数。
- b. Progress:异步任务执行过程中,返回下载进度值的类型。
- c. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致。
注:
- a. 使用时并不是所有类型都被使用。
- b. 若无被使用,可用java.lang.Void类型代替。
- c. 若有不同业务,需额外再写1个AsyncTask的子类。
(2) 核心方法
AsyncTask的核心及常用的方法如下:
方法执行顺序如下:onPreExecute() -> doInBackground() -> onProgressUpdate() -> onPostExecute(Result result) -> onCancelled(Result result)
1.5 使用步骤
AsyncTask的使用步骤有3个:
1. 创建AsyncTask子类并根据需求实现核心方法;
2. 创建AsyncTask子类的实例对象(即任务实例);
3. 手动调用execute()从而执行异步线程任务。
/** * 步骤1:创建AsyncTask子类
* a. 继承AsyncTask类
* b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替
* c. 根据需求,在AsyncTask子类内实现核心方法
private class MyTask extends AsyncTask {
....
// 方法1:onPreExecute()
// 作用:执行线程任务前的操作
// 注:根据需求复写
protected void onPreExecute() {
// 方法2:doInBackground()
// 作用:接收输入参数、执行任务中的耗时操作、返回线程任务执行的结果
// 注:必须复写,从而自定义线程任务
protected String doInBackground(String... params) {
...// 自定义的线程任务
// 可调用publishProgress()显示进度,之后将执行onProgressUpdate()
publishProgress(count);
// 方法3:onProgressUpdate()
// 作用:在主线程显示线程任务执行的进度
protected void onProgressUpdate(Integer... progresses) {
// 方法4:onPostExecute()
// 作用:接收线程任务执行结果、将执行结果显示到UI组件
// 注:必须复写,从而自定义UI操作
protected void onPostExecute(String result) {
...// UI操作
// 方法5:onCancelled()
// 作用:将异步任务设置为:取消状态
protected void onCancelled() {
* 步骤2:创建AsyncTask子类的实例对象(即任务实例)
* 注:AsyncTask子类的实例必须在UI线程中创建
MyTask mTask = new MyTask();
* 步骤3:手动调用execute(Params... params) 从而执行异步线程任务
* a. 必须是在UI线程中调用
* b.同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常
* c.执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute()、doInBackground()、onProgressUpdate()、onPostExecute()。不能手动调用上述方法。
mTask.execute();
.6 实例讲解
下面,我将通过一个实例来详细讲解如何使用AsyncTask。
(1) 实例说明
在这个实例中,我们将实现以下功能:
- 点击按钮后,开启线程执行任务;
- 在后台显示加载进度;
- 任务完成后,更新UI组件;
- 如果在加载过程中点击取消按钮,则取消加载。
(2) 具体实现
首先,我们需要创建一个主布局文件`activity_main.xml`,如下所示:
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" android:orientation="vertical" tools:context=".MainActivity"> android:id="@+id/btn_load" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开始加载" />
android:id="@+id/btn_load" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开始加载" />
android:id="@+id/btn_load"
android:text="开始加载" />
接下来,我们在`MainActivity`中实现具体的逻辑:
import android.widget.ProgressBar;
import androidx.fragment.app.FragmentActivity;
import java.util.concurrent.atomic.AtomicBoolean;
private Button btnLoad;
private ProgressBar progressBar;
private TextView textView;
private AtomicBoolean isCancelled = new AtomicBoolean(false);
btnLoad = findViewById(R.id.btn_load);
progressBar = findViewById(R.id.progress_bar);
textView = findViewById(R.id.text_view);
btnLoad.setOnClickListener(new View.OnClickListener() {
if (!isCancelled.get()) {
new LoadTask().execute();
// 如果已经取消,不再执行加载任务
private class LoadTask extends AsyncTask {
super.onPreExecute();
btnLoad.setEnabled(false); // 禁用按钮,防止重复点击
progressBar.setVisibility(View.VISIBLE); // 显示进度条
protected String doInBackground(Void... voids) {
for (int i = 0; i <= 100; i += 10) { // 这里模拟一个耗时的任务,实际应用中可以根据需要替换为其他操作
if (isCancelled.get()) { // 如果被取消,直接返回null结束任务
return null;
} else if (i == 50) { // 当进度达到50%时,显示“正在加载”信息,并暂停1秒继续执行任务,以模拟异步加载过程
textView.setText("正在加载");
try { Thread.sleep(1000); } catch (InterruptedException e) {} finally {}
} else if (i == 100) { // 当进度达到100%时,显示“加载完成”信息,并释放资源,结束任务
textView.setText("加载完成");
break; // 注意这里要加break,避免继续执行后面的代码,导致不必要的等待时间浪费在UI线程上。实际上,当任务执行完毕后,会自动调用onPostExecute方法进行UI更新。所以这里不需要再调用finish()方法。但为了演示效果,我还是加上了break。实际应用中请根据需要调整。
} else if (isCancelled.get()) { // 如果被取消,直接返回null结束任务,与上面的if条件判断保持一致。注意这里要加isCancelled.get(),因为在doInBackground方法中不能直接访问外部变量isCancelled。如果不加这个判断,会导致无限循环。实际上,当任务执行到这一步时,说明已经被取消了。所以这里不需要再检查isCancelled的值。但为了演示效果,我还是加上了这个判断。实际应用中请根据需要调整。
} else { // 其他情况,更新进度条的进度信息,并继续执行任务。注意这里要加Thread.sleep(10),以模拟异步加载过程。实际上,当任务执行到这一步时,应该立即更新进度条的进度信息,然后继续执行后面的代码。但为了演示效果,我还是加上了Thread.sleep(10)。实际应用中请根据需要调整。同时,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题,可以考虑使用Handler或者runOnUiThread方法将更新操作放到主线程中执行。但为了演示效果,我还是保留了原来的写法。实际应用中请根据需要调整。另外,由于Android系统对UI线程的限制,这里的更新操作可能会被阻塞一段时间,导致进度条的更新速度较慢。为了解决这个问题
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" tools:context="com.example.carson_ho.handler_learning.MainActivity"> android:layout_centerInParent="true" android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点我加载"/> android:id="@+id/text" android:layout_below="@+id/button" android:layout_centerInParent="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="还没开始加载!"/> android:layout_below="@+id/text" android:id="@+id/progress_bar" android:layout_width="fill_parent" android:layout_height="wrap_content" android:progress="0" android:max="100" style="?android:attr/progressBarStyleHorizontal"/> android:layout_below="@+id/progress_bar" android:layout_centerInParent="true" android:id="@+id/cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="cancel"/>
android:gravity="center"
tools:context="com.example.carson_ho.handler_learning.MainActivity">
android:layout_centerInParent="true" android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点我加载"/> android:id="@+id/text" android:layout_below="@+id/button" android:layout_centerInParent="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="还没开始加载!"/> android:layout_below="@+id/text" android:id="@+id/progress_bar" android:layout_width="fill_parent" android:layout_height="wrap_content" android:progress="0" android:max="100" style="?android:attr/progressBarStyleHorizontal"/> android:layout_below="@+id/progress_bar" android:layout_centerInParent="true" android:id="@+id/cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="cancel"/>
android:layout_centerInParent="true"
android:text="点我加载"/>
android:id="@+id/text" android:layout_below="@+id/button" android:layout_centerInParent="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="还没开始加载!"/> android:layout_below="@+id/text" android:id="@+id/progress_bar" android:layout_width="fill_parent" android:layout_height="wrap_content" android:progress="0" android:max="100" style="?android:attr/progressBarStyleHorizontal"/> android:layout_below="@+id/progress_bar" android:layout_centerInParent="true" android:id="@+id/cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="cancel"/>
android:id="@+id/text"
android:layout_below="@+id/button"
android:text="还没开始加载!"/>
android:layout_below="@+id/text" android:id="@+id/progress_bar" android:layout_width="fill_parent" android:layout_height="wrap_content" android:progress="0" android:max="100" style="?android:attr/progressBarStyleHorizontal"/> android:layout_below="@+id/progress_bar" android:layout_centerInParent="true" android:id="@+id/cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="cancel"/>
android:layout_below="@+id/text"
android:id="@+id/progress_bar"
android:layout_width="fill_parent"
android:progress="0"
android:max="100"
style="?android:attr/progressBarStyleHorizontal"/>
android:layout_below="@+id/progress_bar" android:layout_centerInParent="true" android:id="@+id/cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="cancel"/>
android:layout_below="@+id/progress_bar"
android:id="@+id/cancel"
android:text="cancel"/>
主逻辑代码文件:MainActivity.java
// 线程变量
private MyTask mTask;
// 主布局中的UI组件
private Button button, cancel;
private TextView text;
* 步骤1:创建AsyncTask子类
text.setText("加载中");
// 执行前显示提示
// 此处通过计算从而模拟“加载进度”的情况
int count = 0;
int length = 1;
while (count < 99) {
count += length;
// 模拟耗时任务
Thread.sleep(50);
progressBar.setProgress(progresses[0]);
text.setText("loading..." + progresses[0] + "%");
// 执行完毕后,则更新UI
text.setText("加载完毕");
text.setText("已取消");
progressBar.setProgress(0);
// 绑定UI组件
cancel = (Button) findViewById(R.id.cancel);
text = (TextView) findViewById(R.id.text);
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
* 注意:AsyncTask子类的实例必须在UI线程中创建。同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常。执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute()、doInBackground()、onProgressUpdate()、onPostExecute()。不能手动调用上述方法。*/
mTask = new MyTask();
/**按钮按按下时,启动AsyncTask*/// TODO: 实现按钮点击事件逻辑,开始执行异步任务*/// TODO: 在异步任务完成后更新TextView的文本*/// TODO: 点击取消按钮时,取消正在执行的任务,onCancelled方法将会被调用*/ button.setOnClickListener(new View.OnClickListener() {// 点击事件监听器@Override public void onClick(View v)// TODO:实现按钮点击事件逻辑*/ });// 点击取消按钮时,取消正在执行的任务,onCancelled方法将会被调用* cancel.setOnClickListener(new View.OnClickListener() {@Override public void onClick(View v){ mTask.cancel(true);}}});//TODO:实现按钮点击事件逻辑*///TODO:在异步任务完成后更新TextView的文本*///TODO:点击取消按钮时,取消正在执行的任务,onCancelled方法将会被调用*/} else if (btnCancel != null && btnCancel.isShown()){//点击取消按钮时,退出当前页面* finish();}} else if (msg != null && msg != ""){//如果有新的消息提示框,显示提示信息* ToastUtil.showToastLong(msg);}} else if (toast != null && toast != ""){//如果有新的Toast提示框,显示提示信息* ToastUtil.showToastShort(toast);}} else if (seekBar != null && seekBar != ""){// 如果进度条有值,设置进度条当前的位置* seekBarPosition = seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" || seekBar == null || seekBar == "null" || seekBar == "" + position;//TODO:设置进度条的当前位置*}} else if (edit != null && edit != ""){//如果编辑框中有值,设置编辑框的内容* editContent = edit == null || edit == "null" || edit == "" || edit == null || edit == "null" || edit == "" || edit == null || edit == "null" || edit == "" + content;//TODO:设置编辑框的内容*}} else if (spinner != null && spinner != ""){// 如果下拉框有选中项,选中对应的选项* int selectedIndex = spinner == null || spinner == "null" || spinner == "" || spinner == null || spinner == "null" || spinner == "" || spinner == null || spinner == "null" || spinner == "" + index;//TODO:选择下拉框的对应选项*}} else if (spinnerColor != null && spinnerColor != ""){//如果颜色下拉框有选中项,设置颜色* color = spinnerColorColorSpinner == null|| spinnerColorColorSpinner==“null”||spinnerColorColorSpinner==“”||spinnerColorColorSpinner==null||spinnerColorColorSpinner==“null”||spinnerColorColorSpinner==“”||spinnerColorColorSpinner==null||spinnerColorColorSpinner==“null”||spinnerColorColorSpinner==“”||spinnerColorColorSpinner==null||spinnerColorColorSpinner==“null”||spinnerColorColorSpinner==“”+colorStr;//TODO:设置颜色*}} else if (imageView != null && imageView != ""){//如果图片预览控件有值,预览指定图片* Glide.with(this).load(imageViewNullImageView==null|| imageViewNullImageView=="null"||imageViewNullImageView==""||imageViewNullImageView==null||imageViewNullImageView=="null"||imageViewNullImageView===""||imageViewNullImageView==null||imageViewNullImageView=="null")/*Glide can be used with RxJava to show loading image when network request is ongoing*//*https://github.com/bumptech/glide/wiki/Load-http-response-into-a-custom-view#using-rxjava-for-streaming-images*//*Glide can be used with RxJava to show loading image when network request is ongoing*//*https://github.com/bumptech/glide/wiki/Load-http-response-into-a-custom-view#using-rxjava-for-streaming-images*//*Glide can be used with RxJava to show loading image when network request is ongoing*//*https://github.com/bumptech/glide/wiki/Load-http-response-into-a-custom-view#using-rxjava-for-streaming-images*//*Glide can be used with RxJava to show loading image when network request is ongoing*//*https://github.com/bumptech/glide/wiki/Load-http-
在使用AsyncTask时,有以下几个注意点:
1. 生命周期问题:AsyncTask不与任何组件绑定生命周期。建议在Activity或Fragment中使用AsyncTask时,最好在Activity或Fragment的onDestroy()调用cancel(boolean);
2. 内存泄漏问题:若AsyncTask被声明为Activity的非静态内部类,当Activity需销毁时,会因AsyncTask保留对Activity的引用而导致Activity无法被回收,最终引起内存泄露。建议AsyncTask应被声明为Activity的静态内部类;
3. 线程任务执行结果丢失问题:当Activity重新创建时(屏幕旋转/Activity被意外销毁时后恢复),之前运行的AsyncTask(非静态的内部类)持有的之前Activity引用已无效,故复写的onPostExecute()将不生效,即无法更新UI操作。建议在Activity恢复时的对应方法重启任务线程。
AsyncTask的实现原理是线程池+Handler。其中:线程池用于线程调度、复用和执行任务;Handler用于异步通信。其内部封装了2个线程池+1个Handler 。
AsyncTask是一个抽象类,用于控制异步任务的执行。它有三个泛型参数:Params、Progress和Result,分别表示开始异步任务执行时传入的参数类型、异步任务执行过程中返回下载进度值的类型以及异步任务执行完成后返回的结果类型。具体说明如下:
1. Params:开始异步任务执行时传入的参数类型,对应execute()中传递的参数。
2. Progress:异步任务执行过程中,返回下载进度值的类型。
3. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致。
注意:
- 使用时并不是所有类型都被使用。
- 若无被使用,可用java.lang.Void类型代替。
- 若有不同业务,需额外再写一个AsyncTask的子类。
AsyncTask的核心方法和常用方法如下:
方法执行顺序如下:
1. 源码分析
本次源码分析将根据AsyncTask的使用步骤讲解。AsyncTask的使用步骤有4个:
2. 创建AsyncTask子类并根据需求实现核心方法
3. 创建AsyncTask子类的实例对象(即任务实例)
4. 手动调用execute()从而执行异步线程任务
具体介绍如下:
下面,我将根据提供的使用步骤进行源码分析并重构代码。请注意,在重构过程中,我会尽量保持原有的代码结构和逻辑顺序不变。
## 步骤一:创建AsyncTask子类
在这一步骤中,我们需要创建一个继承自`AsyncTask`的子类。这个子类将用于执行异步任务。需要注意的是,我们只需要关注在这个过程中需要复写的方法即可,因为后续的源码实现会调用这些方法。
public class MyAsyncTask extends AsyncTask {
// 这里可以添加你需要执行的任务的具体实现代码
## 步骤二:创建AsyncTask子类的实例对象(即任务实例)
现在,我们需要创建上述子类的一个实例对象,这个实例对象将会成为我们的任务实例。在实际应用中,你可以根据需求自定义这个实例对象的属性和方法。
MyAsyncTask task = new MyAsyncTask();
task.execute(); // 开始执行任务
至此,我们已经完成了对源码的分析和重构。接下来,你可以在`MyAsyncTask`子类中添加具体的任务实现代码以及需要复写的方法。
AsyncTask的具体使用如下:
源码分析:AsyncTask的构造函数。
1. 初始化WorkerRunnable变量,它是一个可存储参数的Callable对象。
2. 初始化FutureTask变量,它是一个包装任务的包装类,内部包含Callable,并增加了一些状态标识和操作Callable的接口。
3. 在任务执行线程池中回调:THREAD_POOL_EXECUTOR.execute()。下面会详细讲解。
4. 添加线程的调用标识。
5. 设置线程的优先级。
6. 执行异步操作,即我们使用过程中复写的耗时任务。
7. 若运行异常,设置取消的标志。
8. 把异步操作执行的结果发送到主线程,从而更新UI。下面会详细讲解。
9. 返回结果。
我们创建了一个WorkerRunnable类的实例对象,并复写了call()方法。接下来,我们还创建了一个FutureTask类的实例对象,并重写了done()方法。最后,我们在步骤3中手动调用execute(Params... params)方法。
以下是重构后的代码,并保持了原有的段落结构。
* 源码分析:AsyncTask的execute()
public final AsyncTask execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
* 分析1:executeOnExecutor(sDefaultExecutor, params)
* 参数说明:sDefaultExecutor = 任务队列线程池类(SerialExecutor)的对象
public final AsyncTask executeOnExecutor(Executor exec, Params... params) {
// 1. 判断 AsyncTask 当前的执行状态
// PENDING = 初始化状态
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task: the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task: the task has already been executed" +
"(a task can be executed only once)");
// 2. 将AsyncTask状态设置为RUNNING状态
mStatus = Status.RUNNING;
// 3. 主线程初始化工作
onPreExecute();
// 4. 添加参数到任务中
mWorker.mParams = params;
// 5. 执行任务
// 此处的exec = sDefaultExecutor = 任务队列线程池类(SerialExecutor)的对象
exec.execute(mFuture);
return this;
* 分析2:exec.execute(mFuture)
* 说明:属于任务队列线程池类(SerialExecutor)的方法
private static class SerialExecutor implements Executor {
/** SerialExecutor = 静态内部类 */
/** 即 是所有实例化的AsyncTask对象公有的 */
/** SerialExecutor内部维持了1个双向队列;容量根据元素数量调节 */
final ArrayDeque mTasks = new ArrayDeque<>();
final Object mLock = new Object(); // 用以同步锁住mActive字段和scheduleNext()方法中的操作。保证任务串行执行。
Thread mActiveThread = null; // 该线程用于运行队列中的任务。当该线程空闲时,它会自动去执行队列中的新任务。因此,该变量用来标记哪个线程正在运行任务。如果队列为空,那么这个变量就为null。这也意味着没有正在执行的任务。如果有正在执行的任务,那么这个变量就会被一个不同的线程占用。这样可以保证在任何时候都只有一个正在执行的任务。同时通过synchronized关键字对run方法进行同步控制,确保同一时刻只能有一个线程访问mRunQueue。从而达到线程安全的效果。即多个任务需一个一个加到该队列中;然后执行完队列头部的再执行下一个,以此类推的效果。因此,这个变量必须声明为volatile类型。因为Java虚拟机不保证所有涉及该变量的操作都是原子性的,所以不能将volatile写成volatile=true。只有将其显式地声明为volatile类型才能得到正确的结果。此外,这个变量还应该被声明为volatile类型的final类型,以便JVM生成读写代码时不会创建额外的对象。因此,最终的声明应该是volatile volatile final Thread。这样一来,无论什么时候都能获取到最新的值。同时还要注意的是,如果这个变量被赋值为null,那就意味着没有正在执行的任务了。因此,在调用startNewTask()方法时要特别小心,确保这个条件成立后再执行相关操作。否则会导致死循环的出现。
重构后的内容如下:
在执行任务前,我们使用了一个任务队列线程池类(SerialExecutor)来将任务按顺序放入队列中。这个类可以帮助我们在多线程环境下安全地管理任务的执行顺序。
为了保证AsyncTask中的任务是串行执行的,我们使用了同步锁来修饰execute()方法。这样可以确保在同一时刻只有一个任务在执行,避免了并发执行导致的数据不一致问题。
之后,线程任务执行是由一个专门的任务线程池类(THREAD_POOL_EXECUTOR)来完成的。这个类负责管理和调度线程,以便我们可以在多个线程之间合理地分配任务,提高程序的执行效率。
继续往下分析:
THREAD_POOL_EXECUTORexecute() /**源码分析:THREAD_POOL_EXECUTOR.execute()说明:a. THREAD_POOL_EXECUTOR实际上是一个已配置好的可执行并行任务的线程池。b. 调用THREAD_POOL_EXECUTOR.execute()实际上是调用线程池的execute()去执行具体耗时任务。c. 而该耗时任务则是步骤2中初始化WorkerRunnable实例对象时复写的call()注:下面先看任务执行线程池的线程配置过程,看完后请回到步骤2中的源码分析call() */
// 步骤1:参数设置
//获得当前CPU的核心数
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//设置线程池的核心线程数2-4之间,但是取决于CPU核数
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
//设置线程池的最大线程数为 CPU核数*2+1
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
//设置线程池空闲线程存活时间30s
private static final int KEEP_ALIVE_SECONDS = 30;
//初始化线程工厂
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
//初始化存储任务的队列为LinkedBlockingQueue 最大容量为128
private static final BlockingQueue sPoolWorkQueue = new LinkedBlockingQueue<>(128);
// 步骤2: 根据参数配置执行任务线程池,即 THREAD_POOL_EXECUTOR
public static final Executor THREAD_POOL_EXECUTOR;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
// 设置核心线程池的超时时间也为30s
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
// 请回到步骤2中的源码分析call()
在步骤2中,我们进行了源码分析。现在让我们回到这个部分,继续深入探讨call()方法的实现细节。
首先,我们需要了解call()方法的作用和功能。call()方法是Python中的一个内置函数,它允许我们调用一个函数,并传递一些参数给这个函数。通过使用call()方法,我们可以在不显式地创建函数对象的情况下,直接调用一个函数。
接下来,我们将详细分析call()方法的内部实现。在Python中,call()方法实际上是一个高阶函数,它接受一个可调用对象(如函数、类或实例方法)作为第一个参数,以及一系列其他参数。这些参数将被传递给可调用对象的__call__()方法。
当我们调用一个函数时,Python解释器会自动执行该函数的代码。在这个过程中,解释器会查找函数名对应的字节码指令,并执行这些指令。这些指令包括加载函数对象、获取参数值、执行函数体等操作。
在call()方法的内部实现中,Python解释器会首先检查传入的第一个参数是否是一个可调用对象。如果是,解释器会继续查找该对象的__call__()方法,并将其作为实际要调用的函数。然后,解释器会将剩余的参数传递给__call__()方法。
需要注意的是,如果传入的第一个参数不是一个可调用对象,或者没有提供足够的参数来调用__call__()方法,那么call()方法将会引发TypeError异常。
总结一下,call()方法是Python中用于动态调用函数的一种便捷方式。通过使用call()方法,我们可以在运行时决定要调用哪个函数,并传递相应的参数。这使得编写灵活且可扩展的代码变得更加容易。
AsyncTask类的源码分析主要包含以下几个部分:
1. `AsyncTask()`构造函数:这个方法初始化了一个`WorkerRunnable`对象,该对象存储了需要在后台线程执行的任务参数。同时,它也设置了线程的优先级为`Process.THREAD_PRIORITY_BACKGROUND`,然后调用`doInBackground(mParams)`方法来执行后台任务。如果任务执行成功,结果会被发送到主线程;如果执行失败,会抛出异常。
public AsyncTask() {
mWorker = new WorkerRunnable() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
result = doInBackground(mParams);
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
return result;
...//省略其他代码
2. `postResult(Result result)`方法:这个方法通过Handler将执行结果发送回主线程,并更新UI。这个过程是通过`getHandler().obtainMessage()`获取一个消息,然后使用`sendToTarget()`方法将消息发送到Handler。
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult(this, result));
message.sendToTarget();
3. `InternalHandler()`类:这是一个内部类,继承自`Handler`。它的构造函数需要在主线程中使用。在这个类中,`handleMessage()`方法用于处理从子线程发送过来的消息。当收到的消息是`MESSAGE_POST_RESULT`时,它会调用`finish()`方法结束任务;当收到的消息是`MESSAGE_POST_PROGRESS`时,它会调用`onProgressUpdate()`方法通知主线程更新进度。
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
4. `finish(Result result)`方法:这个方法首先判断任务是否已经被取消。如果任务被取消,就执行`onCancelled(result)`方法;如果任务没有被取消,就执行`onPostExecute(result)`方法更新UI。无论任务是否被取消,最后都会将AsyncTask的状态设置为已完成。
总结
本文介绍了多线程中的AsyncTask的工作原理和源码分析,总结如下:
2. HandlerThread
2.1 简介
HandlerThread是Android中用于处理消息队列的一个类,它继承自Thread类并封装了Handler类。HandlerThread主要用于在子线程中执行耗时操作,然后通过Handler将结果传递回主线程进行UI更新。
2.2 使用步骤
HandlerThread的使用步骤分为5步:
1) 创建HandlerThread实例;
2) 启动HandlerThread;
3) 获取Handler实例;
4) 在Handler实例中执行耗时操作;
5) 通过Handler将结果传递回主线程。
任务线程池类(THREAD_POOL_EXECUTOR)实际上是一个已配置好的可执行并行任务的线程池。调用THREAD_POOL_EXECUTOR.execute()实际上是调用线程池的execute()去执行具体耗时任务。而该耗时任务则是步骤2中初始化WorkerRunnable实例对象时复写的call()内容。
在call()方法里,先调用我们复写的doInBackground(mParams)执行耗时操作,再调用postResult(result),通过InternalHandler类将任务消息传递到主线程。根据消息标识(MESSAGE_POST_RESULT),最终通过finish()调用我们复写的onPostExecute(result),从而实现UI更新操作。
至此,关于AsyncTask的源码分析完毕。
// 步骤1:创建HandlerThread实例对象
// 传入参数 = 线程名字,作用 = 标记该线程
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
// 步骤2:启动线程
mHandlerThread.start();
// 步骤3:创建工作线程Handler & 复写handleMessage()
// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
Handler workHandler = new Handler(mHandlerThread.getLooper()) {
public boolean handleMessage(Message msg) {
// ...//消息处理
// 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
// 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
workHandler.sendMessage(msg);
// 步骤5:结束线程,即停止线程的消息循环
mHandlerThread.quit();
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" tools:context="com.example.carson_ho.handler_learning.MainActivity"> android:id="@+id/text1" android:layout_centerInParent="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="测试结果" /> android:id="@+id/button1" android:layout_centerInParent="true" android:layout_below="@+id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击延迟1s + 显示我爱学习" /> android:id="@+id/button2" android:layout_centerInParent="true" android:layout_below="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击延迟3s + 显示我不爱学习" /> android:id="@+id/button3" android:layout_centerInParent="true" android:layout_below="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="结束线程的消息循环" />
android:id="@+id/text1" android:layout_centerInParent="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="测试结果" /> android:id="@+id/button1" android:layout_centerInParent="true" android:layout_below="@+id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击延迟1s + 显示我爱学习" /> android:id="@+id/button2" android:layout_centerInParent="true" android:layout_below="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击延迟3s + 显示我不爱学习" /> android:id="@+id/button3" android:layout_centerInParent="true" android:layout_below="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="结束线程的消息循环" />
android:id="@+id/text1"
android:text="测试结果" />
android:id="@+id/button1" android:layout_centerInParent="true" android:layout_below="@+id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击延迟1s + 显示我爱学习" /> android:id="@+id/button2" android:layout_centerInParent="true" android:layout_below="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击延迟3s + 显示我不爱学习" /> android:id="@+id/button3" android:layout_centerInParent="true" android:layout_below="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="结束线程的消息循环" />
android:id="@+id/button1"
android:layout_below="@+id/text1"
android:text="点击延迟1s + 显示我爱学习" />
android:id="@+id/button2" android:layout_centerInParent="true" android:layout_below="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击延迟3s + 显示我不爱学习" /> android:id="@+id/button3" android:layout_centerInParent="true" android:layout_below="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="结束线程的消息循环" />
android:id="@+id/button2"
android:layout_below="@+id/button1"
android:text="点击延迟3s + 显示我不爱学习" />
android:id="@+id/button3" android:layout_centerInParent="true" android:layout_below="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="结束线程的消息循环" />
android:id="@+id/button3"
android:layout_below="@+id/button2"
android:text="结束线程的消息循环" />
private ImageView imgView; // 图片显示控件
private Button btn_select, btn_cancel; // 选择和取消按钮
private ProgressDialog progressDialog; // 进度对话框
private String photoPath = ""; // 照片路径
initUI(); // 初始化界面元素
private void initUI() {
imgView = findViewById(R.id.imgView); // 通过 ID 获取图片显示控件
btn_select = findViewById(R.id.btn_select); // 通过 ID 获取选择按钮
btn_cancel = findViewById(R.id.btn_cancel); // 通过 ID 获取取消按钮
btn_select.setOnClickListener(new View.OnClickListener() {
chooseFromGallery(); // 点击选择按钮,调用选择相册的方法
btn_cancel.setOnClickListener(new View.OnClickListener() {
finish(); // 点击取消按钮,关闭当前界面
* 从相册中选择图片并显示在界面上
private void chooseFromGallery() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); // 如果没有读取外部存储的权限,则请求权限
showProgressDialog(); // 如果已经拥有读取外部存储的权限,则显示进度对话框开始选择图片
Intent intent = new Intent();
intent.setType("image/*"); // 设置要选择的文件类型为图片
intent.setAction(Intent.ACTION_GET_CONTENT); // 设置操作类型为从外部获取内容
startActivityForResult(Intent.createChooser(intent, "Select Picture"), SELECT_PICTURE); // 通过 Intent 启动选择器,并传递选择结果回调的方法名和返回值给 onActivityResult() 方法处理结果
* 在选择图片后,将选中的图片保存到本地并显示在界面上
* @param requestCode:请求码,用于区分不同的请求结果
* @param resultCode:结果码,用于判断是否成功选择了图片
* @param data:返回的数据,包含选中的图片信息,如果没有选中任何图片则为 null
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Uri imageUri = null; // 要显示的图片的文件路径,如果没有选中任何图片则为 null
ImageView imageView = findViewById(R.id.imgView); // 通过 ID 获取图片显示控件
Bitmap bitmap; // 要显示的位图对象,用于缓存选中的图片数据以提高性能和减少内存占用量
BitmapShader shader; // 将位图作为渐变背景使用的遮罩层对象,用于生成圆角矩形渐变效果的背景色或边框样式等效果
int width, height; // 要显示的图片的宽度和高度,如果没有选中任何图片则为默认值0和0
RectF rectF; // 要显示图片的圆形区域的位置和大小范围,用于限制图片的位置和大小范围以避免超出边界或出现变形的情况。该参数也可以省略不设置,此时默认显示整个视图区域。如果需要限制其他形状区域的范围,可以根据需求自行定义该参数的相关属性值。
Handler mainHandler, workHandler;
HandlerThread mHandlerThread;
TextView text;
Button button1, button2, button3;
// 显示文本
text = (TextView) findViewById(R.id.text1);
// 创建与主线程关联的Handler
mainHandler = new Handler();
* 步骤1:创建HandlerThread实例对象
* 传入参数 = 线程名字,作用 = 标记该线程
mHandlerThread = new HandlerThread("handlerThread");
* 步骤2:启动线程
* 步骤3:创建工作线程Handler & 复写handleMessage()
* 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
* 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
workHandler = new Handler(mHandlerThread.getLooper()) {
// 消息处理的操作
//设置了两种消息处理操作,通过msg来进行识别
// 消息1
//延时操作
// 通过主线程Handler.post方法进行在主线程的UI更新操作
mainHandler.post(new Runnable() {
text.setText("我爱学习");
// 消息2
text.setText("我不喜欢学习");
default:
* 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
* 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
// 点击Button1
button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
msg.what = 1; //消息的标识
msg.obj = "A"; // 消息的存放
// 点击Button2
button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
// 点击Button3
* 作用:退出消息循环
button3 = (Button) findViewById(R.id.button3);
button3.setOnClickListener(new View.OnClickListener() {
}```
在HandlerThread中,有2个问题需注意的:内存泄漏 & 连续发送消息。在上面的例子中,出现了严重的警告:In Android, Handler classes should be static or leaks might occur. 即造成了严重的内存泄漏。当你连续点击3下时,发现并无按照最新点击的按钮操作显示,而是按顺序的一个个显示出来。原因:使用HandlerThread时只是开了一个工作线程,当你点击了n下后,只是将n个消息发送到消息队列MessageQueue里排队,等候派发消息给Handler再进行对应的操作。内部原理 = Thread类 + Handler类机制,即:通过继承Thread类,快速地创建1个带有Looper对象的新工作线程。通过封装Handler类,快速创建Handler & 与其他线程进行通信。本次源码分析将根据 HandlerThread的使用步骤讲解。HandlerThread的使用步骤有5个:
1. 实现一个Handler接口。
2. 创建一个HandlerThread对象。
3. 启动HandlerThread线程。
4. 在HandlerThread线程中启动子线程并传递参数。
5. 通过Handler发送消息给子线程。
HandlerThread类继承自Thread类,创建HandlerThread类对象需要先创建Thread类对象,然后设置线程优先级。具体步骤如下:
1. 创建HandlerThread类对象:
2. 设置线程优先级:
有两种构造方法,一种是默认优先级(不设置),另一种是自定义设置优先级。
// 默认优先级
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
// 自定义设置优先级
public HandlerThread(String name, int priority) {
mPriority = priority;
3. 启动线程:
使用start()方法启动线程。
这段代码是一个自定义Thread类的实现,具体使用了HandlerThread。在运行时,会先调用父类(Thread类)的start()方法,最终回调HandlerThread的run()方法。下面是代码的重构:
* 源码分析:此处调用的是父类(Thread类)的start(),最终回调HandlerThread的run()
// 1. 获得当前线程的id
mTid = Process.myTid();
// 2. 创建1个Looper对象 & MessageQueue对象
Looper.prepare();
// 3. 通过持有锁机制来获得当前线程的Looper对象
mLooper = Looper.myLooper();
// 发出通知:当前线程已经创建mLooper对象成功
// 此处主要是通知getLooper()中的wait()
notifyAll();
// 此处使用持有锁机制 + notifyAll()是为了保证后面获得Looper对象前就已创建好Looper对象
// 4. 设置当前线程的优先级
Process.setThreadPriority(mPriority);
// 5. 在线程循环前做一些准备工作 ->> 分析1
// 该方法实现体是空的,子类可实现/不实现该方法
onLooperPrepared();
// 6. 进行消息循环,即不断从MessageQueue中取消息 & 派发消息
Looper.loop();
mTid = -1;
在这段代码中,主要进行了以下操作:
1. 获取当前线程的ID。
2. 创建一个Looper对象和MessageQueue对象。
3. 通过持有锁机制来获得当前线程的Looper对象。
4. 设置当前线程的优先级。
5. 在线程循环前做一些准备工作,该方法实现体是空的,子类可实现/不实现该方法。
6. 进行消息循环,即不断从MessageQueue中取消息并派发消息。
在当前工作线程中,我们需要为步骤1创建的线程创建一个Looper对象和MessageQueue对象。首先,通过持有锁机制来获得当前线程的Looper对象。然后,发出通知表示当前线程已经成功创建了mLooper对象。接下来,工作线程将进行消息循环,即不断从MessageQueue中获取消息并派发消息。
为了实现这一过程,我们需要完成以下步骤:
1. 创建工作线程Handler并重写handleMessage()方法(步骤3)。
2. 在步骤1中创建Thread对象时,传入Runnable对象作为参数,该Runnable对象实现了Handler接口,并重写了handleMessage()方法。
3. 在handleMessage()方法中,处理接收到的消息,并根据需要调用其他线程的方法。
4. 在主线程中,调用Looper.prepare()和loop()方法来启动消息循环。
5. 当不再需要发送消息时,调用Looper.quit()和Thread.join()方法来停止消息循环。
以下是重构后的内容,并保持了原有的段落结构:
具体使用:
作用:将Handler关联HandlerThread的Looper对象、实现消息处理操作以及与其他线程进行通信。注:消息处理操作(HandlerMessage())的执行线程等于mHandlerThread所创建的工作线程中执行。
Handler workHandler = new Handler(handlerThread.getLooper()) {
// 消息处理
源码分析:handlerThread.getLooper()
作用:获得当前HandlerThread线程中的Looper对象。
public Looper getLooper() {
// 若线程不是存活的,则直接返回null
if (!isAlive()) {
// 若当前线程存活,再判断线程的成员变量mLooper是否为null
// 直到线程创建完Looper对象后才能获得Looper对象,若Looper对象未创建成功,则阻塞
while (isAlive() && mLooper == null) {
// 此处会调用wait方法去等待
wait();
// 上述步骤run()使用持有锁机制 + notifyAll()获得Looper对象后
// 则通知当前线程的wait()结束等待并跳出循环
// 最终getLooper()返回的是在run()中创建的mLooper对象
return mLooper;
在获取HandlerThread工作线程的Looper对象时,需要解决同步问题。只有在线程创建成功并且对应的Looper对象也创建成功后,才能获得Looper的值,并将创建的Handler与工作线程的Looper对象绑定。解决方案是使用同步锁、wait()和notifyAll()来保证同步。具体步骤如下:
1. 在run()方法中成功创建Looper对象后,立即调用notifyAll()通知getLooper()中的wait()结束等待并返回run()中成功创建的Looper对象,使得Handler与该Looper对象绑定。
2. 使用工作线程Handler向工作线程的消息队列发送消息。具体操作包括定义要发送的消息、通过Handler发送消息到其绑定的消息队列等。
3. 结束线程,即停止线程的消息循环。
代码示例:
// 确保同步的解决方案:同步锁、wait() 和 notifyAll()
public class MyHandlerThread extends Thread {
private Looper mLooper;
private Handler mWorkHandler = new Handler(mLooper);
// a. 创建Looper对象
mLooper = Looper.prepare();
// b. 通过notifyAll()通知wait()结束等待
} catch (Exception e) {
if (mLooper != null) {
mLooper.quit();
// 具体使用:在工作线程中,当消息循环时取出对应消息并在工作线程执行相关操作
// 注意:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
public void sendMessageToWorkerThread(String message) {
msg.what = 2; // 消息的标识
// c. 通过Handler发送消息到其绑定的消息队列
mWorkHandler.sendMessage(msg);
源码分析:
1. `mHandlerThread.quit()` 方法属于 `HandlerThread` 类,该类有两种让当前线程退出消息循环的方法:`quit()` 和 `quitSafely()`。
2. 方式1:`quit()` 的特点是效率高,但线程不安全。方法内部首先获取 `Looper`,如果 `Looper` 不为空,则调用 `looper.quit()` 退出循环并返回 `true`,否则返回 `false`。
3. 方式2:`quitSafely()` 的特点是效率低,但线程安全。方法内部同样首先获取 `Looper`,如果 `Looper` 不为空,则调用 `looper.quitSafely()` 退出循环并返回 `true`,否则返回 `false`。
4. 注:上述两种方法最终都会调用 `MessageQueue.quit(boolean safe)`,我们可以进行进一步分析。
分析1:`MessageQueue.quit(boolean safe)` 方法根据参数 `safe` 的值选择不同的方式移除所有回调。
5. 若 `safe` 为 `true`,则调用 `removeAllFutureMessagesLocked()` 方法,该方法会遍历消息链表并移除所有回调,然后将消息链表重置为空。这种方式不安全,因为它可能会在消息处理过程中被调用。
6. 若 `safe` 为 `false`,则调用 `removeAllMessagesLocked()` 方法,该方法会遍历消息链表并移除所有回调,然后将消息链表重置为空。这种方式是安全的,因为它会在消息处理完毕后再执行。
7. 最后,调用 `nativeWake(mPtr)` 唤醒处理器线程。
分析2:`removeAllMessagesLocked()` 方法的作用是遍历消息链表、移除所有信息的回调并重置为空。原理是通过循环遍历消息链表,将每个消息的下一个指针指向前一个消息,从而实现链表的删除。
8. 分析3:`removeAllFutureMessagesLocked()` 方法的作用是在判断当前消息队列是否正在处理消息后,选择相应的方式移除消息和退出循环。结论:退出方法的安全与否(`quitSafe()` 或 `quit()`),在于该方法移除消息、退出循环时是否在意当前队列是否正在处理消息。
本文全面分析了多线程中HandlerThread的源码,总结如下:
3. IntentService
3.1 定义
Android里的一个封装类,继承四大组件之一的Service。
3.2 作用
处理异步请求并实现多线程。
3.3 使用场景
线程任务需按顺序、在后台执行。最常见的场景是离线下载。不符合多个数据同时请求的场景是所有的任务都在同一个Thread looper里执行。
3.4 使用步骤
步骤1:定义IntentService的子类,需复写onHandleIntent()方法。
步骤2:在Manifest.xml中注册服务。
步骤3:在Activity中开启Service服务。
3.5 实例讲解
步骤1:定义IntentService的子类,传入线程名称、复写onHandleIntent()方法。
public class MyIntentService extends IntentService {
* 在构造函数中传入线程名字
**/
public MyIntentService() {
// 调用父类的构造函数,参数 = 工作线程的名字
super("myIntentService");
* 复写onHandleIntent()方法
* 根据 Intent实现耗时任务操作
protected void onHandleIntent(Intent intent) {
// 根据 Intent的不同,进行不同的事务处理
String taskName = intent.getExtras().getString("taskName");
switch (taskName) {
case "task1":
Log.i("myIntentService", "do task1");
case "task2":
Log.i("myIntentService", "do task2");
* 默认实现 = 将请求的Intent添加到工作队列里
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("myIntentService", "onStartCommand");
return super.onStartCommand(intent, flags, startId);
在Android开发中,我们通常会创建一个Service以便在我们的应用后台执行一些任务。以下是如何在Manifest.xml中注册服务以及如何在Activity中开启服务的步骤:
1. 在Manifest.xml中注册服务:
在上述代码中,我们定义了一个名为“.myIntentService”的服务,并为其设置了一个动作过滤器,该动作过滤器指向了“cn.scu.finch”。
2. 在Activity中开启Service服务:
首先,我们需要获取到这个服务实例。这通常通过使用上下文的getApplicationContext()方法和调用bindService()方法来完成。然后,我们可以调用startService()方法来启动服务。当不再需要服务时,我们可以调用unbindService()方法来解除服务的绑定。
以下是相关的Java代码:
// 获取到上下文和应用程序的实例
Context context = getApplicationContext();
MyIntentService myIntentService = new MyIntentService();
// 绑定服务
boolean result = context.bindService(new Intent(context, MyIntentService.class), serviceConnection, Context.BIND_AUTO_CREATE);
if (!result) {
// 如果无法绑定,那么可能是由于没有启动该服务或者用户拒绝了服务请求等原因,需要进行相应的处理
// 成功绑定后,可以在这里启动服务或者进行其他操作
请注意,上述代码中的MyIntentService应替换为你实际要使用的服务类名。同时,你需要在你的Activity中实现ServiceConnection接口,这样才能在服务被系统销毁时释放资源。
您好,IntentService是继承于Service并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作。与普通Service相比,IntentService适用于需要在后台执行短时间的、一次性的任务,而不适合长时间运行或需要与UI交互的后台任务。IntentService处理完所有请求后自动停止,而普通Service需要手动调用stopSelf()或stopService()来停止服务 。
.7 工作原理
IntentService的工作原理和源码工作流程如下:
3.7.1 流程示意图
IntentService的工作流程如下:
1. 当启动IntentService时,会创建一个新的线程。
2. 在onCreate()方法中,初始化工作队列。
3. 当接收到客户端发送的Intent时,会调用onHandleIntent()方法处理该Intent。
4. onHandleIntent()方法会将任务添加到工作队列中,并在适当的时候执行。
5. 当所有任务完成后,IntentService会自动结束。
3.7.2 特别注意
若启动IntentService多次,那么每个耗时操作将以队列的方式在IntentService的onHandleIntent回调方法中依次执行,执行完自动结束。
接下来,我们将通过源码分析解决以下问题:
3.8 源码分析
问题1:IntentService如何单独开启1个新的工作线程?
主要分析内容 = IntentService源码中的onCreate()方法。
public void onCreate() {
super.onCreate();
// 1. 通过实例化andlerThread新建线程 & 启动;故 使用IntentService时,不需额外新建线程
// HandlerThread继承自Thread,内部封装了 Looper
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
// 2. 获得工作线程的 Looper & 维护自己的工作队列
mServiceLooper = thread.getLooper();
// 3. 新建mServiceHandler & 绑定上述获得Looper
// 新建的Handler 属于工作线程 ->> 分析1
mServiceHandler = new ServiceHandler(mServiceLooper);
* 分析1:ServiceHandler源码分析
private final class ServiceHandler extends Handler {
// 构造函数
public ServiceHandler(Looper looper) {
super(looper);
// IntentService的handleMessage()把接收的消息交给onHandleIntent()处理
// onHandleIntent方法在工作线程中执行
// onHandleIntent() = 抽象方法,使用时需重写 ->> 分析2
onHandleIntent((Intent) msg.obj);
// 执行完调用 stopSelf() 结束服务
stopSelf(msg.arg1);
* 分析2: onHandleIntent()源码分析
* onHandleIntent() = 抽象方法,使用时需重写
@WorkerThread
protected abstract void onHandleIntent(Intent intent);
IntentService是Android中的一种服务,它可以在一个单独的工作线程中执行任务。通过HandlerThread单独开启一个工作线程,创建一个内部Handler:ServiceHandler,然后将ServiceHandler绑定到IntentService上。当接收到启动服务的请求时,会调用onStartCommand()方法,并将服务intent传递给ServiceHandler。接着,将Intent插入到工作队列中,并逐个发送给onHandleIntent()方法。在onHandleIntent()方法中,依次处理所有Intent对象所对应的任务。
需要注意的是,工作任务队列是顺序执行的。如果一个任务正在IntentService中执行,此时再发送一个新的任务请求,那么这个新的任务会一直等待直到前面一个任务执行完毕后才开始执行。这是因为onCreate()只会被调用一次,只会创建一个工作线程;当多次调用startService(Intent)时(即onStartCommand()也会调用多次),实际上不会创建新的工作线程,只是把消息加入消息队列中并等待执行。所以,多次启动IntentService会按顺序执行事件。如果服务停止了,则会清除消息队列中的消息,后续的事件不执行。
另外,不建议使用bindService()启动IntentService。因为在IntentService中,onBind()默认返回null。采用bindService()启动IntentService的生命周期如下:
onCreate() 是 Android Service 生命周期的第一个回调方法。当 Service 实例被创建时,系统会调用此方法。在这个方法中,我们可以进行一些初始化操作,例如绑定到一个 Activity 或者设置监听器等。
// 初始化操作
onBind() 是 Service 生命周期的第二个回调方法。当 Service 需要与客户端(通常是 Activity)进行通信时,系统会调用此方法。在这个方法中,我们可以将 Service 实例绑定到一个 Intent,以便其他组件可以调用它。
public IBinder onBind(Intent intent) {
return mBinder;
onUnbind() 是 Service 生命周期的第三个回调方法。当客户端(通常是 Activity)解除绑定时,系统会调用此方法。在这个方法中,我们可以进行一些清理操作,例如取消正在执行的任务等。
public boolean onUnbind(Intent intent) {
// 清理操作
onDestroy() 是 Service 生命周期的第四个回调方法。当 Service 不再需要时,系统会调用此方法。在这个方法中,我们可以进行一些资源回收操作,例如关闭线程池等。
public void onDestroy() {
// 资源回收操作
如果使用 IntentService,那么 onStart()、onStop()、onBind()、onUnbind()、onDestroy() 这些生命周期方法将不会被调用。这是因为 IntentService 是在一个单独的进程中运行的,而不是在应用程序的主线程中运行的。因此,无法实现多线程的操作。在这种情况下,你应该使用 Service 而不是 IntentService。