Android基础之AsyncTask

来源:互联网 发布:淘宝推广店铺 编辑:程序博客网 时间:2024/05/11 20:07

背景

AsyncTask是Android中异步请求的基础类。由于在Android系统是单线程模型,即只能在主线程更新UI。之所以这样设计,主要是为了避免多个线程对UI同时操作造成的混乱。另一方面,Android是一个多线程的操作系统,我们不可能把所有操作都放在主线程中操作,如网络请求,读取存储的数据,等等。否则会抛出ANR异常。所以,有必要把耗时操作放在非UI线程中执行,而仅在UI线程更新UI。这样既保证了Android系统的单线程模型,又避免了程序的ANR。

AsyncTask是Android封装好的用于实现异步请求的类。利用这个类,可以很方便的在子线程中更新UI,同时简化异步操作。

AsyncTask基础


AsyncTask是一个抽象类,它包含了三个泛型参数。一般会继承该类。

AsyncTask<Params,Progress,Result>

其中:

  • 泛型参数一:Params。它是启动任务时的输入参数类型;

  • 泛型参数二:Progress。它是后台任务执行时返回的进度值的类型;

  • 泛型参数三:Result。后台任务执行完成后返回的结果类型。


AsyncTask中必须重写的方法:

  • doInBackground():该方法必须重写,用于执行后台的异步任务。所有耗时操作都在该方法中执行。

  • onPreExecute():执行耗时操作前被调用。完成一些初始化操作。该方法在主线程中执行。

  • onPostExecute():在doInBackground()方法执行完毕后,该方法会被调用,并将返回的值传递至该方法中。该方法在主线程中执行;

  • onProgressUpdate():在 doInBackground()中调用publishProgress()方法,可将当前后台执行的进度发送到该方法中。该方法在主线程中执行。


新建一个MyAsyncTask类继承自AsyncTask,并重写上述方法,打印Log,各方法的调用顺序如下:

public class MyAsyncTask extends AsyncTask<Void, Void, Void> {    public static final String TAG = "MyAsyncTask";    @Override    protected Void doInBackground(Void... params) {        Log.e(TAG, "doInBackground");        return null;    }    @Override    protected void onPreExecute() {        super.onPreExecute();        Log.e(TAG, "onPreExecute");    }    @Override    protected void onPostExecute(Void aVoid) {        super.onPostExecute(aVoid);        Log.e(TAG, "onPostExecute");    }    @Override    protected void onProgressUpdate(Void... values) {        super.onProgressUpdate(values);        Log.e(TAG, "onProgressUpdate");    }}

调用顺序:
这里写图片描述


顺序为:onPreExecute()—>doInBackground()—>onPostExecute()


若在doInBackground()中加入方法 publishProgress()方法,再次运行程序,打印的Log为:
这里写图片描述


顺序为:onPreExecute()—>doInBackground()—>onProgressUpdate()—>onPostExecute()


demo1:使用AsyncTask加载一张网络图片


在使用AsyncTask加载完成之前,会实现一个progressBar,提示用户等待。加载完毕后,隐藏progressBar。

布局代码:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/activity_image_load"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context="com.demo.lenovo.asynctasktest.ImageLoadActivity">    <ImageView        android:id="@+id/iv_image"        android:layout_width="match_parent"        android:layout_height="match_parent" />    <ProgressBar        android:id="@+id/pb_progress"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerInParent="true"        android:visibility="gone" /></RelativeLayout>

首先将ProgressBar设为Gone,即不显示。

接着是Activity:

public class ImageLoadActivity extends AppCompatActivity {    private static final String URL = "http://img.my.csdn.net/uploads/201609/14/1473820894_3292.png";    private ImageView iv_image;    private ProgressBar pb_progress;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_image_load);        iv_image = (ImageView) findViewById(R.id.iv_image);        pb_progress = (ProgressBar) findViewById(R.id.pb_progress);        new ImgLoadAsyncTask().execute(URL);    }    private class ImgLoadAsyncTask extends AsyncTask<String, Void, Bitmap> {        @Override        protected void onPreExecute() {            super.onPreExecute();            pb_progress.setVisibility(View.VISIBLE);        }        @Override        protected Bitmap doInBackground(String... params) {            try {                Thread.sleep(3000);            } catch (InterruptedException e) {                e.printStackTrace();            }            String url = params[0];            URLConnection urlConnection = null;            Bitmap bitmap = null;            InputStream inputStream = null;            BufferedInputStream bufferedInputStream = null;            try {                urlConnection = new URL(url).openConnection();                inputStream = urlConnection.getInputStream();                bufferedInputStream = new BufferedInputStream(inputStream);                bitmap = BitmapFactory.decodeStream(bufferedInputStream);            } catch (IOException e) {                e.printStackTrace();            } finally {                try {                    if (inputStream != null) {                        inputStream.close();                    }                    if (bufferedInputStream != null) {                        bufferedInputStream.close();                    }                } catch (IOException e) {                    e.printStackTrace();                }            }            return bitmap;        }        @Override        protected void onPostExecute(Bitmap bitmap) {            super.onPostExecute(bitmap);            iv_image.setImageBitmap(bitmap);            pb_progress.setVisibility(View.GONE);        }    }}

在Activity中,将AsyncTask设为内部类,并将泛型参数分别设为String, Void, Bitmap。其中String表示加载的图片URL地址,Void表示不需要在加载过程中通知主线程更新进度,Bitmap表示加载完毕后返回一张Bitmap图。

在onPreExecute()方法中,首先将ProgressBar设为可见状态;在doInBackground()方法中从泛型数组String中获取url地址,通过URLConnection和InputStream网络请求基础类请求url,最终转化为Bitmap图像并返回;最后,在onPostExecute()方法中将Bitmap设置到ImageView上并将ProgressBar关闭。(在doInBackground()方法中加入一个Thread.sleep()以模拟网络请求的延迟。)

demo2:使用AsyncTask模拟进度条

在demo中,将模拟进度条的更新等待操作。

首先,布局就是一个横向的ProgressBar:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/activity_progress_load"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context="com.demo.lenovo.asynctasktest.ProgressLoadActivity">    <ProgressBar        android:id="@+id/pb_load_progress"        style="?android:attr/progressBarStyleHorizontal"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_centerInParent="true" /></RelativeLayout>

接着是Activity:

public class ProgressLoadActivity extends AppCompatActivity {    private ProgressBar pb_load_progress;    private ProgressLoadAsyncTask mProgressLoadAsyncTask;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_progress_load);        pb_load_progress = (ProgressBar) findViewById(R.id.pb_load_progress);        mProgressLoadAsyncTask = new ProgressLoadAsyncTask();        mProgressLoadAsyncTask.execute();    }    private class ProgressLoadAsyncTask extends AsyncTask<Void, Integer, Void> {        @Override        protected Void doInBackground(Void... params) {            for (int i = 0; i < 100; ++i) {                //根据AsyncTask指定的泛型,该方法可传入一个Integer变长数组作为参数                publishProgress(i);                try {                    Thread.sleep(50);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }            return null;        }        @Override        protected void onProgressUpdate(Integer... values) {            super.onProgressUpdate(values);            pb_load_progress.setProgress(values[0]);        }        @Override        protected void onPostExecute(Void aVoid) {            super.onPostExecute(aVoid);            pb_load_progress.setVisibility(View.GONE);        }    }}

在Activitiy中使用AsyncTask模拟进度条的更新,由于不需要传入参数,所以泛型参数一是Void,而在doInBackground中,调用publishProgress(),传入的值将传给onProgressUpdate()方法,所以泛型参数二是Integer类型。该操作也不需要返回参数,所以泛型参数三也是Void类型。

最后,在onPostExecute()中隐藏ProgressBar。


取消Task

在运行过程中,会遇到一个问题:

  • 当进度条未执行完毕时,退出该Activity,并立即再次启动,发现进度条并没有从上次退出的进度继续执行,也没有从头执行。

原因:

  • 由于AsyncTask的底层实现是线程池,只有当一个线程执行完毕以后,下一个线程才会开始,所以,只有当doInBackground()方法的内容全部执行完毕以后,才能得到下一次执行。

解决方法:

  • 使AsyncTask的生命周期与Activity的生命周期一致。

具体解决方式:
在Activity的onPause()中判断AsyncTask的状态并将其停止:

@Override    protected void onPause() {        super.onPause();        //当AsyncTask不为空且AsyncTask的状态为正在运行        if (mProgressLoadAsyncTask != null && mProgressLoadAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {            //只是将对应的AsyncTask标记为cancel状态,并没有真正取消该线程。            mProgressLoadAsyncTask.cancel(true);        }    }

需要注意的是cancel()方法只是将对应的AsyncTask标记为cancel状态,并没有真正取消该线程。下面是cancel()方法的文档:
这里写图片描述

其中第二段说:调用cancel()方法将会触发onCancelled(Object)在doInBackground()方法结束后调用(并且onCancelled(Object)是在UI线程中调用),同时您还要确保onPostExecute(Object) 不会被调用。最后还需要不断在doInBackground方法中检查isCancelled() 并及时手动停止该AsyncTask。

所以,除了在onPause中调用cancel方法外,还需要在doInBackground方法中调用isCancelled()判断状态,并将onPostExecute方法中的逻辑复制到onCancelled()方法中(如您需要在onCancelled()中直线自己的操作,如关闭进度条等,请不要调用super.onCancelled()):
这里写图片描述

在doInBackground和onProgressUpdate方法中,不断判断isCancelled()方法,若为true即终止任务:

 @Override        protected Void doInBackground(Void... params) {            for (int i = 0; i < 100; ++i) {                if (isCancelled()) {                    break;                }                //根据AsyncTask指定的泛型,该方法可传入一个Integer变长数组作为参数                publishProgress(i);                try {                    Thread.sleep(50);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }            return null;        }        @Override        protected void onProgressUpdate(Integer... values) {            super.onProgressUpdate(values);            if (isCancelled()) {                return;            }            pb_load_progress.setProgress(values[0]);        }

重新运行程序,当中途退出Activity,并再次进入后,可以进度条从头加载。


总结

  • 必须在UI线程中创建AsyncTask实例;

  • 必须在UI线程中调用AsyncTask.execute()方法;

  • 不能手动调用AsyncTask的回调方法;

  • 只能调用AsyncTask.execute()一次,不可重复调用。

  • 只有doInBackground()方法运行在子线程中,其余回调方法均运行在主线程中。

1 0
原创粉丝点击