AsyncTask源码分析

388 阅读7分钟

前言

在安卓中我们的UI更新都是在主线程里面,如果主线程超过5s无响应(还是5s后操作无响应?这个需要待确认),就会导致ANR,那因此就需要启动线程进行耗时操作,然后再到主线程处理,正常我们是通过Handler+Thread实现的,其实这个就是AsyncTask

AsyncTask的优缺点

优点

1、操作简单
封装了Thread和Handler给用户使用,操作比较简单。用户需要使用时,仅需继承AsyncTask,并重写其中的doInBackground方法就可以,若是希望子线程的执行结果反馈到UI线程上,则将onPreExecute(告知UI线程,子线程开始执行)和onPostExectute(将子线程执行的结果反馈给UI线程)和onProgressUpdate(可以实时的将子线程的执行过程反馈给UI,一般是使用进度条)重写就可以。
2、过程可控

缺点

1、线程池容量不够会抛出异常
AsyncTask任务运行于线程池的。一般线程池的大小是固定的,这就导致AsyncTask可以并发的任务也是固定的。Android 1.5规定AsyncTask仅允许存放128个并发任务,同一时间只有10个任务可被同时处理(这10个任务以队列的形式存放)。也就是说如果你在完成138个任务之前排队,你的应用程序就会崩溃。大多数情况下,当人们使用AsyncTask从网络上加载Bitmaps时,会有这个问题。
2、内存泄露(所以最好任务执行时间少一点)
AsyncTask提供的cancell方法只是一个修改标志的方法,并没有真正的停止任务的执行,当人无论寻到此标志时才会停止。 持有外部引用,外部销毁后任务在运行,内存不会回收
3、在安卓3.0版本后AsyncTask默认是单线程的
所以当你添加任务的时候是按照顺序执行的,如果前一个任务卡住了,后面的就无法执行,不过系统提供了一个允许一定数量的线程池进行并行处理,后面也会讲到 简单点说就是调用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,params)方法
4、屏幕旋转
使用AsyncTask的时候,在屏幕切换也会出现问题。虽然屏幕切换后,任务也在执行,也在不停地调用更新进度条的方法,最后也执行了onPostExecute方法,但是界面上就是什么变化都没有。

因为在横竖屏切换的时候,Activity会销毁重建,所以AsyncTask所持有的引用就不是新建的Activity的控件了,新的Activity就不会变化了。

其中一种解决办法

image.png

AsyncTask的使用及注意事项

其实可以参考 AsyncTask的使用这篇文章,讲的蛮好

AsyncTask的使用

package com.xm.studyproject.java;

import android.os.AsyncTask;
import android.util.Log;

import static android.content.ContentValues.TAG;

public class UseAsynckTask {

    public void test() {
        IAsyncTask iAsyncTask = new IAsyncTask();
        iAsyncTask.execute("url1", "url2", "url3");
         //针对上一种利用 单线程存在的问题 提供了一种一定数量的线程池
        iAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"url1", "url2", "url3");
    }

    //泛型参数1:其实这个地方是一个集合
    //参数2:是进度条的进度 也是一个集合
    //参数3:是结果值
    private class IAsyncTask extends AsyncTask<String, Integer, String> {
        //args1 就是上面的参数1
        //这个方法就是在异步线程里面执行的 就是真实任务的执行
        protected String doInBackground(String... args1) {
            int length = args1.length;
            Log.i(TAG, "doInBackground in:" + args1[0]);
            Log.i(TAG, "doInBackground in:length:" + length);
            int times = 0;
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(500);
                    times++;
                    //注意 非得调用这个方法
                    //不然onProgressUpdate方法不会被调用到
                    publishProgress(i);//提交之后,会执行onProcessUpdate方法
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Log.i(TAG, "doInBackground out");
            return "over:" + times;
        }

        /**
         * 在调用cancel方法后会执行到这里
         */
        //也是运行在主线程上
        protected void onCancelled() {
            Log.i(TAG, "onCancelled");
        }

        /**
         * 在doInbackground之后执行
         */
        //这个方法是在主线程执行的
        //args3值是doInBackground方法的返回值
        protected void onPostExecute(String args3) {
            Log.i(TAG, "onPostExecute:" + args3);
        }

        /**
         * 在doInBackground之前执行
         */
        //这个方法也是在主线程执行的
        @Override
        protected void onPreExecute() {
            Log.i(TAG, "onPreExecute");
        }

        /**
         * 特别赞一下这个多次参数的方法,特别方便
         *
         * @param args2
         */
        //进度条更新
        //args2[0]是doInBackground动态更新的值
        @Override
        protected void onProgressUpdate(Integer... args2) {
            int length = args2.length;
            Log.i(TAG, "args2.length:" + length);
            Log.i(TAG, "onProgressUpdate:" + args2[0]);
        }
    }
}

注意

注意1: 记得在doInBackground()即时调用publishProgress()方法 不然onProgressUpdate()不执行。
注意2: 记得执行asyncTask.cancle方法 不然会存在内存泄漏等问题

源码分析

tip: AsyncTask源码还是相对来说比较简单的 但是针对细节的处理还是做得非常好的

1.创建实例IAsyncTask iAsyncTask = new IAsyncTask();

 public AsyncTask() {
        this((Looper) null);
    }
    
 public AsyncTask(@Nullable Looper callbackLooper) {
        //默认是使用主线程的looper 创建了一个Hanlder
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);
       //这里采用的是创建线程方式3 不懂的可以去参考我之前多线程的文章
       
        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
                    //目标方法1 执行doInBackground方法
                    //这个方法此刻是不执行的 因为任务还没开始执行
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                return result;
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    //worker中run方法执行完后就走到这里了
                    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);
                }
            }
        };   
 
 //postResultIfNotInvoked方法
   private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) {
            postResult(result);
        }
    }
  
   private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        //发送到主线程
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }
    
     public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
     }
   
   //方法mTask.finish 
   //mtask 就是之前new AsyncTaskResult<Result>(this, result));里面的mTask 其实就是AsyncTask
       private void finish(Result result) {
        if (isCancelled()) {
            //如果是任务取消 则会走onCancelled方法
            onCancelled(result);
        } else {
            //目标方法2 这个就是执行完任务后回调到主线程里面的
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

2、方法的执行 iAsyncTask.execute("url1", "url2", "url3");

    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        //所以内部还是走的executeOnExecutor方法
        //上面使用的时候我们发现除开通过execute方法执行 还有一种就是可多个线程的线程池执行
        return executeOnExecutor(sDefaultExecutor, params);
    }
    
    //sDefaultExecutor是什么 其实只能说是对任务队列的一个包装 不知道是不是可以理解成一个静态代理类
    //初始化的时候就对这个sDefaultExecutor进行了赋值操作
    //同时使用了static 跟volatitle关键字保证sDefaultExecutor应用里只有一份
    //volatitle后面会讲解
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    
    //SERIAL_EXECUTOR 变量
    //注意1: 使用了static跟final关键字 staic去保证项目里面只有一个的同时通过final去保证    SERIAL_EXECUTOR不能被修改 不系统可以new 多个AsyncTask 去对SERIAL_EXECUTOR重新赋值
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    
    //SerialExecutor类
    private static class SerialExecutor implements Executor {
        //双端队列  双端队列的使用可参考文章 https://www.jianshu.com/p/2f633feda6fb  
        //主要是为了加速数据的生产跟消费
        //是一个可变数组 提供了很多方法
        //是线程不安全的 而我们这里采用了单线程的方式 所以使用这个并不担心
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
           //1.将任务存放到队列中 默认添加到队尾
           // offer是往队列中存放任务
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
          
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            //从队列中取出元素   默认是从队头拿数据     
            if ((mActive = mTasks.poll()) != null) {
                //真正执行的方法
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }
    
    
    //THREAD_POOL_EXECUTOR就是一个在静态代码块创建的一个线程池
    //THREAD_POOL_EXECUTOR 也是static final修饰了
    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }
    
   
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    //核心线程数最大四个线程 或者cpu数-1 取小值
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    //最大线程数 cpu*2+1
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    //最大时长30s
    private static final int KEEP_ALIVE_SECONDS = 30;

上面的代码 我们知道了(比较关键的概念)
1、new 多个AsyncTask实例毫无意义 因为SERIAL_EXECUTOR、THREAD_POOL_EXECUTOR等等 都是被static final修饰了
2、AsyncTask存在1一个串口(只能一个个执行)形式的任务队列的一个代理类就是SERIAL_EXECUTOR 还存在一个执行任务的线程池THREAD_POOL_EXECUTOR

回到iAsyncTask.execute("url1", "url2", "url3");

   public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

sDefaultExecutor以及线程池的创建 以及里面核心方法我们讲解完了
真正方法的执行

 @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
            //这里面存在一个知识点就是如果调用executeOnExecutor多次会抛出异常 
        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是什么之前的代码也有讲解到
        exec.execute(mFuture);

        return this;
    }

FutureTask的分析使用

总结 其实也没什么好总结的

AsyncTask还是比较简单的 只是优缺点需要注意 以及使用上的注意即可

还有volatile关键字 是易变的 说白了是保证数据可见性 但是不能保证数据操作原子性 所以在多读场景使用

参考文章

悲观锁 乐观锁(里面用到CAS)

Java原子操作AtomicInteger的用法