转自:https://www.jianshu.com/p/ee1342fcf5e7

前言

  • 多线程的应用在Android开发中是非常常见的,常用方法主要有:

    1. 继承Thread类
    2. 实现Runnable接口
    3. Handler
    4. AsyncTask
    5. HandlerThread
  • 今天,我将献上一份AsyncTask使用教程,希望大家会喜欢


目录

 
示意图

1. 定义

  • 一个Android 已封装好的轻量级异步类
  • 属于抽象类,即使用时需 实现子类
public abstract class AsyncTask<Params, Progress, Result> {
...
}

2. 作用

  1. 实现多线程

    在工作线程中执行任务,如 耗时任务
  2. 异步通信、消息传递

    实现工作线程 & 主线程(UI线程)之间的通信,即:将工作线程的执行结果传递给主线程,从而在主线程中执行相关的UI操作

从而保证线程安全


3. 优点

  • 方便实现异步通信

    不需使用 “任务线程(如继承Thread类) + Handler”的复杂组合
  • 节省资源

    采用线程池的缓存线程 + 复用线程,避免了频繁创建 & 销毁线程所带来的系统资源开销

4. 类 & 方法介绍

4.1 类定义

AsyncTask类属于抽象类,即使用时需 实现子类

public abstract class AsyncTask<Params, Progress, Result> {
...
} // 类中参数为3种泛型类型
// 整体作用:控制AsyncTask子类执行线程任务时各个阶段的返回类型
// 具体说明:
// a. Params:开始异步任务执行时传入的参数类型,对应excute()中传递的参数
// b. Progress:异步任务执行过程中,返回下载进度值的类型
// c. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致
// 注:
// a. 使用时并不是所有类型都被使用
// b. 若无被使用,可用java.lang.Void类型代替
// c. 若有不同业务,需额外再写1个AsyncTask的子类
}

4.2 核心方法

  • AsyncTask 核心 & 常用的方法如下:
 
示意图
  • 方法执行顺序如下
 
示意图

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<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 实例说明

  1. 点击按钮 则 开启线程执行线程任务
  2. 显示后台加载进度
  3. 加载完毕后更新UI组件
  4. 期间若点击取消按钮,则取消加载

如下图

 
示意图

6.2 具体实现

建议先下载源码再看:Carson_Ho的Github地址:AsyncTask

  • 主布局文件: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不与任何组件绑定生命周期
  • 使用建议

    ActivityFragment中使用 AsyncTask时,最好在ActivityFragmentonDestory()调用 cancel(boolean)

7.2 关于 内存泄漏

  • 结论

    AsyncTask被声明为Activity的非静态内部类,当Activity需销毁时,会因AsyncTask保留对Activity的引用 而导致Activity无法被回收,最终引起内存泄露
  • 使用建议

    AsyncTask应被声明为Activity的静态内部类

7.3 线程任务执行结果 丢失

  • 结论

    Activity重新创建时(屏幕旋转 / Activity被意外销毁时后恢复),之前运行的AsyncTask(非静态的内部类)持有的之前Activity引用已无效,故复写的onPostExecute()将不生效,即无法更新UI操作
  • 使用建议

    Activity恢复时的对应方法 重启 任务线程

8. 源码分析

作者:Carson_Ho
链接:https://www.jianshu.com/p/ee1342fcf5e7
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

转 Android 多线程:手把手教你使用AsyncTask的更多相关文章

  1. Android多线程分析之五:使用AsyncTask异步下载图像

    Android多线程分析之五:使用AsyncTask异步下载图像 罗朝辉 (http://www.cnblogs.com/kesalin) CC 许可,转载请注明出处 在本系列文章的第一篇<An ...

  2. 使用Android Studio手把手教你将应用打包+代码混淆

    最近几天用Google的Design库写了个App,使用Android Studio将app打包时遇到的几个瓶颈,所以把详细步骤写入下来. AS中怎么获取应用签名 这和eclipse不同,eclips ...

  3. Android:手把手教你 实现Activity 与 Fragment 相互通信,发送字符串信息(含Demo)

    前言Activity 与 Fragment 的使用在Android开发中非常多今天,我将主要讲解 Activity 与 Fragment 如何进行通信,实际上是要解决两个问题: Activity 如何 ...

  4. 【转】手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)

    1.引言 特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途.如本文内容有不妥之处,请联系JackJiang进行处理!   我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群 ...

  5. 手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)

    1.引言 特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途.如本文内容有不妥之处,请联系JackJiang进行处理!   我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群 ...

  6. Android多线程任务优化1:探讨AsyncTask的缺陷

     AsyncTask还有别的缺陷,在生成listview的时候,如果adapter里面的count动态改变的话,不能使用AsyncTask,只能使用Thread+Handler,否则会出现如下错误 j ...

  7. Android开发之手把手教你写ButterKnife框架(三)

    欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52672188 本文出自:[余志强的博客] 一.概述 上一篇博客讲了, ...

  8. Android开发之手把手教你写ButterKnife框架(二)

    欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52664112 本文出自:[余志强的博客] 上一篇博客Android开 ...

  9. Android:手把手教你打造可缩放移动的ImageView(下)

    在上一篇Android:手把手教你打造可缩放移动的ImageView最后提出了一个注意点:当自定义的MatrixImageView如ViewPager.ListView等带有滑动效果的ViewGrou ...

随机推荐

  1. 开源的分布式事务框架 springcloud Alibaba Seata 的搭建使用 一次把坑踩完。。。

    seata的使用 1. Seata 概述 Seata 是 Simple Extensible Autonomous Transaction Architecture 的简写,由 feascar 改名而 ...

  2. uni-app app端设置全屏背景色

    设置page:{样式},博主调试的时候在app端不起作用,设置配置文件的backgroundColor也没有用,所以博主就使用了一个稍微比较偏的办法解决了,没有用获取设备信息的api来实现 具体操作就 ...

  3. 一个疏忽损失惨重!就因为把int改成Integer,第2天被辞了

    1 故事背景 一个程序员就因为改了生产环境上的一个方法参数,把int型改成了Integer类型,因为涉及到钱,结果上线之后公司损失惨重,程序员被辞退了.信不信继续往下看.先来看一段代码: public ...

  4. 编译静态库的方式使用spdlog和fmt

    前言 spdlog++库,而且支持header only方式,但header only的使用方式会造成编译时长增加,所以这里简单描述一下,其编译静态库的方式. 又因为spdlog还依赖另一个开源库fm ...

  5. java解析Excel日期格式转换问题

    Excel上传导入,Excel里面单元格是日期的会解析出来数字,比如2020-07-11会解析为44023解决方法一: Excel单元格格式设置为文本格式.解决方法二: 使用代码处理,把解析出来的44 ...

  6. Python科普系列——类与方法(上篇)

    欢迎来到新的系列,up又开新坑了~~ 实际上,Python作为一门易用性见长的语言,看上去简单,却仍然有很多值得一说的内容,因此这个系列会把Python中比较有意思的地方也给科普一遍.而另一方面,关于 ...

  7. 『学了就忘』Linux软件包管理 — 42、对RPM软件包的查询操作

    目录 1.查询RPM软件包是否安装 2.查询系统中所有已安装的RPM软件包 3.查询RPM软件包的详细信息 4.查询RPM软件包中的文件列表 5.查询系统文件属于哪个RPM包 6.查询RPM软件包所依 ...

  8. 环境(8)Linux用户组权限

    一:Linux时间日期-时间同步策略 1.日期与时间 ①时间命令 data:查看当前系统时间 cal :查看日历     cal  2020 修改时间:   date -s  11:11:11    ...

  9. Nginx的try_files指令使用实例

    Nginx的配置语法灵活,可控制度非常高.在0.7以后的版本中加入了一个try_files指令,配合命名location,可以部分替代原本常用的rewrite配置方式,提高解析效率. try_file ...

  10. MySQL用limit代替SQL Server :top

    mysql 中不支持top,而是用limit代替 若要查询前10条记录,mysql用limit 10 LIMIT可以实现top N查询,也可以实现M至N(某一段)的记录查询,具体语法如下: SELEC ...