Android中AsyncTask使用详解

Android中AsyncTask使用详解在 Android 中我们可以通过 Thread Handler 实现多线程通信 一种经典的使用场景是 在新线程中进行耗时操作 当任务完成后通过 Handler 向主线程发送 Message 这样主线程的 Handler 在收到该 Message 之后就可以进行更新 UI 的操作 上述场景中需要分别在 Thread 和 Handler 中编写代码逻辑 为了使得代码更加统一 我们可以使用 AsyncTask 类 AsyncTask 是 And

在Android中我们可以通过Thread+Handler实现多线程通信,一种经典的使用场景是:在新线程中进行耗时操作,当任务完成后通过Handler向主线程发送Message,这样主线程的Handler在收到该Message之后就可以进行更新UI的操作。上述场景中需要分别在Thread和Handler中编写代码逻辑,为了使得代码更加统一,我们可以使用AsyncTask类。

AsyncTask是Android提供的一个助手类,它对Thread和Handler进行了封装,方便我们使用。Android之所以提供AsyncTask这个类,就是为了方便我们在后台线程中执行操作,然后将结果发送给主线程,从而在主线程中进行UI更新等操作。在使用AsyncTask时,我们无需关注Thread和Handler,AsyncTask内部会对其进行管理,这样我们就只需要关注于我们的业务逻辑即可。

  • Params表示用于AsyncTask执行任务的参数的类型
  • Progress表示在后台线程处理的过程中,可以阶段性地发布结果的数据类型
  • Result表示任务全部完成后所返回的数据类型

我们通过调用AsyncTask的execute()方法传入参数并执行任务,然后AsyncTask会依次调用以下四个方法:

  • onPreExecute
    该方法的签名如下所示:
    这里写图片描述
    该方法有MainThread注解,表示该方法是运行在主线程中的。在AsyncTask执行了execute()方法后就会在UI线程上执行onPreExecute()方法,该方法在task真正执行前运行,我们通常可以在该方法中显示一个进度条,从而告知用户后台任务即将开始。






  • doInBackground
    该方法的签名如下所示:
    这里写图片描述
    该方法有WorkerThread注解,表示该方法是运行在单独的工作线程中的,而不是运行在主线程中。doInBackground会在onPreExecute()方法执行完成后立即执行,该方法用于在工作线程中执行耗时任务,我们可以在该方法中编写我们需要在后台线程中运行的逻辑代码,由于是运行在工作线程中,所以该方法不会阻塞UI线程。该方法接收Params泛型参数,参数params是Params类型的不定长数组,该方法的返回值是Result泛型,由于doInBackgroud是抽象方法,我们在使用AsyncTask时必须重写该方法。在doInBackground中执行的任务可能要分解为好多步骤,每完成一步我们就可以通过调用AsyncTask的publishProgress(Progress…)将阶段性的处理结果发布出去,阶段性处理结果是Progress泛型类型。当调用了publishProgress方法后,处理结果会被传递到UI线程中,并在UI线程中回调onProgressUpdate方法,下面会详细介绍。根据我们的具体需要,我们可以在doInBackground中不调用publishProgress方法,当然也可以在该方法中多次调用publishProgress方法。doInBackgroud方法的返回值表示后台线程完成任务之后的结果。






  • onProgressUpdate
    上面我们知道,当我们在doInBackground中调用publishProgress(Progress…)方法后,就会在UI线程上回调onProgressUpdate方法,该方法的方法签名如下所示:
    这里写图片描述
    该方法也具有MainThread注解,表示该方法是在主线程上被调用的,且传入的参数是Progress泛型定义的不定长数组。如果在doInBackground中多次调用了publishProgress方法,那么主线程就会多次回调onProgressUpdate方法。






  • onPostExecute
    该方法的签名如下所示:
    这里写图片描述
    该方法也具有MainThread注解,表示该方法是在主线程中被调用的。当doInBackgroud方法执行完毕后,就表示任务完成了,doInBackgroud方法的返回值就会作为参数在主线程中传入到onPostExecute方法中,这样就可以在主线程中根据任务的执行结果更新UI。






下面我们就以下载多个文件的示例演示AsyncTask的使用过程。

布局文件如下所示:

 
  
    "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:paddingLeft= 
   "@dimen/activity_horizontal_margin" android:paddingRight= 
   "@dimen/activity_horizontal_margin" android:paddingTop= 
   "@dimen/activity_vertical_margin" android:paddingBottom= 
   "@dimen/activity_vertical_margin" tools:context= 
   ".MainActivity"> 
    
  

界面上有一个“开始下载”的按钮,点击该按钮即可通过AsyncTask下载多个文件,对应的Java代码如下所示:

package com.ispring.asynctask; import android.app.Activity; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; public class MainActivity extends Activity implements Button.OnClickListener { 
    TextView textView = null; Button btnDownload = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView)findViewById(R.id.textView); btnDownload = (Button)findViewById(R.id.btnDownload); Log.i("iSpring", "MainActivity -> onCreate, Thread name: " + Thread.currentThread().getName()); } @Override public void onClick(View v) { //要下载的文件地址 String[] urls = { "http://blog.csdn.net/iispring/article/details/", "http://blog.csdn.net/iispring/article/details/", "http://blog.csdn.net/iispring/article/details/", "http://blog.csdn.net/iispring/article/details/", "http://blog.csdn.net/iispring/article/details/" }; DownloadTask downloadTask = new DownloadTask(); downloadTask.execute(urls); } //public abstract class AsyncTask 
    //在此例中,Params泛型是String类型,Progress泛型是Object类型,Result泛型是Long类型 private class DownloadTask extends AsyncTask<String, Object, Long> { 
    @Override protected void onPreExecute() { Log.i("iSpring", "DownloadTask -> onPreExecute, Thread name: " + Thread.currentThread().getName()); super.onPreExecute(); btnDownload.setEnabled(false); textView.setText("开始下载..."); } @Override protected Long doInBackground(String... params) { Log.i("iSpring", "DownloadTask -> doInBackground, Thread name: " + Thread.currentThread().getName()); //totalByte表示所有下载的文件的总字节数 long totalByte = 0; //params是一个String数组 for(String url: params){ //遍历Url数组,依次下载对应的文件 Object[] result = downloadSingleFile(url); int byteCount = (int)result[0]; totalByte += byteCount; //在下载完一个文件之后,我们就把阶段性的处理结果发布出去 publishProgress(result); //如果AsyncTask被调用了cancel()方法,那么任务取消,跳出for循环 if(isCancelled()){ break; } } //将总共下载的字节数作为结果返回 return totalByte; } //下载文件后返回一个Object数组:下载文件的字节数以及下载的博客的名字 private Object[] downloadSingleFile(String str){ Object[] result = new Object[2]; int byteCount = 0; String blogName = ""; HttpURLConnection conn = null; try{ URL url = new URL(str); conn = (HttpURLConnection)url.openConnection(); InputStream is = conn.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; int length = -1; while ((length = is.read(buf)) != -1) { baos.write(buf, 0, length); byteCount += length; } String respone = new String(baos.toByteArray(), "utf-8"); int startIndex = respone.indexOf(""); if(startIndex > 0){ startIndex += 7; int endIndex = respone.indexOf(""); if(endIndex > startIndex){ //解析出博客中的标题 blogName = respone.substring(startIndex, endIndex); } } }catch(MalformedURLException e){ e.printStackTrace(); }catch(IOException e){ e.printStackTrace(); }finally { if(conn != null){ conn.disconnect(); } } result[0] = byteCount; result[1] = blogName; return result; } @Override protected void onProgressUpdate(Object... values) { Log.i("iSpring", "DownloadTask -> onProgressUpdate, Thread name: " + Thread.currentThread().getName()); super.onProgressUpdate(values); int byteCount = (int)values[0]; String blogName = (String)values[1]; String text = textView.getText().toString(); text += "\n博客《" + blogName + "》下载完成,共" + byteCount + "字节"; textView.setText(text); } @Override protected void onPostExecute(Long aLong) { Log.i("iSpring", "DownloadTask -> onPostExecute, Thread name: " + Thread.currentThread().getName()); super.onPostExecute(aLong); String text = textView.getText().toString(); text += "\n全部下载完成,总共下载了" + aLong + "个字节"; textView.setText(text); btnDownload.setEnabled(true); } @Override protected void onCancelled() { Log.i("iSpring", "DownloadTask -> onCancelled, Thread name: " + Thread.currentThread().getName()); super.onCancelled(); textView.setText("取消下载"); btnDownload.setEnabled(true); } } }

下面对以上代码进行一下说明。

  1. 我们在MainActivity中定义了内部类DownloadTask,DownloadTask继承自AsyncTask,在该例中,Params泛型是String类型,Progress泛型是Object类型,Result泛型是Long类型。
  2. 我们定义了一个Url字符串数组,将该数组传递给AsyncTask的execute方法,用于异步执行task。
  3. 在执行了downloadTask.execute(urls)之后,AsyncTask会自动回调onPreExecute方法,在该方法中我们将textView设置为“开始下载…”几个字,告知用户即将执行下载操作。通过控制台输出我们也可以看出该方法是在主线程中执行的。
  4. 在执行了onPreExecute方法之后,AsyncTask会回调doInBackground方法,该方法中的输入参数是String类型的不定长数组,此处的String就对应着Params泛型类型,我们在该方法中遍历Url数组,依次下载对应的文件,当我们下载完一个文件,就相当于我们阶段性地完成了一部分任务,我们就通过调用publishProgress方法将阶段性处理结果发布出去。在此例中我们将阶段性的处理结果定义为Object类型,即Progress泛型类型。通过控制台输出我们可以看出doInBackground方法是运行在新的工作线程”AsyncTask #1”中的,AsyncTask的工作线程都是以”AsyncTask #”然后加上数字作为名字。当所有文件下载完成后,我们就可以通过totalSize返回所有下载的字节数,返回值类型为Long,对应着AsyncTask中的Result泛型类型。
  5. 在doInBackground方法中,每当下载完一个文件,我们就会调用publishProgress方法发布阶段性结果,之后AsyncTask会回调onProgressUpdate方法,在此例中,onProgressUpdate的参数为Object类型,对应着AsyncTask中的Progress泛型类型。通过控制台输出我们可以发现,该方法是在主线程中调用的,在该方法中我们会通过textView更新UI,告知用户哪个文件下载完成了,这样用户体验相对友好。
  6. 在整个doInBackground方法执行完毕后,AsyncTask就会回调onPostExecute方法,在该方法中我们再次通过textView更新UI告知用户全部下载任务完成了。
  7. 在通过execute方法执行了异步任务之后,可以通过AsyncTask的cancel方法取消任务,取消任务后AsyncTask会回调onCancelled方法,这样不会再调用onPostExecute方法。

在使用Android的过程中,有以下几点需要注意:

  • AsyncTask的实例必须在主线程中创建。
  • AsyncTask的execute方法必须在主线程中调用。
  • onPreExecute()、onPostExecute(Result),、doInBackground(Params…) 和 onProgressUpdate(Progress…)这四个方法都是回调方法,Android会自动调用,我们不应自己调用。
  • 对于一个AsyncTack的实例,只能执行一次execute方法,在该实例上第二次执行execute方法时就会抛出异常。

通过上面的示例,大家应该熟悉了AsyncTask的使用流程。我们上面提到,对于某个AsyncTask实例,只能执行一次execute方法,如果我们想并行地执行多个任务怎么办呢?我们可以考虑实例化多个AsyncTask实例,然后分别调用各个实例的execute方法,为了探究效果,我们将代码更改如下所示:

public void onClick(View v) { //要下载的文件地址 String[] urls = { "http://blog.csdn.net/iispring/article/details/", "http://blog.csdn.net/iispring/article/details/", "http://blog.csdn.net/iispring/article/details/", "http://blog.csdn.net/iispring/article/details/", "http://blog.csdn.net/iispring/article/details/" }; DownloadTask downloadTask1 = new DownloadTask(); downloadTask1.execute(urls); DownloadTask downloadTask2 = new DownloadTask(); downloadTask2.execute(urls); }

我们观察一下控制台的输出结果,可以发现对于downloadTask1,doInBackground方法是运行在线程“AsyncTask #1”中的;对于downloadTask2,doInBackground方法是运行在线程”AsyncTask #2”中的,此时我们可能会认为太好了,两个AsyncTask实例分别在不同的线程中运行,实现了并行处理。此处真的是并行运行的吗?

我们自己观察控制台输出就可以发现,downloadTask1的doInBackground方法执行后,下载了五个文件,并五次触发了onProgressUpdate,在这之后才执行downloadTask2的doInBackground方法。我们对比上面的GIF图也可以发现,在downloadTask1按照顺序下载完五篇文章之后,downloadTask2才开始按照顺序下载五篇文章。综上所述,我们可以知道,默认情况下如果创建了AsyncTask创建了多个实例,并同时执行实例的各个execute方法,那么这些实例的execute方法并不是并行执行的,是串行执行的,即在第一个实例的doInBackground完成任务后,第二个实例的doInBackgroud方法才会开始执行,然后再执行第三个实例的doInBackground方法… 那么你可能会问,不对啊,上面downloadTask1是运行在”AsyncTask #1”线程中的,downloadTask2是运行在”AsyncTask #2”线程中的,这明明是两个线程啊!其实AsyncTask为downloadTask1开辟了名为”AsyncTask #1”的工作线程,在其完成了任务之后可能就销毁了,然后AsyncTask又为downloadTask2开辟了名为”AsyncTask #2”的工作线程。

AsyncTask在最早的版本中用一个单一的后台线程串行执行多个AsyncTask实例的任务,从Android 1.6(DONUT)开始,AsyncTask用线程池并行执行异步任务,但是从Android 3.0(HONEYCOMB)开始为了避免并行执行导致的常见错误,AsyncTask又开始默认用单线程作为工作线程处理多个任务。

从Android 3.0开始AsyncTask增加了executeOnExecutor方法,用该方法可以让AsyncTask并行处理任务,该方法的方法签名如下所示:

public final AsyncTask<Params, Progress, Result> executeOnExecutor (Executor exec, Params... params)

第一个参数表示exec是一个Executor对象,为了让AsyncTask并行处理任务,通常情况下我们此处传入AsyncTask.THREAD_POOL_EXECUTOR即可,AsyncTask.THREAD_POOL_EXECUTOR是AsyncTask中内置的一个线程池对象,当然我们也可以传入我们自己实例化的线程池对象。第二个参数params表示的是要执行的任务的参数。

通过executeOnExecutor方法并行执行任务的示例代码如下所示:

public void onClick(View v) { if(Build.VERSION.SDK_INT >= 11){ String[] urls = { "http://blog.csdn.net/iispring/article/details/", "http://blog.csdn.net/iispring/article/details/", "http://blog.csdn.net/iispring/article/details/", "http://blog.csdn.net/iispring/article/details/", "http://blog.csdn.net/iispring/article/details/" }; DownloadTask downloadTask1 = new DownloadTask(); downloadTask1.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, urls); DownloadTask downloadTask2 = new DownloadTask(); downloadTask2.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, urls); } }

我们实例化了两个DownloadTask的实例,然后执行了这两个实例的executeOnExecutor方法,并将AsyncTask.THREAD_POOL_EXECUTOR作为Executor传入,二者都接收同样的Url数组作为任务执行的参数。

通过控制台的输出结果我们可以看到,在downloadTask1执行了doInBackground方法后,downloadTask2也立即执行了doInBackground方法。并且通过程序运行完的UI界面可以看到在一个DownloadTask实例下载了一篇文章之后,另一个DownloadTask实例也立即下载了一篇文章,两个DownloadTask实例交叉按顺序下载文件,可以看出这两个AsyncTask的实例是并行执行的。

如果大家想了解AsyncTask的工作原理,可参见另一篇博文《源码解析Android中AsyncTask的工作原理》 。

希望本文对大家使用AsyncTask的使用有所帮助!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/218679.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午11:42
下一篇 2026年3月17日 下午11:43


相关推荐

  • OpenClaw 安全实战指南:构建个人 AI 助手的信任边界

    OpenClaw 安全实战指南:构建个人 AI 助手的信任边界

    2026年3月13日
    2
  • 华为交换机关闭网口_关闭端口的命令 怎么开启华为交换机关闭端口,命令谁知道啊。…[通俗易懂]

    华为交换机关闭网口_关闭端口的命令 怎么开启华为交换机关闭端口,命令谁知道啊。…[通俗易懂]1025端口和UDP13137、138、445端口。想和你喝酒是假的,余生都想醉在你心头却是真的。怎样用命令关闭和开启80端口?电脑点击开始按钮,点击控制面板。遇见的一部分叫做缘分,另一部分,小编们叫它劫数,有命中注定就有在劫难逃。进入控制面板页面,点击windows防火墙。进入windows防火墙界面,点击高级设置。小编们都在淡化这段感情,你选择新欢,小编选择时间。进入防火墙高级管理…

    2022年7月20日
    56
  • 带你简单了解音频放大电路

    带你简单了解音频放大电路音频放大电路简介能够为负载提供足够大的功率放大倍数的电路称之为功率放大电路,简称功放。音频功率放大器的基本功能是把前级送来的声频信号不失真地加以放大,输出足够的功率去驱动负载(扬声器)发出优美的声音。在音频电路中,往往要求放大电路的输出级能输出足够大的功率去驱动扬声器等负载。音频放大电路在各种音频设备上被广泛使用。因此放大器一般包括前置放大和功率放大两部分,前者以放大信号振幅为目的,因而又称电压…

    2022年6月6日
    38
  • wrk服务器性能测试

    wrk服务器性能测试转载地址 http zjumty iteye com blog 测试先行是软件系统质量保证的有效手段 在单元测试方面 我们有非常成熟的 xUnit 方案 在集成测试方面 我们 selenium 等自动化方案 在性能测试方面也有很多成熟的工具 比如 LoadRunner Jmeter 等 但是很多工具都是给专门的性能测试人员使用的 功能虽然强大 但是安装和操

    2026年3月17日
    2
  • 解决VSCode出现“launch: program …… does not exist”的问题

    解决VSCode出现“launch: program …… does not exist”的问题一 问题描述 C 源文件进行调试 按下 F5 出现第一个弹窗 点击 仍要调试 出现第二个弹窗 二 问题解决 1 找到 cpp 配置文件中的 tasks json 和 launch json2 使两者的 label 内容相同 如都为 Compile 3 更改 task json 的 args 中的参数 fileDirname fileBasename 注 task 中的 fileDirname fileBasename 用于指定输出文

    2026年3月17日
    2
  • Git修改用户名和邮箱

    Git修改用户名和邮箱最近在提交代码时发现用户名和邮箱很长 感觉很奇怪 于是通过 Git 命令修改了一下用户名 用户名截图如下 修改步骤如下 1 进入 Git 的安装目录 找到 git git cmd exe 例如我的目录是 D softwore git Git 目录截图如下 或者配置环境变量进行修改 环境变量配置为在 PATH 后面加上 git 的 bin 目录 D softwore git Git bin 截图如下

    2026年3月18日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号