Android开发线程间的交互之异步任务(AsyncTask)

一、AsyncTask简介

AsyncTask是对Handler与线程池的封装。更新用户界面的操作还是在主线程中完成的,但是由于AsyncTask内部包含一个Handler,所以可以发送消息给主线程让它更新UI。另外,AsyncTask内还包含了一个线程池。使用线程池的主要原因是避免不必要的创建及销毁线程的开销。

二、AsnycTask参数及方法介绍

AsyncTask是一个抽象类,我们在使用时需要定义一个它的派生类并重写相关方法。

class MyAnyscTask extends AsyncTask<Params,Progress, Result> {}

参数的含义如下:

  • Params:doInBackground方法的参数类型;
  • Progress:AsyncTask所执行的后台任务的进度类型;
  • Result:后台任务的返回结果类型。
    class MyAnyscTask extends AsyncTask<Void,Void,Void>{
        /**
         * 此方法中定义要执行的后台任务,在这个方法中可以调用 
         * publishProgress来更新任务进度 
         * (publishProgress内部会调用onProgressUpdate方法)
         * @param params
         * @return
         */
        @Override
        protected Void doInBackground(Void... params) {
            return null;
        }
        /**
         * 此方法会在后台任务执行前被调用,用于进行一些准备工作
         */
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }
        /**
         * 后台任务执行完毕后,此方法会被调用,参数即为后台任务的返回结果
         */
        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
        }
       /**
         * 由publishProgress内部调用,表示任务进度更新
         */
        @Override
        protected void onProgressUpdate(Void... values) {
            super.onProgressUpdate(values);
        }
    }

注:AsyncTask的方法中除了doInBackground是运行在子线程中做耗时操作外,其他几个都是运行在主线程中

三、AsnycTask使用

前面介绍的AsyncTask的参数以及方法,下面就使用AsyncTask来模拟一个下载的例子。

layout布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ProgressBar
        android:id="@+id/test_ansyctask_download_pb"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"/>

    <Button
        android:id="@+id/test_ansyctask_download_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="开始下载" />
</LinearLayout>

Activity

//下载进度条
mProgressBar =(ProgressBar)findViewById(R.id.test_ansyctask_download_progressBar);
//开始下载按钮
mBtn = (Button)findViewById(R.id.test_ansyctask_download_btn);
//为按钮添加点击事件
mBtn.setOnClickListener(this);

在onClick中调用AnyscTask.execute()执行异步任务

@Override
public void onClick(View v) {
    myAnyscTask = new MyAnyscTask();
    //执行异步任务
    myAnyscTask.execute();
}

自定义AnyscTask

class MyAnyscTask extends AsyncTask<Void,Integer,Void> {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        @Override
        protected Void doInBackground(Void... params) {
            //该方法运行在子线程,用于耗时操作。模拟http下载 1s更新一次进度
            for(int i = 0; i < 100; i ++){
                publishProgress(i);
                try{
                    Thread.sleep(1000);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            //更新进度条
            mProgressBar.setProgress(values[0]);
        }
    }

这样一个简单的异步任务例子就完成了。

四、AsnycTask取消

运行上面的例子没有什么问题,但是当进度条开始加载一段时间后(没有加载完成)退出当前activity然后再次进入,点击开始按钮就会发现需要等一会儿进度条才会再次执行。这是因为AsyncTask内包含了一个线程池。需要等到上一次任务结束才会执行当前任务。为了让AsnycTask马上取消正在执行的任务,我们需要在停止的方法里面调用

//判断myAnyscTask不为空且正在运行
if(myAnyscTask!=null&&myAnyscTask.getStatus()==AsyncTask.Status.RUNNING){
   //Asynctask设置cancelled仅仅只是通知Asynctask设置一个取消标志,Asynctask并不能马上退出
   myAnyscTask.cancel(true);
}

然后在doInBackground中退出正在执行的任务

@Override
protected Void doInBackground(Void... params) {
    for(int i = 0; i < 100; i ++){
        //判断当myAnyscTask.isCancelled()标记为取消时,退出耗时操作
        if(myAnyscTask.isCancelled()){
            break;
        }
        publishProgress(i);
        try{
            Thread.sleep(1000);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    return null;
}

五、AsyncTask机制原理

在了解AsyncTask的内部原理的时候需要了解ArrayDeque和FutureTask类

ArrayDeque

数据结构的物理实现方式主要还是两种:链表和数组

Java中提供了具体实现队列的类ArrayDeque(循环队列,依赖于可变数组来实现的)

ArrayDeque不是线程安全的。

ArrayDeque不可以存取null元素,因为系统根据某个位置是否为null来判断元素的存在。 当作为栈使用时,性能比Stack好;当作为队列使用时,性能比LinkedList好。

方法:

//向队列中插入一个元素,并返回true 如果队列已满,抛出IllegalStateException异常
boolean add(E e);

//向队列中插入一个元素,并返回true 如果队列已满,返回false
boolean offer(E e);

//取出队列头部的元素,并从队列中移除 队列为空,抛出NoSuchElementException异常
E remove();

//取出队列头部的元素,并从队列中移除 队列为空,返回null
E poll();

//取出队列头部的元素,但并不移除 如果队列为空,抛出NoSuchElementException异常
E element();

//取出队列头部的元素,但并不移除 队列为空,返回null
E peek(); 
FutureTask

FutureTask可用于异步获取执行结果或取消执行任务的场景。是Future 的一个实现。通过get()方法可以异步获取执行结果,不论FutureTask调用多少次run()或者call()方法,它都能确保只执行一次Runable或Callable任务。因此,FutureTask非常适合用于耗时高并发的计算,另外可以通过cancel()方法取消执行任务。

方法:

 1.get() //阻塞一直等待执行完成拿到结果

 2.get(int timeout, TimeUnit timeUnit)//阻塞一直等待执行完成拿到结果,如果在超时时间内,没有拿到抛出异常 

 3.isCancelled()//是否被取消

 4.isDone()//是否已经完成

 5.cancel(boolean mayInterruptIfRunning)//试图取消正在执行的任务

使用:

 1、 新建异步任务
 Task task = new Task();

 2、定义FutrueTask
 FutureTask<Integer> future = new FutureTask<Integer>(task) {
     // 异步任务执行完成,回调
     @Override
     protected void done() {
        try {
            System.out.println("future.done():" + get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
             e.printStackTrace();
        }
    }
 };
	
 3、 创建线程池 执行futrue
 ExecutorService executor = Executors.newCachedThreadPool();
 executor.execute(future);//执行的时候会回调Callable(即task)的call方法
 
 4、获取异步任务返回值
 try {
    // 阻塞,等待异步任务执行完毕-获取异步任务的返回值
    System.out.println("future.get():" + future.get());
 } catch (InterruptedException e) {
     e.printStackTrace();
 } catch (ExecutionException e) {
     e.printStackTrace();
 }
 
 5、自定义异步任务
 static class Task implements Callable<Integer> {
    // 返回异步任务的执行结果
    @Override
    public Integer call() throws Exception {
        int i = 0;
        for (; i < 10; i++) {
            try {
                System.out.println(Thread.currentThread().getName() + "_"
                        + i);
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return i;
    }
 }
AsyncTask的源码分析

1、自定义AsyncTask的子类MyAsyncTask

AsyncTask构造函数中初始化三个对象 WorkerRunnable<Params, Result> mWorker; FutureTask mFuture;Handler mHandler;

<1> mWorker实现了Callable接口的类,内部的call方法调用MyAsyncTask的result = doInBackground()并得到返回值。执行完毕后调用postResult(result)方法

<2> postResult(result)方法中使用mHandler将result发送给主线程,并调用finish方法。

<3> finish方法中会判断任务是否取消。如果取消则调用MyAsyncTask的onCancelled方法,否则就会调用MyAsyncTask的onPostExecute方法

2、MyAsyncTask.execute()

该方法会调用executeOnExecutor(sDefaultExecutor)

executeOnExecutor方法内部会调用MyAsyncTask的onPreExecute()方法和sDefaultExecutor.execute(mFuture)方法

<1> sDefaultExecutor是实现了Executor的SerialExecutor一个线程池
   
<2> 该线程池内部定义了ArrayDeque<Runnable> mTasks的队列的变量、Runnable mActive变量以及execute(Runnable r)方法(Runnable r参数就是mFuture)
   
<3> execute方法内部首先会通过mTasks.offer添加Runnable对象。之后判断mActive是否为空,首次进来肯定为空,会执行scheduleNext()方法
   
<4> scheduleNext()方法会从队列mTasks中取出之前添加的Runnable对象(使用mTasks.poll())赋值给mActive,并判断
       如果不为空交给THREAD_POOL_EXECUTOR去执行(THREAD_POOL_EXECUTOR是实现ThreadPoolExecutor的一个线程池),可见AsyncTask内部真正执行任务的是THREAD_POOL_EXECUTOR线程池
	   
<5> 添加的Runnable对象,内部会调用参数r的run方法(即mFuture的run方法)。run()内部会回调mWorker.call()方法,执行完毕后会再次去调用scheduleNext()方法执行下一个方法。从这里看出默认是串行执行的

3、更新进度条

在doInBackground中调用publishProgress(int progress)方法,在该方法内部会使用mHandler将progress发送到主线程,会调用MyAsyncTask的onProgressUpdate方法

4、取消

使用MyAsyncTask的cancel(true)取消任务的时候,内部会设置AtomicBoolean mCancelled的set方法设置true,设置取消的标志。如果doInBackground中正在循环的执行耗时任务,任务并不会停止,直至执行结束。如果想在doInBackground中停止正在执行的循环操作,那么就需要在doInBackground的循环中使用:

if(isCancelled()){
            break;
}来跳出循环

六、AsyncTask注意事项

1、内存泄漏

AsyncTask内存泄漏问题和handler是一样的。如果在activity中使用非静态内部类或匿名内部类来创建AsyncTask的话,由于Java非静态内部类会会持有外部类activity的隐式引用。如果AsyncTask的生命周期比Activity的长,当Activity进行销毁AsyncTask还在执行时,由于AsyncTask持有Activity的引用,导致Activity对象无法回收,进而产生内存泄露。

2、生命周期
它的生命周期会执行耗时操作,所以它不会随着activity的销毁而销毁, 需要手动取消

3、结果丢失

在屏幕旋转等造成Activity重新创建时AsyncTask数据丢失。当Activity销毁并创新创建后,还在运行的AsyncTask会持有一个Activity的非法引用即之前的Activity实例。导致onPostExecute()没有任何作用。

4、并行与串行

Android1.6之前:

任务是串行调度。一个任务执行完成另一个才能执行

Android1.6到2.3

Android团队让AsyncTask并行

Android3.0到现在

Android团队又把AsyncTask改成了串行。当然这一次的修改并没有完全禁止AsyncTask并行。可以通过设置executeOnExecutor(Executor)来实现多个AsyncTask并行。

七、AsyncTask的缺点

在3.0以前,最大支持128个线程的并发,10个任务的等待。在3.0以后,无论有多少任务,都会在其内部单线程执行。为了使AsyncTask执行并行任务并可以一次执行更多个任务,可以使用executeOnExecutor(Executor)自定义线程池来实现

public static ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
public static ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
public static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
public static Executor exec = new ThreadPoolExecutor(15, 200, 10,TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
	   
//顺序执行任务,与AsyncTask默认是串行执行任务一致
new MyAsyncTask((i + 1)).executeOnExecutor(singleThreadExecutor);
//无限制启动线程执行任务
new MyAsyncTask((i + 1)).executeOnExecutor(cachedThreadPool);
//指定线程的数量,每次执行固定数量
new MyAsyncTask((i + 1)).executeOnExecutor(fixedThreadPool);
//AsyncTask中定义的线程池ThreadPoolExecutor。指定每次执行的线程数量以及缓存池中的大小默认128,当线程数量超过线程数量和缓冲池的大小时会抛出异常
new MyAsyncTask((i + 1)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
//自定义ThreadPoolExecutor
new MyAsyncTask((i + 1)).executeOnExecutor(exec);

八、使用AsyncTask注意的事项

注意的地方:

1、由于Handler需要和主线程交互,而Handler又是内置于AsnycTask中的,所以, AsyncTask的创建必须在主线程。
  
2、AsyncTask对象的execute方法必须在主线程中调用

3、AsyncTaskResult的doInBackground(mParams)方法执行异步任务运行在子线 程中,其他方法运行在主线程中,可以操作UI组件。
  
4、不要手动的去调用AsyncTask的onPreExecute, doInBackground, publishPro- gress, onProgressUpdate, onPostExecute方法,这些都是由Android系统自 动调用的
  
5、一个AsyncTask对象只能调用一次execute方法
  
6、运行中可以随时调用cancel(boolean)方法取消任务,如果成功调用isCancelled() 会 返 回 true,并 且 不 会 执 行 onPostExecute() 方 法 了,取 而 代 之 的 是 调 用 onCancelled() 方法。而且从源码看,如果这个任务已经执行了这个时候调用 cancel是不会真正的把task结束,而是继续执行,只不过改变的是执行之后的回 调方法是onPostExecute还是onCancelled。

九、AsyncTask和Handler对比

AsyncTask

使用的优点:

简单、快捷、过程可控

使用的缺点:

在使用多个异步操作和并需要进行Ui变更时,就变得复杂起来.

Handler

使用的优点:

结构清晰,功能定义明确。对于多个后台任务时,简单,清晰

使用的缺点:在单个后台异步处理时,显得代码过多,结构过于复杂(相对性)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值