您的位置:首页 > 移动开发 > Android开发

Android 异步任务:AsyncTask 学习解析及基本使用 (Android单线程模式)

2016-10-09 18:39 791 查看
(在此声明:以下部分内容来自于慕课网中“AsyncTask概述”讲解视频中个人总结及书籍总结)

提示:如果对AsyncTask机制熟练于心,想要了解Android中源码底层实现来拔高,这里有源码详细分析:http://blog.csdn.net/itermeng/article/details/52159831

一 .Android单线程模式

Android应用程序中大部分代码在一个组件(比如活动或服务)的上下文中运行。

大部分时间, 一个Android进程中仅进行一个线程,称为主线程。各个组件之间共享此主线程的影响,可是Android不可能只有单一的主线程,将所有的逻辑放在主线程中实现。类似于一些耗时的操作:请求网络、读取文件……若全部放在主线程中执行,会造成后面任务的阻塞。一旦阻塞的时间过长,Android系统检测到就会ANRApplication Not Responding 应用程序无响应)。



二. 初识AsyncTask

1. 为什么要异步任务?

(1)Android单线程模型

(2)耗时操作应该放到非主线程中执行

如上所示,Android中只有主线程才能对UI线程进行操作,这样做的好处是保证了UI的准确性和稳定性,避免多线程同时对UI操作,造成UI混乱。AndroidUI线程主要负责处理用户的按键事件、用户触屏事件及屏幕绘图事件等,因此开发者的其它操作不应该、也不能阻塞 UI线程,否则会引发ANR

所以这些耗时操作应该放到非主线程中执行,比如请求网络时出现问题,也不会影响到主线程。这样即保证了Android的单线程模型,又避免了系统出现ANR。

为了解决子线程不能更新UI组件的问题,Android提供了如下几种解决方案:

(1)使用Hanlder实现线程之间的通信

(2) Activity.runOnUiThread(Runnable)

(3) View.Post(Runnable)

(4) View.PostDelayed(Runnable, long )

暂不考虑Hanlder的实例,后三种方式略显繁琐,而异步任务(AsyncTask)可进一步简化这种操作,更轻量,适用于简单异步处理,不需要借助线程和Handler

提到异步任务,最先想到的是线程,而Android也为我们提供了主线程与其它线程的通信机制,更赞的是Android已经封装好了AsyncTask 类,它可以方便的实现异步任务。

2. AsyncTask作用

(1)子线程中更新UI

(2)封装、简化异步操作

我们要实现异步任务 通常使用 线程和线程池,这里同时涉及到了线程的同步和管理,当线程结束时,还需要一个handler通知主线程进行UI更新,这不免增加了代码量,使逻辑复杂,而AsyncTask封装好,只需要简单重写它的方法,在对应的方法中进行UI更新。

三. AsyncTask解析

1. AsyncTask的参数

AsyncTask< Params ,Progress,Result>是一个抽象类,通常用于被继承,继承AsyncTask需要指定以下三个泛型参数:

(1)Params:启动任务执行的输入参数的类型。

(2)Progress:后台任务完成的进度值的类型。

(3)Result:后台执行任务完成后返回结果的类型。

2. AsyncTask的实现方法

2.1. 继承AsyncTask,并为三个泛型参数指定类型,若某个参数无需求则可写void。

2.2. 根据需要,实现父类AsyncTask的方法:

(1)onPreExecute():该方法最先被调用,执行在主线程,其次是doInBackground()方法(执行后台耗时操作)。通常用于完成一些初始化的准备工作,比如界面上进度条显示正在加载。

(2)doInBackground(Params…):【重点】该方法规定必须重写,在子线程中执行,所以重写该方法就是为了后台线程将要完成的任务(例如耗时操作:请求网络……)。方法中可以调用onProgressUpdate()来更新任务的执行进度(如进度条),该方法执行完后,会回调onPostExecute(),并传入结果result

(3)onPostExecute(Result result):doInBackground()完成后,系统会回调此方法,并将doInBackground()方法的返回值传过来。该方法运行在主线程,一般用来更新UI界面。

(4)onProgressUpdate(Progress… values):doInBackground()方法中调用此方法,在主线程执行,一般用来更新任务的执行进度(如进度条)。

2.3 遵循规则

调用AsyncTask子类实例的 execute(Params… params)开始执行耗时任务,需遵循以下规则:

(1)必须在UI线程(主线程)中创建AsyncTask的实例。

(2)必须在UI线程(主线程)中调用AsyncTaskexecute()方法。

(3)每个AsyncTask只能被执行一次,多次调用execute()方法会抛异常。

(4)以上介绍实现AsyncTask类的四个方法,方法之间存在互相调用联系,无需我们手动调用,Android系统已封装好!我们只需重写实现需要的方法即可。

以上的规则必须遵循,如若有疑惑的话,可查看Android中AsyncTask源码部分,即可明白以上道理。

四. 通过实例(进度条)进一步解析AsyncTask

一般写的例子就是在doInBackground()方法中进行网络请求,在被回调的onPostExecute()方法中更新UI界面(也可以在onProgressUpdate()方法更新加载中的进度条)。这里为了继续挖掘AsyncTask的性质,就不举这个例子了,网上也有许多,这里举另外一个:进度条的更新。

要求:实现一个进度条,人工在doInBackground()方法中模拟进度更新,调用onProgressUpdate()方法显示加载过程中的进度更新。

布局代码:

activity_progress_bar.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:padding="10dp"
tools:context="com.gym.progressbarasunctask.ProgressBarTest">

<ProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/progressBar"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />

</RelativeLayout>


逻辑代码:

ProgressBarTest.java

package com.gym.progressbarasunctask;

import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import android.widget.ProgressBar;

public class ProgressBarTest extends AppCompatActivity {

private ProgressBar mProgressBar;
private MyAsyncTask myAsyncTask;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_progress_bar);
mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
//启动异步任务
myAsyncTask = new MyAsyncTask();
myAsyncTask.execute();
}

class MyAsyncTask extends AsyncTask<Void,Integer,Void>{

@Override
protected Void doInBackground(Void... voids) {
//模拟进度更新
for (int i=0; i<100; i++){
publishProgress(i);//传入进度,方便进度条更新时取值
try {
Thread.sleep(100);//延缓更新进度
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}

@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
//获取进度更新值,UI线程中获取子线程更新进度
mProgressBar.setProgress(values[0]);
}
}
}


显示情况:



1. 生命周期方法与AsyncTask

以上程序问题:第一次启动异步任务,进度条进行更新过程中退出,第二次再进入时需要待会一会,进度条才会重新开始加载,为什么?

分析: 其实AsyncTask机制底层实现是通过一个线程池,当一个线程没有执行完毕,后面的线程是无法进行的。所以退出来后第二次启动异步任务时会等待第一次执行完毕(即我们模拟的进度更新 ——for循环 执行完毕),才会开始加载进度条更新。

解决: 令AsyncTask的生命周期与对应Activity的保持一致!

ProgressBarTest.java文件中添加onPause()方法,方法体中if判断AsyncTask的状态部位空且等于“任务正在执行”的状态,满足条件就需要cancel掉该AsyncTask

@Override
protected void onPause() {
super.onPause();
if(myAsyncTask != null &&
myAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
myAsyncTask.cancel(true);
}
}


问题:可是加上以上方法也不可行,并没有解决问题,为什么cancel()方法不起作用?

分析:因为cancel()方法并没有将AsyncTask中的线程停止,只是给AsyncTask发送了一个cancel的请求,将该任务标记为cancel状态。而且在java中也无法粗暴地去停止线程,必须要等一个线程执行完后,再进行下一个任务。所以我们还需要一个步骤:

解决:在具体的异步线程中监视状态!一旦当前AsyncTask的状态等于cancel,就结束掉整个线程的剩余逻辑(即跳出for循环)。

doInBackground()方法的for循环中 和 onProgressUpdate()中判断状态,若为cancel状态,程序就可以快速终结掉线程中的剩余操作,尽快的将权利留给下一个异步任务。

逻辑代码:

ProgressBarTest.java

package com.gym.progressbarasunctask;

import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import android.widget.ProgressBar;

public class ProgressBarTest extends AppCompatActivity {

private ProgressBar mProgressBar;
private MyAsyncTask myAsyncTask;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_progress_bar);
mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
//启动异步任务
myAsyncTask = new MyAsyncTask();
myAsyncTask.execute();
}

@Override
protected void onPause() {
super.onPause();
if(myAsyncTask != null &&
myAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
//cancel方法只是将对应的AsyncTask标记为cancle状态,并不是真正取消线程的逻辑
myAsyncTask.cancel(true);
}
}

class MyAsyncTask extends AsyncTask<Void,Integer,Void>{

@Override
protected Void doInBackground(Void... voids) {
//模拟进度更新
for (int i=0; i<100; i++){
//判断状态,若为cancel则终结掉该线程剩余操作
if(isCancelled()){
break;
}
publishProgress(i);//传入进度,方便进度条更新时取值
try {
Thread.sleep(100);//延缓更新进度
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}

@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
//判断状态,若为cancel则终结掉该线程剩余操作
if(isCancelled()){
return;
}
//获取进度更新值,UI线程中获取子线程更新进度
mProgressBar.setProgress(values[0]);
}
}
}


显示结果:



如上GIF动图所示,第一次启动AsyncTask异步任务,执行到一半时退出,再次启动异步任务,程序成功判断到cancel状态,结束掉上次线程中剩余的逻辑,进度条重新从0开始显示!

2. Cancel()方法总结:

Cancel()方法只是给当前AsyncTask传递了一个信号量,标记了AsyncTask的状态,并没有真正cancle掉当前AsyncTask,需要将AsyncTask的生命周期和ActivityFragment的进行绑定,同时在AsyncTask中判断状态,停止线程中剩余逻辑!

总而言之,正确的做法是确认活动的生命周期状态,相应的取消异步任务。异步任务需要充分意识到活动的生命周期状态。因此,必须真正实现任务的生命周期方法,并且由活动调用这些生命周期方法,这样异步任务才会像活动的一部分那样运行

五. AsyncTask 与 Thread

阅读完以上内容后,相信你已经可以熟练掌握AsyncTask 机制了,可是应该何时使用呢?包括之前介绍“子线程不能更新UI组件”的解决办法中,还有一个不容忽视的Thread

看似AsyncTask 机制使用简单,可是大部分还是坚持使用HandlerThread。浅析原因:

比较:

基本原因: AsyncTask 主要应用于短暂且较少重复的任务,若长时间的运行AsyncTask,而且同一时间规定最多只能有5个线程同时运行,否则会出现异常。

技术层面的原因:Android 3.2系统版本中,AsyncTask的内部实现发生了变化,自3.2版本起,AsyncTask不再为每一个AsyncTask实例单独创建一个线程,相反,它使用一个Executor在单一的后台线程上运行所有的AsyncTask的后台任务。结合上面实例的讲解,这意味着每个AsyncTask都需要排队逐个运行,显然,长时间运行的AsyncTask会阻塞其他的AsyncTask异步任务。

虽然使用一个线程池Executor可以安全地并发多个AsyncTask,但是更好的办法是:自己处理线程相关工作,必要时使用Handler与主线程通信!

(以上只是根据书籍内容浅显分析,

https://www.zhihu.com/question/30804052/answer/49562693这里知乎大神分析的更加全!)

以上内容为AsyncTask 学习解析及基本使用,基本掌握应该没问题,但还是推荐大家要真正理解还是要分析源代码!http://blog.csdn.net/itermeng/article/details/52159831这是自己分析的,可能有不足的地方,但十分详细,而且网上也有其它大神的分析,学无止境~

希望对你们有帮助 :)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: