AsyncTask的基本构成
AsyncTask概述
为什么要异步任务
- Android中,只有主线程可以对UI进行操作,而其他线程是不可以直接操作UI的。这用做的好处是保证了UI的稳定性和准确性。避免了多线程同时对UI进行操作,而造成对UI的一个混乱。
- 安卓是个多线程的系统,我们不可能把所有的事情都放在主线程中。比如一些网络操作、读取文件等一些耗时的操作。如果把这些操作都放在了主线程中,就会造成阻塞,从用户的角度,就会感觉这个App很卡或者未响应。所以,应该把耗时的操作放在非主线程中。
AsyncTask为何而生
那么,主线程和子线程如何进行通信呢?
要使用异步任务,可以使用线程和线程池,在这里涉及到了一个线程的同步和线程的管理。同时,当线程结束的时候,我们还需要通过handle去通知主线程更新UI。而AsyncTask对这一切都封装了起来,让我们可以更方便地在子线程中更新UI。总结起来AsyncTask的功能就是:
AsyncTask基本结构介绍
构建AsyncTask子类的参数
AsyncTask是一个抽象类,通常用于被继承,继承AsyncTask需要指定如下三个泛型参数:
params:启动任务时输入参数的类型。
progress:后台任务执行中返回进度值的类型。
Result:后台执行任务完成后返回结果的类型。
注:如果不需要param或者progress,设置为void即可。
构建AsyncTask子类的回调方法
doInBackground: 必须重写,异步执行后台线程将要完成的任务;注意:这个方法中是不可以对UI操作的,如果需要更新UI元素,可以调用publicProgress()方法。
onPreExecute:执行后台耗时操作前被调用,通常用户完成一些初始化操作。
onPostExecute:当doInBackground()完成后,系统会自动调用onPostExecute()方法,并将doInBackground方法返回的值传给该方法;再对返回值进行处理。改方法执行在主线程中,所以可以在该方法中对UI进行操作。
onProgressUpdate:在doInBackground()方法中调用publicProgress()方法,。更新任务的执行进度后,就会触发该方法。通过这个方法,我们就可以清楚的知道任务完成的进度。publicProgress()用于传入进度值,onProgressUpdate则获取这个进度,更新进度条。
这些方法的执行顺序:
onPreExecute—>doInBackground—>onProgressUpdate(当在doInBackground()中调用publicProgress()时才会执行)—>onPostExecute
AsyncTask的使用示例一
下载图片的时间取决于网速,网速慢的情况下是一个耗时操作,应在子线程中下载一张图片,然后同步到主线程中的UI中。
异步处理—>下载图片
UI线程—>设置图像
效果:点击开始界面的按钮,进入下一个活动,首先出现下载图片的等待ProgressBar,然后显示图片。
涉及的文件有:
MainActivity.java:默认进入的活动,有一个按钮,点击后进入ImageActivity。
ImageActivity.java:显示图片的活动
activity_main.xml:默认活动的布局
activity_image.xml:图片活动的布局
创建UI
首先,找到一张网络图片,并记住链接;然后创建一个新的活动,并在对应的xml中布局,即设置一个Image控件和一个ProgressBar控件。ProgressBar设置默认visibility为隐藏gone。最后在活动中setContentView这个布局,创建必要控件变量以及图片链接URL。
main活动的xml布局代码:
1 2 3 4 5 6
| <Button android:id="@+id/btn_next" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="next" android:textSize="25sp"/>
|
对应的main活动代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class MainActivity extends AppCompatActivity { private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button)findViewById(R.id.btn_next); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(MainActivity.this, ImageActivity.class)); } }); } }
|
image活动的xml布局代码:
1 2 3 4 5 6 7 8 9 10
| <ImageView android:id="@+id/img_pic1" android:layout_width="match_parent" android:layout_height="match_parent"/> <ProgressBar android:id="@+id/progressBar1" android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_centerInParent="true" android:visibility="gone"/>
|
对应的Image活动代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class ImageActivity extends AppCompatActivity { private ImageView imageView; private ProgressBar progressBar; private static String URL = "http://ico.ooopic.com/ajax/iconpng/?id=132347.png"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_image); imageView = (ImageView)findViewById(R.id.img_pic1); progressBar = (ProgressBar)findViewById(R.id.progressBar1);
} }
|
创建AsyncTask类
在ImageActivity里创建内部类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| class MyAsyncTask extends AsyncTask<String, Void, Bitmap>//Bitmap位图类 { @Override protected void onPreExecute() { super.onPreExecute(); progressBar.setVisibility(View.VISIBLE); }
@Override protected Bitmap doInBackground(String... params) { String url = params[0]; Bitmap bitmap = null; URLConnection connection; InputStream is; try { connection = new URL(url).openConnection(); is = connection.getInputStream(); BufferedInputStream bis = new BufferedInputStream(is); Thread.sleep(3000); bitmap = BitmapFactory.decodeStream(bis); is.close(); bis.close(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } return bitmap; }
@Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); progressBar.setVisibility(View.GONE); imageView.setImageBitmap(bitmap); } }
|
总结一下流程就是:
- onPreExecute—>加载进度条
- doInBackground—>下载网络数据(耗时操作)
- onPostExecute—>显示图片
与UI线程通信
onPostExecute方法就是将子线程中返回的结果在主线程中处理。
修改补充ImageActivity的onCreate方法:
1 2 3 4 5 6 7 8 9 10
| @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_image); imageView = (ImageView)findViewById(R.id.img_pic1); progressBar = (ProgressBar)findViewById(R.id.progressBar1); new MyAsyncTask().execute(URL);
}
|
效果如下:
AsyncTask的使用示例二
模拟进度条
效果:点击开始界面的按钮,进入下一个活动,首先出现ProgressBar读取进度。
代码实现
注释里写的很清楚了,这里就直接给代码了。
activity_main.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?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: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"> <Button android:id="@+id/btn_next" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="next" android:textSize="25sp"/> </RelativeLayout>
|
MainActivity.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.demoniaccube.chobits.asynctask_test;
import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.ProgressBar;
public class MainActivity extends AppCompatActivity { private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button)findViewById(R.id.btn_next); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(MainActivity.this, ProgressBarActivity.class)); } }); } }
|
activity_progress_bar.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?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: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="com.demoniaccube.chobits.asynctask_test.ProgressBarActivity">
<ProgressBar android:id="@+id/progressBar2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" style="?android:attr/progressBarStyleHorizontal"/>
</RelativeLayout>
|
ProgressBarActivity.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| package com.demoniaccube.chobits.asynctask_test;
import android.os.AsyncTask; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.ProgressBar;
public class ProgressBarActivity extends AppCompatActivity { private ProgressBar progressBar; private MyAsyncTask myAsyncTask;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_progress_bar); progressBar = (ProgressBar)findViewById(R.id.progressBar2); myAsyncTask = new MyAsyncTask(); myAsyncTask.execute();
}
@Override protected void onPause() { super.onPause(); if (myAsyncTask != null && myAsyncTask.getStatus() == AsyncTask.Status.RUNNING) { myAsyncTask.cancel(true); } }
class MyAsyncTask extends AsyncTask<Void, Integer, Void> //这次并不需要传入数据,只需要显示进度条,所以第一个参数为Void,第二个参数要返回进度条的值,所以为Integer,Integer为int的封装类。没有传入数据处理,也没必要返回结果,所以第三个参数为Void { @Override protected Void doInBackground(Void... params) { for (int i = 0; i < 100; i++) { if (isCancelled()) { break; } publishProgress(i); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } return null; }
@Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); progressBar.setProgress(values[0]); } }
}
|
优化
这里出现了一个bug,当从读取进度条界面退出,再进入时,会出现等待进度条的情况。
这是因为,AsyncTask底层是通过线程池作用的。当一个线程没有执行完毕的时候,后面的线程是没法执行的。由于在AsyncTask里我们用了一个for循环,所以它必须等到for循环全部执行完后才会去执行下一个Task,这就导致了重复进入ProgressBarActivity时,等待进度条的一个bug。
那么如何解决呢?
可以让这个AsyncTask周期和Activity周期保持一致。
在ProgressBarActivity暂停的时候,对myAsyncTask进行一个判断,当myAsyncTask不为空且是正在运行的状态的时候,给Task一个“取消”标记;然后在doInBackground的循环语句里,每次更新前,判断Task是否被标记取消。如果被标记取消了,就跳出循环,从而间接的结束了进程。
AsyncTask注意事项
- 必须在UI线程(即主线程)中创建AsyncTask的实例;
- 必须在UI线程中调用AsyncTask的execute()方法;
- 重写的四个方法是系统自动调用的,不应手动调用;
- 每个AsyncTask只能执行一次,多次调用将会引发异常;
- 在AsyncTask类中,只能doInBackground方法是在其他线程中执行的,其他三个方法是执行在主线程中的。所以这也是不能再doInBackground方法里操作UI控件的原因。