Android中的AsyncTask-异步任务

/ 0评 / 2

在Android中实现异步任务机制有两种方式,Handler和AsyncTask。

Handler模式需要为每一个任务创建一个新的线程,任务完成后通过Handler实例向UI线程发送消息,完成界面的更新,这种方式对于整个过程的控制比较精细,但也是有缺点的,例如代码相对臃肿,在多个任务同时执行时,不易对线程进行精确的控制,为了简化操作,Android1.5提供了工具类android.os.AsyncTask,它使创建异步任务变得更加简单,不再需要编写任务线程和Handler实例即可完成相同的任务。

AsyncTask的定义

public abstract class AsyncTask<Params, Progress, Result>

三种泛型类型分别代表“启动任务执行的输入参数”、“后台任务执行的进度”、“后台计算结果的类型”。在特定场合下,并不是所有类型都被使用,如果没有被使用,可以用java.lang.Void类型代替。不能使用基本数据类型例如int,double等

AsyncTask的使用方法

使用AsyncTask需要自定义一个类继承自AsyncTask,里面有这样几个回调函数

上面几个回调方法中,只有doInBackgound()方法运行的新线程中,其他的方法均运行在主线程里面,在这些方法里面进行耗时操作可能会产生ANR

使用AsyncTask的注意事项

上面两行红色字体是由线程的特性决定的,线程的生命周期是不能回头的,线程不能被强行结束,一般的做法是设置标志位,然后在run方法中判断标志位,Thread.interrupted()并不能中断线程。

使用AsyncTask的demo

我们先来看看效果图,这是加载一张网络上的图片

Demo下载:360云盘下载  访问密码 3b6c 环境为:Android Stdio

AsyncTaskDemo

布局文件非常简单,两个Button以及一个ProgressBar和一个ImageView,默认的情况下ProgressBar和ImageView是不可见的。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical">

    <Button
        android:id="@+id/ioadimg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="加载图片" />

    <Button
        android:id="@+id/stopload"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="停止加载" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ProgressBar
            android:id="@+id/progressBar"
            style="?android:attr/progressBarStyleLarge"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:visibility="gone" />

        <ImageView
            android:id="@+id/img"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@mipmap/ic_launcher"
            android:visibility="gone" />
    </RelativeLayout>

</LinearLayout>

然后就是我们继承自AsyncTask的类,MyAsyncTask extends AsyncTask<String, Void, Bitmap>,三个泛型参数前面已经说过了,一个是传入的参数,这是为String类型的,是一个url,第二个为执行的进度,没有使用到,使用Void,第三个是Bitmap,是在doInBackground中加载的网络图片。

class MyAsyncTask extends AsyncTask<String, Void, Bitmap> {
    /**
     * 在任务提交之前被调用,可以用来执行一些初始化任务
     * 运行再主线程
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mProgressBar.setVisibility(View.VISIBLE);
    }

    /**
     * 异步任务完成后调用,运行在主线程
     *
     * @param bitmap
     */
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
        mProgressBar.setVisibility(View.GONE);
        mImagel.setVisibility(View.VISIBLE);
        mImagel.setImageBitmap(bitmap);
    }

    /**
     * 执行异步任务,运行在新线程
     */
    @Override
    protected Bitmap doInBackground(String... params) {
        String str = params[0];
        Bitmap bitmap = null;
        try {
            URL url = new URL(str);
            InputStream inputStream = url.openStream();
            bitmap = BitmapFactory.decodeStream(inputStream);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //下面注释的代码是用来测试cancel是否能真的停止线程
        /*long timeMillis = System.currentTimeMillis();
        while(System.currentTimeMillis() - timeMillis <= 1000 * 10)
        {
        }*/
        return bitmap;
    }

    @Override
    protected void onCancelled() {
        Log.v("x", "取消了");
        super.onCancelled();
    }

    @Override
    protected void onProgressUpdate(Void... values) {
        super.onProgressUpdate(values);
    }
}

下面这段代码是给加载按钮添加点击监听消息,点击的时候加载网络图片

mLoadimg.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        myAsyncTask = new MyAsyncTask();
        myAsyncTask.execute("http://27house.cn/wp-content/uploads/2016/01/android.jpg", "");
    }
});

测试AsyncTask.cancel()方法

关于这个方法,并不是真的取消了线程的执行,而是给正在执行的线程发送了一个中断消息,也就是Thread.interrupted();

还是上面那个demo,取消MyAsyncTask中的注释,也就是在doInBackground()返回的时候让这个函数停止10秒。

//下面注释的代码是用来测试cancel是否能真的停止线程
long timeMillis = System.currentTimeMillis();
while (System.currentTimeMillis() - timeMillis <= 1000 * 10) {
}

然后我们点击取消加载按钮,执行AsyncTask.cancel()方法。

mStopLoad.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.v("x", "点击了取消");
        myAsyncTask.cancel(true);
    }
});

在logcat中打印如下语句,我们可以很清楚的看到,并不是调用cancel()方法以后就直接停止了,而是继续等待后台线程的完成。

02-02 03:21:23.630 29914-30002/com.jay.myasynctask D/dalvikvm: GC_FOR_ALLOC freed 184K, 7% free 3543K/3780K, paused 3ms, total 3ms

02-02 03:21:24.294 29914-29914/com.jay.myasynctask V/x: 点击了取消

02-02 03:21:33.750 29914-29914/com.jay.myasynctask V/x: 取消了

AsyncTask提交的任务的执行顺序

AsyncTask默认的执行的顺序是按照任务提交的顺序,除非调用AsyncTask.executeOnExecutor才是并发执行,不过此方法可能造成数据混乱。下面一个demo是验证这一点的。(源码类似,为了篇幅,可以自行下载)

Demo下载:360云盘下载 访问密码 cc43 环境为:Android Stdio

MyAsyncTask2

如图,当点击同时开始的时候,会同时启动两个AsyncTask去更新ProgressBar,我们可以很清楚的看到,只有第一个在更新,只有当第一个任务被取消或者完成以后第二个进度条才会开始更新,这就验证我上面说的,AsyncTask默认是按照任务提交的顺序来执行的。你也可以采用这个系统提供的线程池来处理你的任务AsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR),默认这个线程池是并发处理任务的,也就是不按顺序来.核心为5条,最大128条,不过这种方法使用不当可能照成数据异常,比如在这个demo中,我们使用AsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR),当多次点击开始按钮,也就是启动多个任务同时执行去刷新进度,这样当每一个任务进度不同的时候,进度条的显示就会异常。

AsyncTask适合处理短时间的操作,长时间的操作,比如下载一个很大的视频,这就需要你使用自己的线程来下载,不管是断点下载还是其它的.从google官方文档你也可以看到,AsyncTasks should ideally be used for short operations (a few seconds at the most.)

参考文章: liuhe688 AsyncTask执行顺序

 

 

发表评论

您的电子邮箱地址不会被公开。