Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

性能优化十八之多线程优化-AsyncTask源码分析

$
0
0

前言

       做过安卓开发的没有人不知道AsyncTask类,它是一个处理异步任务极为强大的类,Andorid是个单线程模型的系统,但是为了提高UI的流畅度,也支持多线程操作,但前提是更新UI的线程必须是主线程,在子线程中无法更新UI,会抛出异常,而AsyncTask就是为此而生的。
       AsyncTask内部封装的其实就是Thread+Handler,相信在没有AsyncTask之前,很多人都是new个子线程,在子线程中去处理相关的耗时操作,然后通过handler去发送消息到主线程来处理,而AsyncTask的出现,将这一过程变得更加容易。

演变过程:

       在1.5中初始引入的时候, AsyncTask 执行( AsyncTask.execute() )起来是顺序的,当同时执行多个 AsyncTask的时候,他们会按照顺序一个一个执行。前面一个执行完才会执行后面一个。这样当同时执行多个比较耗时的任务的时候 可能不是您期望的结果,具体情况就像是execute的task不会被立即执行,要等待前面的task执行完毕后才可以执行。


       在android 1.6(Donut) 到 2.3.2(Gingerbread)中,AsyncTask的执行顺序修改为并行执行了。如果同时执行多个任务,则这些任务会并行执行。 当任务访问同一个资源的时候 会出现并发问题.


       而在Android 3.0(Honeycomb)以后的版本中,AsyncTask又修改为了顺序执行,并且新添加了一个函数 executeOnExecutor(Executor),如果您需要并行执行,则只需要调用该函数,并把参数设置为并行执行即可。即创建一个单独的线程池(Executors.newCachedThreadPool())。或者最简单的方法法就是使用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Params… params),这样起码不用等到前面的都结束了再执行了。

源码分析:

       源码分析就不多讲了,想了解的看这篇博客:
       http://blog.csdn.net/liyuchong2537631/article/details/49760177

流程原理分析(重点):

       这里我重点去总结相关的流程,AsyncTask内部到底是如何实现在doInBackground()中执行耗时操作,如何在onPostExecute、onProgress等当中得到返回的值。
       在了解之前,我们必须要准备一些相关的知识,首先关于Callable和Future的相关知识,Callable可以理解为产生结果,Future可以理解为拿到结果。allable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值,下面来看一个简单的例子:

    public static void main(String[] args) {
        Task work = new Task();
        FutureTask<Integer> future = new FutureTask<Integer>(work){
            //异步任务执行完成,回调
            @Override
            protected void done() {
                try {
                    System.out.println("done:"+get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        };
        //线程池(使用了预定义的配置)
        ExecutorService executor = Executors.newCachedThreadPool();
        executor.execute(future);

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        //取消异步任务
        future.cancel(true);

        try {
            //阻塞,等待异步任务执行完毕
            System.out.println(future.get()); //获取异步任务的返回值
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    //异步任务
    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;
        }       
    }

       上面的代码主要是利用Callable来设置异步任务,通过FutureTask获取线程执行的返回结果(注意:当FutureTask处于未启动或已启动状态时,如果此时我们执行FutureTask.get()方法将导致调用线程阻塞;当FutureTask处于已完成状态时,执行FutureTask.get()方法将导致调用线程立即返回结果或者抛出异常。)。
       之所以介绍Callable和FutureTask,是因为AsyncTask内部原理就是利用Callable和FutureTask进行设计的。

public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    //可以看到doInBackground是在子线程中执行的
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    //postResult中的方法是利用handler和message,将结果发送给
                    //InternalHandler处理
                    postResult(result);
                }
                return result;
            }
        };
        //由于上面的WorkerRunnable是实现了Callable类的,所以可以利用FutureTask运行
        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                //在done方法中拿到子线程执行后的返回值result
                try {
                    //这个方法里实际调用的还是postResult(result)方法
                    //在InternalHandler的handleMessage方法中,调用了finish()和
                    //onProgressUpdate()这两个方法是运行在主线程的,因为
                    //InternalHandler的构造方法中获取了主线程的looper,所以它是与
                    //主线程关联的
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

       上面这段代码可以算的上是整个AsyncTask的开始。接下来就看最重要的execute方法:

   private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;
        //核心方法 
        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        //执行AsyncTask任务  
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

       AsyncTask中的execute方法实际上调用的是executeOnExecutor方法,里面传入一个Executor,上面提到了在AsyncTask3.0之后改为了顺序执行,要调用并发执行的方法需要调用executeOnExecutor方法:

    @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

       上面的方法里面需要传入一个执行器Executor,当调用execute()的时候内部其实默认是使用了上面提到的SerialExecutor执行器,这个执行器是顺序执行的,而调用executeOnExecutor()是并发执行的,但是需要我们自己外部去定义一个线程池。
       大概的执行流程如下:
       Executor.execute(mFuture) -> SerialExecutor.myTasks(队列)
-> (线程池)THREAD_POOL_EXECUTOR.execute

       虽然AsyncTask看起来如此强大,但是它依然存在着缺陷:
一、线程池容量不够抛出异常
       首先我们得先看下,它的内部是用的什么线程池

  ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);

CORE_POOL_SIZE 核心线程数
MAXIMUM_POOL_SIZE 最大线程数量
KEEP_ALIVE 1s闲置回收
TimeUnit.SECONDS 时间单位
sPoolWorkQueue 异步任务队列
sThreadFactory 线程工厂

       AsyncTask内部异步任务队列默认是128个,核心线程数和最大线程数是根据CPU计算出来的,这种情况下,当调用executeOnExecutor并发执行任务时,可能会抛出异常,其中有这样的机制:

1、如果当前线程池中的数量小于corePoolSize,创建并添加的任务。
2、如果当前线程池中的数量等于corePoolSize,缓冲队列 workQueue未满,那么任务被放入缓冲队列、等待任务调度执行。
3、如果当前线程池中的数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的数量小于maximumPoolSize,新提交任务会创建新线程执行任务。
4、如果当前线程池中的数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的数量等于maximumPoolSize,新提交任务由Handler处理(抛出异常)。
当线程池中的线程大于corePoolSize时,多余线程空闲时间超过keepAliveTime时,会关闭这部分线程。

       面对这样的问题该如何解决呢?
       AsyncTask对应的线程池ThreadPoolExecutor都是进程范围内共享的,都是static的,所以AsyncTask控制着进程范围内所有的子类的实例,由于这个限制的存在,当使用默认线程池时,如果线程数超过线程池的最大容量,线程池就会爆掉(3.0后默认是串行执行,不会出现这个问题)。但是当并发执行的时候就可能抛出异常,针对这种情况,可以尝试自定义线程池,配合AsyncTask使用。

二、内存泄露
       内存泄露根据对AsyncTask生命周期的分析可以发现,如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。
所以还是那句话,在Activity销毁之前cancel掉AsyncTask。即中断AsyncTask的执行,因为此时已经没有必要让AsyncTask继续执行了,结果丢失,屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。
       解决方式:第一,在Activity生命周期结束前,去cancel AsyncTask,因为Activity都要销毁了,这个时候再跑线程,绘UI显然已经没什么意义了。第二,如果一定要写成内部类的形式,对context采用WeakRefrence,在使用之前判断是否为空。

三、一个线程,一个异步任务
       这个问题将会引入一个新的知识点:HandlerThread,这个将会在后面的博客中总结。

作者:hpc19950723 发表于2017/4/25 14:21:02 原文链接
阅读:48 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles