前言
-
多线程的应用在Android开发中是非常常见的,常用方法主要有:
- 继承Thread类
- 实现Runnable接口
- Handler
- AsyncTask
- HandlerThread
-
今天,我将献上一份
AsyncTask
使用教程,希望大家会喜欢
目录
示意图
1. 定义
- 一个
Android
已封装好的轻量级异步类 - 属于抽象类,即使用时需 实现子类
public abstract class AsyncTask<Params, Progress, Result> {
...
}
. 作用
- 实现多线程
在工作线程中执行任务,如 耗时任务 - 异步通信、消息传递
实现工作线程 & 主线程(UI
线程)之间的通信,即:将工作线程的执行结果传递给主线程,从而在主线程中执行相关的UI
操作
从而保证线程安全
3. 优点
- 方便实现异步通信
不需使用 “任务线程(如继承Thread
类) +Handler
”的复杂组合 - 节省资源
采用线程池的缓存线程 + 复用线程,避免了频繁创建 & 销毁线程所带来的系统资源开销
4. 类 & 方法介绍
4.1 类定义
AsyncTask
类属于抽象类,即使用时需 实现子类
方法执行顺序如下
5. 使用步骤
-
AsyncTask
的使用步骤有3个:
- 创建
AsyncTask
子类 & 根据需求实现核心方法 - 创建
AsyncTask
子类的实例对象(即 任务实例) - 手动调用
execute(()
从而执行异步线程任务
- 具体介绍如下
/**
* 步骤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 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()
* d. 不能手动调用上述方法
*/
mTask.execute();
6. 实例讲解
下面,我将用1个实例讲解 具体如何使用 AsyncTask
6.1 实例说明
- 点击按钮 则 开启线程执行线程任务
- 显示后台加载进度
- 加载完毕后更新UI组件
- 期间若点击取消按钮,则取消加载
如下图
6.2 具体实现
建议先下载源码再看:Carson_Ho的Github地址:AsyncTask
7.2 关于 内存泄漏
7.3 线程任务执行结果 丢失
8. 源码分析
- 主布局文件:activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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" tools:context="com.example.carson_ho.handler_learning.MainActivity"> <Button android:layout_centerInParent="true" android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点我加载"/> <TextView 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="还没开始加载!" /> <ProgressBar 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"/> <Button 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"/> </RelativeLayout>
- 主逻辑代码文件:MainActivity.java
-
public class MainActivity extends AppCompatActivity { // 线程变量 MyTask mTask; // 主布局中的UI组件 Button button,cancel; // 加载、取消按钮 TextView text; // 更新的UI组件 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() { 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); } } @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); /** * 步骤2:创建AsyncTask子类的实例对象(即 任务实例) * 注:AsyncTask子类的实例必须在UI线程中创建 */ mTask = new MyTask(); // 加载按钮按按下时,则启动AsyncTask // 任务完成后更新TextView的文本 button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { /** * 步骤3:手动调用execute(Params... params) 从而执行异步线程任务 * 注: * a. 必须在UI线程中调用 * b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常 * c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute() * d. 不能手动调用上述方法 */ mTask.execute(); } }); cancel = (Button) findViewById(R.id.cancel); cancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 取消一个正在执行的任务,onCancelled方法将会被调用 mTask.cancel(true); } }); } }
-
7. 使用时的注意点
在使用
AsyncTask
时有一些问题需要注意的:7.1 关于 生命周期
- 结论
AsyncTask
不与任何组件绑定生命周期 - 使用建议
在Activity
或Fragment
中使用AsyncTask
时,最好在Activity
或Fragment
的onDestory()
调用cancel(boolean)
; - 结论
若AsyncTask
被声明为Activity
的非静态内部类,当Activity
需销毁时,会因AsyncTask
保留对Activity
的引用 而导致Activity
无法被回收,最终引起内存泄露 - 使用建议
AsyncTask
应被声明为Activity
的静态内部类 - 结论
当Activity
重新创建时(屏幕旋转 /Activity
被意外销毁时后恢复),之前运行的AsyncTask
(非静态的内部类)持有的之前Activity
引用已无效,故复写的onPostExecute()
将不生效,即无法更新UI操作 - 使用建议
在Activity
恢复时的对应方法 重启 任务线程 - 知其然 而须知其所以然,了解
AsyncTask
的源码分析有利于更好地理解AsyncTask
的工作原理 - 具体请看文章:Android 多线程:AsyncTask的原理 及其源码分析