Android异步之旅:探索AsyncTask

前言: 

在Android应用程序开发中,异步操作是非常常见的需求。比如,我们可能需要在后台线程中执行网络请求、数据库操作或者其他耗时的任务,而不阻塞UI线程。为了实现这些异步操作,Android提供了多种方式,其中之一就是使用AsyncTask类。

1.什么是AsyncTask?


     AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终的结果传递给主线程并在主线程中更新UI。(两个线程池+Handler

2.介绍AsyncTask类的泛型参数和核心方法


AsyncTask是一个抽象的泛型类,它提供了ParamsProgressResult这三个泛型参数。

public abstract class AsyncTask<Params, Progress, Result>

其中,

  • 🟨Params表示参数的类型
  • 🟨Progress表示后台任务的执行进度和类型
  • 🟨Result表示后台任务的返回结果

AsyncTask类提供了四个核心方法(按照执行顺序介绍):

1️⃣onPreExecute():主线程中执行,用于准备工作

2️⃣doInBackground(Params...params):在线程池中执行,用于执行异步任务。在此方法中可以通过publishProgress方法来更新任务的进度,publishProgress方法调用onProgressUpdate方法。

3️⃣onProgressUpdate(Progress...values):在主线程中执行,当后台任务的执行进程发送变化时此方法会被调用。

4️⃣onPostExecute(Result result):在主线程中执行,在异步任务执行之后,此方法会被调用。

AsyncTask还提供了onCancelled()方法,它同样在主线程中执行,当异步任务被取消时,onCancelled会被调用,这个时候,onPostExecute则不会被调用。

3.如何使用AsyncTask?


使用AsyncTask的步骤:

🔶步骤一:继承AsyncTask类,并实现它的几个回调方法,比如doInBackground()方法用来执行后台任务,onPostExecute()方法用来更新UI。

🔶步骤二:在UI线程中创建AsyncTask的实例,并调用execute()方法来启动异步操作。

示例:

在Activity中,创建一个子类来继承AsyncTask,完成以下任务:

1.展示ProgressDialog

2.发送网络请求

3.关闭ProgressDialog

代码如下:

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);new AsyncTask_test().execute("");}
​public static ProgressDialog dialog;public class AsyncTask_test extends AsyncTask<String,Integer,Long> {@Overrideprotected void onPreExecute() {dialog=ProgressDialog.show(MainActivity.this,"", "努力加载中");}
​@Overrideprotected Long doInBackground(String... strings) {OkHttpClient okHttpClient = new OkHttpClient();Request request=new Request.Builder().url("https://www.httpbin.org/get?a=1&b=2").build();Response response= null;try {response = okHttpClient.newCall(request).execute();if(!response.isSuccessful()){Log.e("xxx","网络请求失败"+response);}String responsedata=response.body().string();if(responsedata!=null){Log.e("xxx","输出:"+responsedata);}} catch (IOException e) {Log.e("xxx","报错"+e);throw new RuntimeException(e);}
​return null;}
​@Overrideprotected void onPostExecute(Long aLong) {if(dialog!=null){dialog.dismiss();}super.onPostExecute(aLong);}}
}

AsyncTask在使用时的注意事项:

🟠AsyncTask的对象必须在主线程中创建。(原因:AsyncTask的handler对象是静态的,Handler对象要切换到主线程,由于静态成员在类加载时就被初始化,因此AsyncTask必须在主线程中加载)

🟠execute方法必须在UI线程调用

🟠不要在线程中直接调用onPreExecute()、onPostExecute、doInBackground和onProgressUpdate方法。

🟠一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常。

4.AsyncTask的工作原理


工作原理:

➡️当asyncTask执行execute()方法的时候,会先调用onPreExecute()方法。

➡️然后调用SERIAL_EXECUTOR的execute(mFuture),把任务加入到队列的尾部等待执行。

➡️执行的时候调用THREAD_POOL_EXECUTOR的execute(mFuture)方法。

➡️mFuture调用mWorker的call()方法,在call()方法中调用了dolnBackground()方法,并在最后调用了postResult()方法。

➡️postResult()方法也就是通过Handler发送消息给主线程,在主线程中调用AsyncTask的finish()方法,来决定是调用onCancelled()还是onPostExecute()方法。

源码解析:

@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {return executeOnExecutor(sDefaultExecutor, params);
}@MainThreadpublic final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,Params... params) {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)");}}
​mStatus = Status.RUNNING;
​onPreExecute();
​mWorker.mParams = params;exec.execute(mFuture);
​return this;}

其中,sDefaultExecutor是一个串行的线程池,一个进程中所有的AsyncTask全部在这个串行的线程池中排队执行。在executeOnExecutor方法中,AsyncTask的onPreExecute方法最先执行,然后线程池开始执行,下面是线程池的执行过程:

    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;private static class SerialExecutor implements Executor {final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();Runnable mActive;
​public synchronized void execute(final Runnable r) {mTasks.offer(new Runnable() {public void run() {try {r.run();} finally {scheduleNext();}}});if (mActive == null) {scheduleNext();}}
​protected synchronized void scheduleNext() {if ((mActive = mTasks.poll()) != null) {THREAD_POOL_EXECUTOR.execute(mActive);}}}

当一个AsyncTask任务执行完后,AsyncTask会调用scheduleNext()方法继续执行下一个任务直到所有任务被执行为止,总的来说,默认情况下,AsyncTask是串行执行的。

AsyncTask中有两个线程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handle:

SerialExecutor:用于任务的排队

THREAD_POOL_EXECUTOR:用于真正地执行任务

Handler:用于将执行环境从线程池切换到主线程

在AsyncTask的构造方法中有如下一段代码,由于FutureTask的run方法会调用mWorker的call方法,因此mWorker的call方法最终会在线程池中执行。

  public AsyncTask(@Nullable Looper callbackLooper) {mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()? getMainHandler(): new Handler(callbackLooper);
​mWorker = new WorkerRunnable<Params, Result>() {public Result call() throws Exception {mTaskInvoked.set(true);Result result = null;try {Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);//noinspection uncheckedresult = doInBackground(mParams);Binder.flushPendingCommands();} catch (Throwable tr) {mCancelled.set(true);throw tr;} finally {postResult(result);}return result;}};
​mFuture = new FutureTask<Result>(mWorker) {@Overrideprotected void done() {try {postResultIfNotInvoked(get());} catch (InterruptedException e) {android.util.Log.w(LOG_TAG, e);} catch (ExecutionException e) {throw new RuntimeException("An error occurred while executing doInBackground()",e.getCause());} catch (CancellationException e) {postResultIfNotInvoked(null);}}};}
​

在mWorker的call方法中,首先将mTaskInvoked设为true,表示当前任务已经被调用过了,然后执行AsyncTask的doInBackground方法,接着将返回值传递给postResult方法,它的实现如下:

private Result postResult(Result result) {@SuppressWarnings("unchecked")Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,new AsyncTaskResult<Result>(this, result));message.sendToTarget();return result;
}

在上面代码中,postResult方法会通过Handler对象发送一个MESSAGE_POST_RESULT的消息,这个Handler对象的定义如下:

getHandler()获取Handler对象

private Handler getHandler() {return mHandler;
}

赋值mHandler

private final Handler mHandler;
public AsyncTask(@Nullable Looper callbackLooper) {mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()? getMainHandler(): new Handler(callbackLooper);...}

(1)如果callbackLooper == null,就getMainHandler()

private static Handler getMainHandler() {synchronized (AsyncTask.class) {if (sHandler == null) {sHandler = new InternalHandler(Looper.getMainLooper());}return sHandler;}
}

(2)如果callbackLooper == Looper.getMainLooper(),就new Handler(callbackLooper)

public Handler(@NonNull Looper looper) {this(looper, null, false);
}

Handler对象收到MESSAGE_POST_RESULT这个消息后会调用AsyncTask的finish方法。

private void finish(Result result) {if (isCancelled()) {onCancelled(result);} else {onPostExecute(result);}mStatus = Status.FINISHED;
}

如果AsyncTask被取消执行了,那么就调用onCancelled方法,否则就会调用onPostExecute方法,就可以看到doInBackground的返回结果会传递给onPostExecute方法,到这里AsyncTask的整个工作过程就分析完毕了。


总结

通过本篇博客,我们了解了AsyncTask的工作原理和如何在Android应用程序中使用它来进行异步操作。AsyncTask提供了一种简单而强大的方式来管理异步任务,并在UI线程中更新UI,是Android开发中不可或缺的工具之一。希望本篇博客能帮助你更好地理解和使用AsyncTask。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/160941.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Qt的UDP通信、TCP文件传输程序的设计与实现——QQ聊天群聊

&#x1f64c;秋名山码民的主页 &#x1f602;oi退役选手&#xff0c;Java、大数据、单片机、IoT均有所涉猎&#xff0c;热爱技术&#xff0c;技术无罪 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 获取源码&#xff0c;添加WX 目录 前言一…

PostgreSQL序列,怎么才能第二天重新从1开始计数

--确定日期最大值和每天序列号最大值 with cte as(select (((1::bigint)<<32)-1) as max_date_second,(((1::bigint)<<31)-1) as max_sn )select max_date_second,to_timestamp(max_date_second),max_sn,((max_date_second<<31)|max_sn) as max_val,(((max_d…

Selenium 元素不能定位总结

目录 元素不能定位总结: 1、定位语法错误&#xff1a; 定位语法错误&#xff0c;如无效的xpath&#xff0c;css selector,dom路径错误&#xff0c;动态dom 定位语法错误&#xff0c;动态路径&#xff08;动态变化&#xff09; 定位策略错误&#xff0c;如dom没有id用id定位…

研发探索:导购APP、查券返利机器人与淘客系统,全面对比与选择

研发探究&#xff1a;导购APP、查券返利机器人与淘客系统&#xff0c;全面对比与选择 在互联网购物的时代&#xff0c;导购APP、淘客机器人和微赚淘客系统成为了消费者们的三大好帮手。它们各具优势&#xff0c;但也存在一些不足。本文将为您详细对比这三种工具&#xff0c;帮…

vue history路径编码

记录今天遇到的一个问题&#xff1a; 问题现状 有一个需要前端伪造302进行重定向的需求&#xff0c;我们需要将这样的一个路径&#xff1a;http://xxx.com/system-name/#/index&#xff0c;拼接在跳转地址的后面&#xff0c;进行重定向。拼接的方式是这样的&#xff1a; htt…

攻防世界-web-Confusion1

1. 题目描述 打开链接&#xff0c;如图 点击Login和Rigister&#xff0c;都报错 但是有提示 指出了flag所在的位置&#xff0c;题目中直接能获取到的信息暂时就这么些了 2. 思路分析 既然告诉了我们flag文件的位置&#xff0c;那么要读取到这个文件&#xff0c;要么是任意文…

AI辅助带货直播场景源码系统 附带网站的搭建教程

互联网技术的发展和普及&#xff0c;直播带货行业迅速崛起。然而&#xff0c;直播带货在带来商机的同时&#xff0c;也面临着诸多挑战。如直播内容缺乏新意、转化率低等问题。针对这些问题&#xff0c;AI辅助带货直播场景源码系统应运而生&#xff0c;旨在利用人工智能技术&…

【高级渗透篇】网络安全面试

【高级渗透篇】网络安全面试 1.权限维持2.代码安全Python语法相关 1.权限维持 Linux权限维持方法论 Windows权限维持方法论 2.代码安全 Python 语法相关 1、Python的值类型和引用类型是哪些 Python 中的值类型包括&#xff1a; 数字类型&#xff08;如整数、浮点数、复数…

对接苹果支付退款退单接口

前言 一般而言&#xff0c;我们其实很少对接退款接口&#xff0c;因为退款基本都是商家自己决定后进行操作的&#xff0c;但是苹果比较特殊&#xff0c;用户可以直接向苹果发起退款请求&#xff0c;苹果觉得合理会退给用户&#xff0c;但是目前公司业务还是需要对接这个接口&am…

试试MyBatis-Plus可视化代码生成器,太香了,你一定会感谢我

前言 在基于Mybatis的开发模式中&#xff0c;很多开发者还会选择Mybatis-Plus来辅助功能开发&#xff0c;以此提高开发的效率。虽然Mybatis也有代码生成的工具&#xff0c;但Mybatis-Plus由于在Mybatis基础上做了一些调整&#xff0c;因此&#xff0c;常规的生成工具生成的代码…

PC端使子组件的弹框关闭

子组件 <template><el-dialog title"新增部门" :visible"showDialog" close"close"> </el-dialog> </template> <script> export default {props: {showDialog: {type: Boolean,default: false,},},data() {retu…

【JavaSE】-5-嵌套循环

回顾 一、java语言特点 二、配置java环境 path 三、记事本 javac -d . java 包名.类名 四、eclipse 五、变量 定义变量 数据类型 变量名值; 六、相关的数据类型 ​ 基本&#xff08;四类 、8种&#xff09;、引用 ​ 类型转换&#xff08;自动、强制&#xff09; ​ 运…

Java面向对象(高级)-- 类中属性赋值的位置及过程

文章目录 一、赋值顺序&#xff08;1&#xff09;赋值的位置及顺序&#xff08;2&#xff09;举例&#xff08;3&#xff09;字节码文件&#xff08;4&#xff09;进一步探索&#xff08;5&#xff09;最终赋值顺序&#xff08;6&#xff09;实际开发如何选 二、(超纲)关于字节…

1992-2021年省市县经过矫正的夜间灯光数据(GNLD、VIIRS)

1992-2021年省市县经过矫正的夜间灯光数据&#xff08;GNLD、VIIRS&#xff09; 1、时间&#xff1a;1992-2021年3月&#xff0c;其中1992-2013年为年度数据&#xff0c;2013-2021年3月为月度数据 2、来源&#xff1a;DMSP、VIIRS 3、范围&#xff1a;分区域汇总&#xff1a…

SpringBoot : ch05 整合Mybatis

前言 随着Java Web应用程序的快速发展&#xff0c;开发人员需要越来越多地关注如何高效地构建可靠的应用程序。Spring Boot作为一种快速开发框架&#xff0c;旨在简化基于Spring的应用程序的初始搭建和开发过程。而MyBatis作为一种优秀的持久层框架&#xff0c;提供了对数据库…

【Linux】-进程间通信-共享内存(SystemV),详解接口函数以及原理(使用管道处理同步互斥机制)

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

中低压MOSFET 2N7002T 60V 300mA 双N通道 采用SOT-523封装形式

2N7002KW小电流双N通道MOSFET&#xff0c;电压60V电流300mA&#xff0c;采用SOT-523封装形式。低Ros (on)的高密度单元设计&#xff0c;坚固可靠&#xff0c;具有高饱和电流能力&#xff0c;ESD防护门HBM2KV。可应用于直流/直流转换器&#xff0c;电池开关等产品应用上。

Redis JDBC

1、导入依赖&#xff1a; <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.4.3</version></dependency> 2、连接JDBC public class JedisDemo05 {public static void main(String[]…

成为AI产品经理——AI产品经理工作全流程

一、业务背景 背景&#xff1a;日常排球训练&#xff0c;中考排球项目和排球体测项目耗费大量人力成本和时间成本。 目标&#xff1a;开发一套用于实时检测排球运动并进行排球垫球计数和姿势分析的软件。 二、产品工作流程 我们这里对于产品工作流程的关键部分进行讲解&…

「Docker」如何在苹果电脑上构建简单的Go云原生程序「MacOS」

介绍 使用Docker开发Golang云原生应用程序&#xff0c;使用Golang服务和Redis服务 注&#xff1a;写得很详细 为方便我的朋友可以看懂 环境部署 确保已经安装Go、docker等基础配置 官网下载链接直达&#xff1a;Docker官网下载 Go官网下载 操作步骤 第一步 创建一个…