序言:线程和进程
1、进程
进程是指运行中的程序,比如我们使用QQ,就启动该进程分配内存空间。
进程是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。
2、线程
线程是由进程创建的,是进程的一个实体。
一个进程可以拥有多个线程,一个线程还可以创建它的子线程。
3、其他概念
单线程:同时允许执行一个线程
多线程:同一个时刻,可以执行多个线程。比如:QQ可以打开多个聊天窗口;一个迅雷进程,可以同时下载多个文件。
并发:同一个时刻,多个任务交替执行,造成“貌似同时”的错觉,简单的说,单核CPU实现的多任务就是并发。
并行:同一个时刻,多个任务同时执行。多核CPU可以同时执行。
也可能出现:“并行和并发”同时存在的情况。
进程(Process)是一个程序的一个运行实例,“一个程序运行可以做很多事”;而线程则是CPU调度的基本单位,可以理解为“程序执行过程中做的某一件事”。
我们知道,一个应用程序的主入口一般都是main函数,这基本上成了程序开发的一种规范——它是“一切事物的起源”。
而main()函数的工作也是前篇一律:
- 初始化 (比如创建对象、申请资源等)
- 进入死循环 (在循环中处理各种事件,直到进程退出)
这种模型是“以事件为驱动”的软件系统的必然结果,因此几乎存在于任何操作系统和编程语言中。
对于Android应用开发者而言,通常面对的都是Activity、Service等组件,并不需要特别关心进程是什么。
因而产生了一些误区,如部分开发者认为系统四大组件就是进程的载体。
正确的观点是:
(1)四大组件并不是程序(进程)的全部,只能算作进程的组成部分,只是程序的零件。
(2)应用程序启动后,将创建ActivityThread主线程。
(3)一个Activity应用启动后至少会有3个线程:主线程 + 两个Binder线程。
Android多线程
实现方式有很多种:
1、基础使用
- 继承Thread类
- 实现Runnable接口
- Handler
2、复合使用
- AsyncTask
- HandlerThread
- IntentService
3、高级使用
- 线程池(ThreadPool)
一、基础使用
Android多线程实现的基础使用包括:
- 继承Thread类
- 实现Runnable接口
- Handler
1、继承Thread类
应用场景:创建两个线程,实现两个不同的耗时任务。
应用说明:实现2个窗口同时售卖火车票,每个窗口各卖100张,但卖票速度不同:窗口1是1s/张,窗口2是3s/张。
具体实现:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:orientation="vertical"> <Button android:id="@+id/btnTicketByThread" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="两个窗口各售票100张" /> </LinearLayout>
MainActivity.java
import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final String TAG = "MainActivity"; private Button btnTicketByThread; //步骤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; } //在run方法里复写需要进行的操作:卖票速度是1s/张 @Override public void run() { while (ticket > 0) { ticket--; Log.d(TAG, 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; } //在run方法里复写需要进行的操作:卖票速度是3s/张 @Override public void run() { while (ticket > 0) { ticket--; Log.d(TAG, name + "卖掉了1张票,剩余票数为:" + ticket); try { Thread.sleep(3000); //卖票速度是1s一张 } catch (InterruptedException e) { e.printStackTrace(); } } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnTicketByThread = findViewById(R.id.btnTicketByThread); btnTicketByThread.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.btnTicketByThread: //步骤2:创建线程类的实例 //创建两个线程,模拟两个窗口卖票 MyThread1 mt1 = new MyThread1("窗口1"); MyThread2 mt2 = new MyThread2("窗口2"); //步骤3:调用start()方法开启线程 //启动两个线程,也即是窗口,开始卖票 mt1.start(); mt2.start(); break; } } }
运行结果:
由于卖票速度不同,所以窗口1卖3张时,窗口2才卖1张。
窗口1卖完了,后面只剩窗口2在卖票。
与“实现Runnable接口”对比
在Java中,继承Thread类和实现Runnable接口是实现多线程最常用的2种方法。
对比下这两种方法:
2、实现Runnable接口
特别注意:
- Java中真正能创建新线程的只有Thread类对象。
- 通过实现Runnable的方式,最终还是通过Thread类对象来创建线程。
- 所以对于实现了Runnable接口的类,称为线程辅助类;Thread类才是真正的线程类。
应用场景:创建两个线程,实现一个耗时任务
应用说明:实现2个窗口同时卖火车票;两个窗口一共卖100张,卖票速度均为1s/张。
具体实现:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:orientation="vertical"> <Button android:id="@+id/btnTicketByThread" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="两个窗口各售票100张" /> <Button android:id="@+id/btnTicketByRunnable" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="两个窗口共售票100张" /> </LinearLayout>
MainActivity.java
import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final String TAG = "MainActivity"; private Button btnTicketByThread, btnTicketByRunnable; //步骤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; } //在run方法里复写需要进行的操作:卖票速度是1s/张 @Override public void run() { while (ticket > 0) { ticket--; Log.d(TAG, 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; } //在run方法里复写需要进行的操作:卖票速度是3s/张 @Override public void run() { while (ticket > 0) { ticket--; Log.d(TAG, name + "卖掉了1张票,剩余票数为:" + ticket); try { Thread.sleep(3000); //卖票速度是1s一张 } catch (InterruptedException e) { e.printStackTrace(); } } } } //步骤1:创建线程类,实现Runnable接口 private class MyThread3 implements Runnable { private int ticket = 100; //两个窗口一共要卖100张票 //在run方法里复写需要进行的操作:卖票速度1s/张 @Override public void run() { while (ticket > 0) { ticket--; Log.d(TAG, Thread.currentThread().getName() + "卖掉了1张票,剩余票数为:" + ticket); try { Thread.sleep(1000); //卖票速度是1s一张 } catch (InterruptedException e) { e.printStackTrace(); } } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnTicketByThread = findViewById(R.id.btnTicketByThread); btnTicketByThread.setOnClickListener(this); btnTicketByRunnable = findViewById(R.id.btnTicketByRunnable); btnTicketByRunnable.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.btnTicketByThread: //步骤2:创建线程类的实例 //创建两个线程,模拟两个窗口卖票 MyThread1 mt1 = new MyThread1("窗口1"); MyThread2 mt2 = new MyThread2("窗口2"); //步骤3:调用start()方法开启线程 //启动两个线程,也即是窗口,开始卖票 mt1.start(); mt2.start(); break; case R.id.btnTicketByRunnable: //步骤2:创建线程类的实例 //因为是两个窗口共卖100张票,即共用资源 //所以只实例化一个实现了Runnable接口的类 MyThread3 mt3 = new MyThread3(); //因为要创建两个线程,模拟两个窗口卖票 Thread mt31 = new Thread(mt3, "窗口1"); Thread mt32 = new Thread(mt3, "窗口2"); //步骤3:调用start()方法开启线程 //启动两个线程,也即是窗口,开始卖票 mt31.start(); mt32.start(); break; } } }
运行结果:
观察运行结果,会发现,有时候会出现售票错误,窗口1和2同时买出去同一张票或者卖出去票的顺序不对。
这个问题是因为不同线程之间抢占cpu资源,线程醒过来的时间不同,造成了访问共享数据ticket(剩余票数)错误。
解决这个问题,我们需要对共享数据加锁,将共享数据的代码块锁起来,使在任意时刻时,只能有一个线程来修改数据,保证访问共享数据的操作原子性。
如何加锁呢?最简单的就是使用synchronized关键字。
synchronized (共享数据对象) { }
由于原先定义的ticket(剩余票数)是int型,不是引用类型,不是对象,所以我们要将ticket(剩余票数)的数据类型改为Integer类型。
import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final String TAG = "MainActivity"; private Button btnTicketByThread, btnTicketByRunnable; //步骤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; } //在run方法里复写需要进行的操作:卖票速度是1s/张 @Override public void run() { while (ticket > 0) { ticket--; Log.d(TAG, 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; } //在run方法里复写需要进行的操作:卖票速度是3s/张 @Override public void run() { while (ticket > 0) { ticket--; Log.d(TAG, name + "卖掉了1张票,剩余票数为:" + ticket); try { Thread.sleep(3000); //卖票速度是1s一张 } catch (InterruptedException e) { e.printStackTrace(); } } } } //步骤1:创建线程类,实现Runnable接口 private class MyThread3 implements Runnable { // private int ticket = 100; //两个窗口一共要卖100张票 private Integer ticket = 100; //两个窗口一共要卖100张票 //在run方法里复写需要进行的操作:卖票速度1s/张 @Override public void run() { while (ticket > 0) { synchronized (ticket) { ticket--; Log.d(TAG, Thread.currentThread().getName() + "卖掉了1张票,剩余票数为:" + ticket); try { Thread.sleep(1000); //卖票速度是1s一张 } catch (InterruptedException e) { e.printStackTrace(); } } } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnTicketByThread = findViewById(R.id.btnTicketByThread); btnTicketByThread.setOnClickListener(this); btnTicketByRunnable = findViewById(R.id.btnTicketByRunnable); btnTicketByRunnable.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.btnTicketByThread: //步骤2:创建线程类的实例 //创建两个线程,模拟两个窗口卖票 MyThread1 mt1 = new MyThread1("窗口1"); MyThread2 mt2 = new MyThread2("窗口2"); //步骤3:调用start()方法开启线程 //启动两个线程,也即是窗口,开始卖票 mt1.start(); mt2.start(); break; case R.id.btnTicketByRunnable: //步骤2:创建线程类的实例 //因为是两个窗口共卖100张票,即共用资源 //所以只实例化一个实现了Runnable接口的类 MyThread3 mt3 = new MyThread3(); //因为要创建两个线程,模拟两个窗口卖票 Thread mt31 = new Thread(mt3, "窗口1"); Thread mt32 = new Thread(mt3, "窗口2"); //步骤3:调用start()方法开启线程 //启动两个线程,也即是窗口,开始卖票 mt31.start(); mt32.start(); break; } } }
运行结果:
我们可以多运行几次,观察输出。这样就可以正确实现了两个窗口一起卖100张票的目的。
3、Handler
(1)作用
在多线程的应用场景中,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理。
(2)意义
问:为什么要用Handler消息传递机制?
答:多个线程并发更新UI的同时保证线程安全。
(3)相关概念
关于 Handler 异步通信机制中的相关概念如下:
在下面的讲解中,我将直接使用英文名讲解,即 Handler、Message、Message Queue、Looper。
(4)使用方式
Handler的使用方式因发送消息到消息队列的方式不同而不同。
共分为2种:
- Handler.sendMessage()
- Handler.post()
方式1:Handler.sendMessage()
在该使用方式中,又分为2种:新建Handler子类(内部类)、匿名 Handler子类,但本质相同,即继承了Handler类 和 创建了子类 。
import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.os.Handler; import android.os.Message; public class HandlerActivity extends AppCompatActivity { /** * 方式1:新建Handler子类(内部类) */ // 步骤1:在主线程中,自定义Handler子类(继承Handler类),并重写handleMessage()方法 // class MyHandler extends Handler { // // 通过重写handlerMessage()从而确定更新UI的操作 // @Override // public void handleMessage(@NonNull Message msg) { // super.handleMessage(msg); // switch (msg.what) { // case 1: // // 需执行的UI操作 // break; // default: // // 需执行的UI操作 // break; // } // } // } // 在主线程中,创建Handler实例 // private Handler myHandler = new MyHandler(); /** * 方式2:匿名内部类 */ // 步骤1:在主线程中,通过匿名内部类,创建Handler类对象 private Handler myHandler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); switch (msg.what) { case 1: // 需执行的UI操作 break; default: // 需执行的UI操作 break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler); new Thread(new Runnable() { // 多线程可采用AsyncTask、继承Thread类、实现Runnable @Override public void run() { // 步骤2:创建所需的消息对象 Message msg = Message.obtain(); // 实例化消息对象 msg.what = 1; // 消息标识 msg.obj = "OK"; // 消息内容存放 // 步骤3:在子线程中,通过Handler发送消息到消息队列中 myHandler.sendMessage(msg); } }).start(); // 步骤4:启动子线程(同时启动了Handler) } }
方式2:Handler.post()
import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.widget.Button; import android.widget.TextView; public class HandlerActivity extends AppCompatActivity { // 步骤1:在主线程中创建Handler实例 private Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler); // 步骤2:在子线程中,发送消息到消息队列中,并指定操作UI内容 // 需传入1个Runnable对象 new Thread() { @Override public void run() { handler.post(new Runnable() { @Override public void run() { // 需执行的UI操作 } }); } }.start(); } }
(5)实例讲解
注:
- 由于Handler的作用 = 将工作线程需操作UI的消息 传递 到主线程,使得主线程可根据工作线程的需求
- 更新UI,从而避免线程操作不安全的问题 故下文的实例 = 1个简单 “更新UI操作” 的案例
- 主布局文件相同 = 1个用于展示的TextView,具体如下:
activity_handler.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <Button android:id="@+id/btnSendMessage" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Handler.sendMessage()" /> <Button android:id="@+id/btnPost" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Handler.post()" /> <TextView android:id="@+id/tvMsg" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="消息" /> </LinearLayout>
① 使用 Handler.sendMessage()
方式1:新建Handler子类(内部类)具体使用
HandlerActivity.java
import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; public class HandlerActivity extends AppCompatActivity implements View.OnClickListener { private static final String TAG = "HandlerActivity"; private Button btnSendMessage, btnPost; private TextView tvMsg; /** * 方式1:新建Handler子类(内部类) */ // 步骤1:在主线程中,自定义Handler子类(继承Handler类),并重写handleMessage()方法 class MyHandler extends Handler { // 通过重写handlerMessage()从而确定更新UI的操作 @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); // 根据不同线程发送过来的消息,执行不同的UI操作 // 根据 Message对象的what属性 标识不同的消息 switch (msg.what) { case 1: Log.d(TAG, "handleMessage: " + msg.obj); tvMsg.setText("执行了线程1的UI操作"); break; case 2: Log.d(TAG, "handleMessage: " + msg.obj); tvMsg.setText("执行了线程2的UI操作"); break; } } } // 在主线程中,创建Handler实例 private Handler myHandler = new MyHandler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler); tvMsg = findViewById(R.id.tvMsg); btnSendMessage = findViewById(R.id.btnSendMessage); btnSendMessage.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.btnSendMessage: Log.d(TAG, "onClick: btnSendMessage"); // 创建第1个子线程 new Thread() { @Override public void run() { try { Thread.sleep(3000); // 间隔3s } catch (InterruptedException e) { e.printStackTrace(); } // 步骤2:创建所需的消息对象 Message msg = Message.obtain(); msg.what = 1; // 消息标识 msg.obj = "A"; // 消息内存存放 // 步骤3:在工作线程中 通过Handler发送消息到消息队列中 myHandler.sendMessage(msg); } }.start(); // 步骤4:启动子线程(同时启动了Handler) // 此处再创建第2个子线程 new Thread() { @Override public void run() { try { Thread.sleep(6000); // 间隔6s } catch (InterruptedException e) { e.printStackTrace(); } Message msg = Message.obtain(); msg.what = 2; // 消息标识 msg.obj = "B"; // 消息内存存放 myHandler.sendMessage(msg); } }.start(); // 启动子线程(同时启动了Handler) break; } } }
方式2:匿名内部类具体使用
import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; public class HandlerActivity extends AppCompatActivity implements View.OnClickListener { private static final String TAG = "HandlerActivity"; private Button btnSendMessage, btnPost; private TextView tvMsg; /** * 方式2:匿名内部类 */ // 步骤1:在主线程中,通过匿名内部类,创建Handler类对象 private Handler myHandler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); // 根据不同线程发送过来的消息,执行不同的UI操作 // 根据 Message对象的what属性 标识不同的消息 switch (msg.what) { case 1: Log.d(TAG, "handleMessage: " + msg.obj); tvMsg.setText("执行了线程1的UI操作"); break; case 2: Log.d(TAG, "handleMessage: " + msg.obj); tvMsg.setText("执行了线程2的UI操作"); break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler); tvMsg = findViewById(R.id.tvMsg); btnSendMessage = findViewById(R.id.btnSendMessage); btnSendMessage.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.btnSendMessage: Log.d(TAG, "onClick: btnSendMessage"); // 创建第1个子线程 new Thread() { @Override public void run() { try { Thread.sleep(3000); // 间隔3s } catch (InterruptedException e) { e.printStackTrace(); } // 步骤2:创建所需的消息对象 Message msg = Message.obtain(); msg.what = 1; // 消息标识 msg.obj = "A"; // 消息内存存放 // 步骤3:在工作线程中 通过Handler发送消息到消息队列中 myHandler.sendMessage(msg); } }.start(); // 步骤4:启动子线程(同时启动了Handler) // 此处再创建第2个子线程 new Thread() { @Override public void run() { try { Thread.sleep(6000); // 间隔6s } catch (InterruptedException e) { e.printStackTrace(); } Message msg = Message.obtain(); msg.what = 2; // 消息标识 msg.obj = "B"; // 消息内存存放 myHandler.sendMessage(msg); } }.start(); // 启动子线程(同时启动了Handler) break; } } }
② 使用 Handler.post()
import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; public class HandlerActivity extends AppCompatActivity implements View.OnClickListener { private static final String TAG = "HandlerActivity"; private Button btnSendMessage, btnPost; private TextView tvMsg; // 步骤1:在主线程中创建Handler实例 private Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler); tvMsg = findViewById(R.id.tvMsg); btnSendMessage = findViewById(R.id.btnSendMessage); btnSendMessage.setOnClickListener(this); btnPost = findViewById(R.id.btnPost); btnPost.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.btnSendMessage: // ...... break; case R.id.btnPost: Log.d(TAG, "onClick: btnPost"); // 步骤2:在子线程中,发送消息到消息队列中,并指定操作UI内容 // 需传入1个Runnable对象 new Thread() { @Override public void run() { try { Thread.sleep(3000); // 间隔3s } catch (InterruptedException e) { e.printStackTrace(); } // 通过psot()发送,需传入1个Runnable对象 handler.post(new Runnable() { @Override public void run() { // 指定操作UI内容 Log.d(TAG, "run: 执行了线程1的UI操作"); tvMsg.setText("执行了线程1的UI操作"); } }); } }.start(); // 步骤3:开启子线程(同时启动了Handler) // 此处再创建第2个子线程 new Thread() { @Override public void run() { try { Thread.sleep(6000); // 间隔6s } catch (InterruptedException e) { e.printStackTrace(); } handler.post(new Runnable() { @Override public void run() { Log.d(TAG, "run: 执行了线程2的UI操作"); tvMsg.setText("执行了线程2的UI操作"); } }); } }.start(); break; } } }
二、复合使用
Android多线程实现的复合使用包括:
- AsyncTask
- HandlerThread
- IntentService
称为”复合使用“的主要原因是:这3种方式的本质原理都是Android多线程基础实现(继承Thread类、实现Runnable接口、Handler)的组合实现。
1、AsyncTask
- 一个Android 已封装好的轻量级异步类
- 属于抽象类,即使用时需实现子类
AsyncTask的本质:AsyncTask的实现原理 = 线程池 + Handler
其中:线程池用于线程调度、复用 和 执行任务;Handler 用于异步通信。
使用步骤
AsyncTask的使用步骤有3个:
- 创建 AsyncTask 子类,并根据需求实现核心方法
- 创建 AsyncTask子类的实例对象(即 任务实例)
- 手动调用execute()从而执行异步线程任务
import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { /** * 步骤1:创建AsyncTask子类 * 注: * a. 继承AsyncTask类 * b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替 * c. 根据需求,在AsyncTask子类内实现核心方法 */ private class MyTask extends AsyncTask<Params, Progress, Result> { // 方法1:onPreExecute() // 作用:执行子线程任务前的操作 // 注:根据需求重写 @Override protected void onPreExecute() { // 下载的准备工作 } // 方法2:doInBackground() // 作用:接收输入参数、执行任务中的耗时操作、返回线程任务执行的结果 // 注:必须重写,从而定义子线程任务 @Override protected Result doInBackground(Params... params) { // 子线程中的,要异步执行完成的任务 // 可调用publishProgress()显示进度, 之后将执行onProgressUpdate() publishProgress(count); return null; } // 方法3:onProgressUpdate() // 作用:在主线程,显示线程任务执行的进度 // 注:根据需求重写 @Override protected void onProgressUpdate(Progress... progresses) { // 下载过程中,要更新进度的方法 } // 方法4:onPostExecute() // 作用:接收子线程任务执行结果、将执行结果显示到UI组件 // 注:必须重写,从而更新UI操作 @Override protected void onPostExecute(Result result) { // UI操作,下载完成后,子线程给主线程提交结果的方法 } // 方法5:onCancelled() // 作用:将异步任务设置为”取消“状态 @Override protected void onCancelled() { } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); /** * 步骤2:创建AsyncTask子类的实例对象(即 任务实例) * 注:AsyncTask子类的实例必须在UI线程中创建 */ MyTask mTask = new MyTask(); /** * 步骤3:手动调用execute(Params... params) 从而执行异步线程任务 * 注: * a. 必须在UI线程中调用 * b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常 * c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute() * d. 不能手动调用上述方法 */ mTask.execute(); } }
实例讲解
(1) 实例说明
点击按钮 则 开启线程执行线程任务
显示后台加载进度
加载完毕后更新UI组件
期间若点击取消按钮,则取消加载
(2) 具体实现
activity_asynctask.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical" tools:context=".AsyncTaskActivity"> <Button android:id="@+id/btnStart" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开始加载" /> <TextView android:id="@+id/tvMessage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="还没开始加载!" /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:max="100" android:progress="0" /> <Button android:id="@+id/btnCancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="取消" /> </LinearLayout>
AsyncTaskActivity.java
package com.sdbi.threadtest; import androidx.appcompat.app.AppCompatActivity; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; public class AsyncTaskActivity extends AppCompatActivity { // 线程变量 private MyTask mTask; // 主布局中的UI组件 private Button btnStart, btnCancel; // 加载、取消按钮 private TextView tvMessage; // 更新的UI组件 private ProgressBar progressBar; // 进度条 /** * 步骤1:创建AsyncTask子类 * 注: * a. 继承AsyncTask类 * b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替 * 此处指定为:输入参数 = String类型、执行进度 = Integer类型、执行结果 = String类型 * c. 根据需求,在AsyncTask子类内实现核心方法 */ private class MyTask extends AsyncTask<String, Integer, String> { // 方法1:onPreExecute() // 作用:执行 线程任务前的操作 @Override protected void onPreExecute() { tvMessage.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]); tvMessage.setText("loading..." + progresses[0] + "%"); } // 方法4:onPostExecute() // 作用:接收线程任务执行结果、将执行结果显示到UI组件 @Override protected void onPostExecute(String result) { // 执行完毕后,则更新UI tvMessage.setText("加载完毕"); } // 方法5:onCancelled() // 作用:将异步任务设置为:取消状态 @Override protected void onCancelled() { tvMessage.setText("已取消"); progressBar.setProgress(0); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_asynctask); btnStart = findViewById(R.id.btnStart); btnCancel = findViewById(R.id.btnCancel); tvMessage = findViewById(R.id.tvMessage); progressBar = findViewById(R.id.progressBar); // 加载按钮按按下时,则启动AsyncTask // 任务完成后更新TextView的文本 btnStart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { /** * 步骤2:创建AsyncTask子类的实例对象(即 任务实例) * 注:AsyncTask子类的实例必须在UI线程中创建 */ mTask = new MyTask(); /** * 步骤3:手动调用execute(Params... params) 从而执行异步线程任务 * 注: * a. 必须在UI线程中调用 * b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常 * c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute() * d. 不能手动调用上述方法 */ mTask.execute(); } }); btnCancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 取消一个正在执行的任务,onCancelled方法将会被调用 mTask.cancel(true); } }); } }
运行结果:
核心方法:
异步任务类的三个泛型参数 与 四个回调方法参数或返回值的关系:
方法执行顺序如下:
2、HandlerThread
HandlerThread的本质:继承Thread类 & 封装Handler类
HandlerThread的使用步骤分为5步:
3、IntentService
Android里的一个封装类,继承四大组件之一的Service
处理异步请求 和 实现多线程
使用场景
线程任务 需 按顺序、在后台执行。
使用步骤
- 步骤1:定义 IntentService的子类,需复写onHandleIntent()方法
- 步骤2:在Manifest.xml中注册服务
- 步骤3:在Activity中开启Service服务
三、 高级使用
Android多线程的高级使用主要是线程池(ThreadPool)。
标签:Thread,private,Handler,线程,Override,import,Android,多线程 From: https://www.cnblogs.com/lihuawei/p/16702907.html