转 Android 多线程:手把手教你使用AsyncTask
转自:https://www.jianshu.com/p/ee1342fcf5e7
前言
多线程的应用在Android开发中是非常常见的,常用方法主要有:
- 继承Thread类
- 实现Runnable接口
- Handler
- AsyncTask
- HandlerThread
今天,我将献上一份
AsyncTask
使用教程,希望大家会喜欢
目录
1. 定义
- 一个
Android
已封装好的轻量级异步类 - 属于抽象类,即使用时需 实现子类
public abstract class AsyncTask<Params, Progress, Result> {
...
}
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个:
- 创建
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
- 主布局文件: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)
;
7.2 关于 内存泄漏
- 结论
若AsyncTask
被声明为Activity
的非静态内部类,当Activity
需销毁时,会因AsyncTask
保留对Activity
的引用 而导致Activity
无法被回收,最终引起内存泄露 - 使用建议
AsyncTask
应被声明为Activity
的静态内部类
7.3 线程任务执行结果 丢失
- 结论
当Activity
重新创建时(屏幕旋转 /Activity
被意外销毁时后恢复),之前运行的AsyncTask
(非静态的内部类)持有的之前Activity
引用已无效,故复写的onPostExecute()
将不生效,即无法更新UI操作 - 使用建议
在Activity
恢复时的对应方法 重启 任务线程
8. 源码分析
- 知其然 而须知其所以然,了解
AsyncTask
的源码分析有利于更好地理解AsyncTask
的工作原理 - 具体请看文章:Android 多线程:AsyncTask的原理 及其源码分析
作者:Carson_Ho
链接:https://www.jianshu.com/p/ee1342fcf5e7
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
转 Android 多线程:手把手教你使用AsyncTask的更多相关文章
- Android多线程分析之五:使用AsyncTask异步下载图像
Android多线程分析之五:使用AsyncTask异步下载图像 罗朝辉 (http://www.cnblogs.com/kesalin) CC 许可,转载请注明出处 在本系列文章的第一篇<An ...
- 使用Android Studio手把手教你将应用打包+代码混淆
最近几天用Google的Design库写了个App,使用Android Studio将app打包时遇到的几个瓶颈,所以把详细步骤写入下来. AS中怎么获取应用签名 这和eclipse不同,eclips ...
- Android:手把手教你 实现Activity 与 Fragment 相互通信,发送字符串信息(含Demo)
前言Activity 与 Fragment 的使用在Android开发中非常多今天,我将主要讲解 Activity 与 Fragment 如何进行通信,实际上是要解决两个问题: Activity 如何 ...
- 【转】手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)
1.引言 特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途.如本文内容有不妥之处,请联系JackJiang进行处理! 我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群 ...
- 手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)
1.引言 特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途.如本文内容有不妥之处,请联系JackJiang进行处理! 我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群 ...
- Android多线程任务优化1:探讨AsyncTask的缺陷
AsyncTask还有别的缺陷,在生成listview的时候,如果adapter里面的count动态改变的话,不能使用AsyncTask,只能使用Thread+Handler,否则会出现如下错误 j ...
- Android开发之手把手教你写ButterKnife框架(三)
欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52672188 本文出自:[余志强的博客] 一.概述 上一篇博客讲了, ...
- Android开发之手把手教你写ButterKnife框架(二)
欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52664112 本文出自:[余志强的博客] 上一篇博客Android开 ...
- Android:手把手教你打造可缩放移动的ImageView(下)
在上一篇Android:手把手教你打造可缩放移动的ImageView最后提出了一个注意点:当自定义的MatrixImageView如ViewPager.ListView等带有滑动效果的ViewGrou ...
随机推荐
- pvcreate vgcreate lvcreate 扩容
centos6 服务器磁盘扩容 1.创建物理卷 /dev/sdb #pvcreate /dev/sdb 参数:/dev/sdb 设备名 2.创建卷组 vg_02 #vgcreate vg_02 / ...
- 【完美解决】IDEA 中 Maven 报错 Cannot resolve xxx 和 Maven 中 Dependencies 报红/报错。
目录 前提 场景 解决办法 1.首先,清除缓存,点击之后重启IDEA. 2.关闭IDEA,打开项目文件夹 3.重新打开 IDEA,找到右边的 Maven 4.解决 Maven 中 Dependenci ...
- IP基础 & 子网划分 & 路由寻址
IP地址详解 IP地址概念 就像用身份证号码来区别毎个人一样,为了区别 网上的每台计算机,我们给因特网上的每一台计算机一个唯一的编号 ,我们把它称为IP地址 IP地址就是一个唯一标识 ,是一段网络编码 ...
- flask gevent
flask的不同部署方式 使用gevent部署,只是在不同请求之间是异步的,同一个请求之间还是串行的. https://iximiuz.com/en/posts/flask-gevent-tutori ...
- Java学习(十五)
下棋又被暴打 所以心态有点爆炸,学完习又去打云顶,忘记写博客了... 所以今天也有点水了,这下棋真是搞崩我心态了. Java学了函数重载 大概的要点在这 还有Web的学习,否定伪类. 如 p:not( ...
- Python 中的反转字符串:reversed()、切片等
摘要:以相反的顺序反转和处理字符串可能是编程中的一项常见任务.Python 提供了一组工具和技术,可以帮助您快速有效地执行字符串反转. 本文分享自华为云社区<Python 中的反转字符串:rev ...
- oracle基础:怎样把查询的null转换为0、打印、定义变量
https://blog.csdn.net/xuxile/article/details/49943665 oracle怎样把查询的null转换为0 1.查询的null转换为0 NVL(Expr1,E ...
- Duboo整合SpringBoot超级详细例子(附源码)
dubbo3.0整合SpringBoot例子 dubbo新版本(3.0以上)在相对于 dubbo 旧版本(2.5.2.6.2.7),有很多的不相同的地方. 官方文档也说了新版本的特性: https:/ ...
- MySQL5.5.33对应的JDBC驱动包怎样使用?
双击msi文件就会自动安装,然后找到安装路径下的jar,并把它加到类路径下,如手动编译和执行时javac -classpath c:\program files\...\mysql.jar;... m ...
- [loj3501]图函数
$f(i,G)_{x}$为$x$对$i$是否有贡献,即在枚举到$x$时,$i$与$x$是否强连通 事实上,$f(i,G)_{x}=1$即不经过$[1,x)$中的点且$i$与$x$强连通 首先,当存在这 ...