IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android AsyncTask(线程间通信) -> 正文阅读

[移动开发]Android AsyncTask(线程间通信)

1.AsyncTask
在Android中我们可以通过Thread+Handler实现多线程通信,一种经典的使用场景是:在新线程中进行耗时操作,当任务完成后通过Handler向主线程发送Message,这样主线程的Handler在收到该Message之后就可以进行更新UI的操作。上述场景中需要分别在Thread和Handler中编写代码逻辑,为了使得代码更加统一,我们可以使用AsyncTask类。
AsyncTask是Android提供的一个助手类,它对Thread和Handler进行了封装,方便我们使用。Android之所以提供AsyncTask这个类,就是为了方便我们在后台线程中执行操作,然后将结果发送给主线程,从而在主线程中进行UI更新等操作。在使用AsyncTask时,我们无需关注Thread和Handler,AsyncTask内部会对其进行管理,这样我们就只需要关注于我们的业务逻辑即可。

AsyncTask有四个重要的回调方法,分别是:onPreExecute()、doInBackground()、onProgressUpdate()和onPostExecute()。这四个方法会在AsyncTask的不同时期进行自动调用,我们只需要实现这几个方法的内部逻辑即可。这四个方法的一些参数和返回值都是基于泛型的,而且泛型的类型还不一样,所以在AsyncTask的使用中会遇到三种泛型参数:Params, Progress和Result,如下图所示:
在这里插入图片描述
①Params表示用于AsyncTask执行任务的参数的类型。
②Progress表示在后台线程处理的过程中,可以阶段性地发布结果的数据类型。
③Result表示任务全部完成后所返回的数据类型。

我们通过调用AsyncTask的execute()方法传入参数并执行任务,然后AsyncTask会依次调用以下四个方法:
①onPreExecute
在这里插入图片描述
该方法运行在主线程。在AsyncTask执行了execute()方法后就会在UI线程上执行onPreExecute()方法,该方法在task真正执行前运行,我们通常可以在该方法中显示一个进度条,从而告知用户后台任务即将开始。
②doInBackground
在这里插入图片描述
该方法运行在单独的工作线程,而不是运行在主线程中。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方法:
在这里插入图片描述
该方法在主线程上被调用,且传入的参数是Progress泛型定义的不定长数组。如果在doInBackground中多次调用了publishProgress方法,那么主线程就会多次回调onProgressUpdate方法。

④onPostExecute
该方法在主线程中被调用。当doInBackgroud方法执行完毕后,就表示任务完成了,doInBackgroud方法的返回值就会作为参数在主线程中传入到onPostExecute方法中,这样就可以在主线程中根据任务的执行结果更新UI。

2.使用举例
我们以下载多个文件的示例演示AsyncTask的使用过程。
界面上有一个“开始下载”的按钮,点击该按钮即可通过AsyncTask下载多个文件,对应的Java代码如下所示:
public class MainActivity extends Activity {
TextView textView = null;
Button btnDownload = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i(“iSpring”, "MainActivity -> onCreate, Thread name: " + Thread.currentThread().getName());
// 开始下载
btnDownload.setonClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String[] urls = { “http://blog.csdn.net/iispring/article/details/47115879”,
“http://blog.csdn.net/iispring/article/details/47180325”,
“http://blog.csdn.net/iispring/article/details/47300819”,
“http://blog.csdn.net/iispring/article/details/47320407”,
“http://blog.csdn.net/iispring/article/details/47622705”
};
DownloadTask downloadTask = new DownloadTask();
downloadTask.execute(urls);
}
});
}

//在此例中,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("< title>");
if(startIndex > 0){
startIndex += 7;
int endIndex = respone.indexOf("</ title>");
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);
}
}
}
点击下载按钮后,界面如下所示:
在这里插入图片描述
打印log如下:
在这里插入图片描述
下面对以上代码进行一下说明。
①在MainActivity中定义了内部类DownloadTask,DownloadTask继承自AsyncTask,在该例中,Params泛型是String类型,Progress泛型是Object类型,Result泛型是Long类型。
②定义了一个Url字符串数组,将该数组传递给AsyncTask的execute方法,用于异步执行task。
③在执行了downloadTask.execute(urls)之后,AsyncTask会自动回调onPreExecute方法,在该方法中将textView设置为“开始下载…”几个字,告知用户即将执行下载操作。通过控制台输出我们也可以看出该方法是在主线程中执行的。
④在执行了onPreExecute方法之后,AsyncTask会回调doInBackground方法,该方法中的输入参数是String类型的不定长数组,此处的String就对应着Params泛型类型,我们在该方法中遍历Url数组,依次下载对应的文件,当我们下载完一个文件,就相当于我们阶段性地完成了一部分任务,我们就通过调用publishProgress方法将阶段性处理结果发布出去。在此例中我们将阶段性的处理结果定义为Object类型,即Progress泛型类型。通过控制台输出我们可以看出doInBackground方法是运行在新的工作线程”AsyncTask #1”中的,AsyncTask的工作线程都是以”AsyncTask #”然后加上数字作为名字。当所有文件下载完成后,我们就可以通过totalSize返回所有下载的字节数,返回值类型为Long,对应着AsyncTask中的Result泛型类型。
⑤在doInBackground方法中,每当下载完一个文件,我们就会调用publishProgress方法发布阶段性结果,之后AsyncTask会回调onProgressUpdate方法,在此例中,onProgressUpdate的参数为Object类型,对应着AsyncTask中的Progress泛型类型。通过控制台输出我们可以发现,该方法是在主线程中调用的,在该方法中我们会通过textView更新UI,告知用户哪个文件下载完成了,这样用户体验相对友好。
⑥在整个doInBackground方法执行完毕后,AsyncTask就会回调onPostExecute方法,在该方法中我们再次通过textView更新UI告知用户全部下载任务完成了。
⑦在通过execute方法执行了异步任务之后,可以通过AsyncTask的cancel方法取消任务,取消任务后AsyncTask会回调onCancelled方法,这样不会再调用onPostExecute方法。

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

我们上面提到,对于某个AsyncTask实例,只能执行一次execute方法,如果我们想并行地执行多个任务怎么办呢?我们可以考虑实例化多个AsyncTask实例,然后分别调用各个实例的execute方法,为了探究效果,我们将代码更改如下所示:
DownloadTask downloadTask1 = new DownloadTask();
downloadTask1.execute(urls);

DownloadTask downloadTask2 = new DownloadTask();
downloadTask2.execute(urls);
修改后,在单击了按钮之后,实例化了两个DownloadTask,并分别执行其execute方法,运行后界面如下所示:
在这里插入图片描述
打印log如下:
在这里插入图片描述
我们观察一下控制台的输出结果,可以发现对于downloadTask1,doInBackground方法是运行在线程“AsyncTask #1”中的;对于downloadTask2,doInBackground方法是运行在线程”AsyncTask #2”中的,此时我们可能会认为太好了,两个AsyncTask实例分别在不同的线程中运行,实现了并行处理。但是,此处真的是并行运行的吗?
我们自己观察控制台输出就可以发现,downloadTask1的doInBackground方法执行后,下载了五个文件,并五次触发了onProgressUpdate,在这之后才执行downloadTask2的doInBackground方法。对比两次的效果图也可以发现,在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方法并行执行任务的示例代码如下所示:
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数组作为任务执行的参数。
点击下载按钮后,运行完的界面如下所示:
在这里插入图片描述
打印log如下:
在这里插入图片描述
通过log可以看到,在downloadTask1执行了doInBackground方法后,downloadTask2也立即执行了doInBackground方法。并且通过程序运行完的UI界面可以看到在一个DownloadTask实例下载了一篇文章之后,另一个DownloadTask实例也立即下载了一篇文章,两个DownloadTask实例交叉按顺序下载文件,可以看出这两个AsyncTask的实例是并行执行的。

3.AsyncTask源码解析
AsyncTask类一开始定义了一些字段,如下所示:
private static final String LOG_TAG = “AsyncTask”;
//CPU_COUNT为手机中的CPU核数
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//将手机中的CPU核数加1作为AsyncTask所使用的线程池的核心线程数的大小
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
//将CPU_COUNT*2 + 1作为AsyncTask所使用的线程池的最大线程数的大小
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;

//实例化线程工厂,sThreadFactory用于在后面创建线程池
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
//mCount为AtomicInteger类型,AtomicInteger是一个提供原子操作的Integer类,确保了其getAndIncrement方法是线程安全的
private final AtomicInteger mCount = new AtomicInteger(1);
//重写newThread方法的目的是为了将新增线程的名字以"AsyncTask #"标识
public Thread newThread(Runnable r) {
return new Thread(r, “AsyncTask #” + mCount.getAndIncrement());
}
};

//实例化阻塞式队列BlockingQueue,队列中存放Runnable,容量为128
private static final BlockingQueue< Runnable> sPoolWorkQueue = new LinkedBlockingQueue< Runnable>(128);

//根据上面定义的参数实例化线程池
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

AsyncTask初始化了一些参数,并用这些参数实例化了一个线程池THREAD_POOL_EXECUTOR,需要注意的是该线程池被定义为public static final,由此我们可以看出AsyncTask内部维护了一个静态的线程池,默认情况下,AsyncTask的实际工作就是通过该THREAD_POOL_EXECUTOR完成的。
在执行完上面的代码后,AsyncTask又有如下一条语句:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
上面的代码实例化了一个SerialExecutor类型的实例SERIAL_EXECUTOR,它也是public static final的。SerialExecutor是AsyncTask的一个内部类,代码如下所示:
//SerialExecutor实现了Executor接口中的execute方法,该类用于串行执行任务,即一个接一个地执行任务,而不是并行执行任务
private static class SerialExecutor implements Executor {
//mTasks是一个维护Runnable的双端队列,ArrayDeque没有容量限制,其容量可自增长
final ArrayDeque< Runnable> mTasks = new ArrayDeque< Runnable>();
//mActive表示当前正在执行的任务Runnable
Runnable mActive;

public synchronized void execute(final Runnable r) {
//execute方法会传入一个Runnable类型的变量r,然后我们会实例化一个Runnable类型的匿名内部类对r进行封装,通过队列的offer方法将封装后的Runnable添加到队尾
mTasks.offer(new Runnable() {
public void run() {
try {
//执行r的run方法,开始执行任务
//此处r的run方法是在线程池中执行的
r.run();
} finally {
//当任务执行完毕的时候,通过调用scheduleNext方法执行下一个Runnable任务
scheduleNext();
}
}
});
//只有当前没有执行任何任务时,才会立即执行scheduleNext方法
if (mActive == null) {
scheduleNext();
}
}

protected synchronized void scheduleNext() {
//通过mTasks的poll方法进行出队操作,删除并返回队头的Runnable,将返回的Runnable赋值给mActive,如果不为空,那么就将其作为参数传递给THREAD_POOL_EXECUTOR的execute方法进行执行
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute( mActive);
}
}
}
通过以上代码可以知道:
①SerialExecutor实现了Executor接口中的execute方法,该类用于串行执行任务,即一个接一个地执行任务,而不是并行执行任务。
②SerialExecutor内部维护了一个存放Runnable的双端队列mTasks。当执行SerialExecutor的execute方法时,会传入一个Runnable变量r,但是mTasks并不直接存储r,而是又新new了一个匿名Runnable对象,其内部会调用r,这样就对r进行了封装,将该封装后的Runnable对象通过队列的offer方法入队,添加到mTasks的队尾。
③SerialExecutor内部通过mActive存储着当前正在执行的任务Runnable。当执行SerialExecutor的execute方法时,首先会向mTasks的队尾添加进一个Runnable。然后判断如果mActive为null,即当前没有任务Runnable正在运行,那么就会执行scheduleNext()方法。当执行scheduleNext方法的时候,会首先从mTasks中通过poll方法出队,删除并返回队头的Runnable,将返回的Runnable赋值给mActive;如果不为空,就将其作为参数传递给THREAD_POOL_EXECUTOR的execute方法进行执行。由此可以看出SerialExecutor实际上是通过之前定义的线程池THREAD_POOL_EXECUTOR进行实际的处理的。
④当将mTasks中的Runnable作为参数传递给THREAD_POOL_EXECUTOR执行execute方法时,会在线程池的工作线程中执行匿名内部类Runnable中的try-finally代码段,即先在工作线程中执行r.run()方法去执行任务,无论任务r正常完成还是抛出异常,都会在finally中执行scheduleNext方法,用于执行mTasks中的下一个任务。从而在此处我们可以看出SerialExecutor是一个接一个执行任务,是串行执行任务,而不是并行执行。

AsyncTask内部定义了一个Status枚举类型,如下所示:
public enum Status {
//PENDING表示还没有开始执行任务
PENDING,
//RUNNING表示已经开始执行任务
RUNNING,
//FINISHED表示任务已经执行完成或被取消了,总之onPostExecute方法已经被调用了
FINISHED,
}
一个AsyncTask正常情况下会经历PENDING->RUNNING->FINISHED三个状态。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-10-24 15:02:38  更:2021-10-24 15:02:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:10:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码