Android-AsyncTask讲解

AsyncTask就是安卓中一个对线程池的典型的应用,其中还涉及了之前博文中介绍的FutureTask等知识点,本文主要对AsyncTask源码做一次讲解

官方介绍

AnsycTask官方介绍.png

上面截图就是官方对AsyncTask的介绍,内容比较多就不逐字逐句翻译了,挑几个重要的解释一下。

  1. AnsycTask主要目的是执行短时间的操作,其他的任务更推荐使用线程池
  2. AnsycTask定义了后台计算的方法,同时结果更新在UI线程,定义了Params、Progress、Result分别用于表示执行参数、进度参数以及结果,不需要某个参数时可以用Viod。
  3. 使用过程相关的方法主要有如下几个:
  • onPreExecute():主要用于一些计算前的数据准备,在UI线程被调用
  • doInBackground(Params...):后台计算的方法,子线程中调用,一般在此方法调用publishProgress(Progress... values)用于更新进度等操作,在子线程调用
  • onProgressUpdate(Progress...):后台操作进度的更新到UI上,在UI线程中调用
  • onPostExecute(Result):计算结果的返回,在UI线程调用
  1. AnsycTask执行过程中可以取消,取消的形式和FutureTask一致
  2. AnsycTask实例只能在UI线程被创建,同时execute(Params...) 也只能在UI线程被调用,并且只能被调用一次,多次调用抛出异常。
  3. 多个AnsycTask实例对象分别调用execute(Params...)也是被同步执行的,这点比较坑,因为内部用的默认线程池是静态定义的,并且是同步的
  4. 调用AnsycTask的静态方法 execute (Runnable runnable),多个runnable也是同步执行的
  5. 调用AnsycTask的成员方法executeOnExecutor (Executor exec, Params... params)可指定任务执行的线程池,一般推荐调用系统默认实现的THREAD_POOL_EXECUTOR线程池。这样可以多个实例并行执行。

上面差不多就是AnsycTask的重点了下面就源码做一次解析。

先从线程池开始说起,与多线程相关的主要就是THREAD_POOL_EXECUTOR和sDefaultExecutor这个两个线程池。

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;
    }

之前博文中有介绍的,这个构建出来的线程池拥有的核心线程数是CORE_POOL_SIZE,最大线程数是MAXIMUM_POOL_SIZE,任务队列是sPoolWorkQueue,线程构建通过sThreadFactory,非核心线程存活时间为KEEP_ALIVE_SECONDS

核心线程的计算如下:

// We want at least 2 threads and at most 4 threads in the core pool,             
// preferring to have 1 less than the CPU count to avoid saturating               
// the CPU with background work                                                   
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));

最多为cpu核心的个数-1个。

任务队列构建如下:

private static final BlockingQueue<Runnable> sPoolWorkQueue =        
        new LinkedBlockingQueue<Runnable>(128);                      

可以看到最多为128个任务。

SerialExecutor

这个就是不指定线程池时任务默认处理用的线程池,来看一下里面的实现

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 {                                                
                    r.run();                                         
                } finally {                                          
                    scheduleNext();                                  
                }                                                    
            }                                                        
        });                                                          
        if (mActive == null) {                                       
            scheduleNext();                                          
        }                                                            
    }                                                                
                                                                     
    protected synchronized void scheduleNext() {                     
        if ((mActive = mTasks.poll()) != null) {                     
            THREAD_POOL_EXECUTOR.execute(mActive);                   
        }                                                            
    }                                                                
}                                                                    

内部很简单,将传递进来的任务保存在新的Runnable中并入队,等待被执行。

  1. 这个执行的过程时通过scheduleNext()来控制的,可以看出队列里任务都是等上一个任务执行完毕后再执行下一个任务实现了串行的效果。
  2. SerialExecutor实例对象在AsyncTask中以静态变量的形式存在,这意味着所有的子类共用这个默认的线程池,这也是为什么开头说的多个子类实例执行也是串行的原因所在。

构造方法

AsyncTask开始构造时,会初始化两个成员变量mWorker和mFuture,

  1. mWorker将耗时操作放在此执行,并将结果传递下去
  2. mFuture包装mWorker,传递给线程池执行,并处理结果

任务执行状态的区分主要靠这两个变量,我们来看一下源码

public AsyncTask() { 
    
    //其实是一个Callable                                                                           
    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                                                        
                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 {                                                                               
                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);                                                   
            }                                                                                   
        }                                                                                       
    };                                                                                          
}                                                                                               
 
 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;                                                   
 }                                                                                                                                                                                                           

WorkerRunnable本质上是一个Callable,配合下面的FutureTask,可以让线程池执行之后获取对应的结果。

上面的代码也可以看到一点点端倪,看AsyncTask是怎么执行代码的:

  1. mWorker开始的时候就mTaskInvoked状态置为了true(防止结果无法被处理)同时在当前线程调用了doInBackground(),保存了结果。
  2. 在将结果往下传递的过程中先判断了任务是否被中断,若任务被中断,则mCancelled置为true。
  3. mFuture获取结果,通过不同的状态讲结果处理。

任务的开启

上面说了任务的构造,这里说说任务的开启,先上代码

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

@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;                                                                   
}                                                                                                                                                              

这两个方法都在UI线程调用,其中不指定执行线程池时,默认使用AsyncTask内部实现的SerialExecutor线程池。

这里可以看到对任务的多次执行以及完成的任务再次执行都会抛出异常。

任务开启后调用了onPreExecute(),接着执行构造方法里面初始化的mFuture,上面我们分析过会调用doInBackground(),同时会根据不同状态处理结果,这样任务的开始后的逻辑就可以撸顺了。

任务执行静态方法

上面提到了2个任务执行的实例对象方法,AsyncTask中还存在一个静态方法execute(Runnable runnable)

 @MainThread                                     
 public static void execute(Runnable runnable) { 
     sDefaultExecutor.execute(runnable);         
 }                                               

其实就是用默认实现的SerialExecutor线程池去执行任务,这里可以提交多个任务,上面我们也分析过
SerialExecutor任务是串行执行的,这意味着,这个方法的多个任务也是串行执行的。

验证

  • 先验证,多个实例对象的方法是串行执行的

    public class AsyncTaskTest extends BaseActivity {                             
                                                                                  
        @BindView(R.id.progress_tv)                                               
        TextView mProgressView;                                                   
        @BindView(R.id.content_tv)                                                
        TextView mContentView;                                                    
                                                                                  
        @Override                                                                 
        protected int getContentViewResId() {                                     
            return R.layout.activity_async_task_test;                             
        }                                                                         
                                                                                  
                                                                                  
        @Override                                                                 
        protected void init() {                                                   
            super.init();                                                         
                                                                                  
            try {                                                                 
                for (int i = 0; i < 10; i++) {                                    
                    new AsyncTaskImpl().execute();                                
                }                                                                 
            } catch (Exception e) {                                               
                e.printStackTrace();                                              
            }                                                                     
        }                                                                         
                                                                                  
        private static class AsyncTaskImpl extends AsyncTask<Void, Void, Void> {  
                                                                                  
            @Override                                                             
            protected Void doInBackground(Void... voids) {                        
                try {                                                             
                    Thread.sleep(2000);                                           
                    System.out.println(System.currentTimeMillis());               
                } catch (InterruptedException e) {                                
                    e.printStackTrace();                                          
                }                                                                 
                return null;                                                      
            }                                                                     
        }                                                                         
    }                                                                             
                                                                                  
    

    打印如下:

    I/System.out: 1526473028045
    I/System.out: 1526473030050
    I/System.out: 1526473032054
    I/System.out: 1526473034059
    I/System.out: 1526473036061
    I/System.out: 1526473038065
    I/System.out: 1526473040067
    I/System.out: 1526473042070
    I/System.out: 1526473044073
    I/System.out: 1526473046075
    

    每次打印都间隔了2秒以上,符合我们任务串行执行的预期。

  • 再验证一下,调用静态任务执行方法也是串行执行的

        public class AsyncTaskTest extends BaseActivity {                                                                          
                                                                                 
        @Override                                                                
        protected int getContentViewResId() {                                    
            return R.layout.activity_async_task_test;                            
        }                                                                        
                                                                                                                                                    
        @Override                                                                
        protected void init() {                                                  
            super.init();                                                        
                                                                                 
            try {                                                                
                for (int i = 0; i < 10; i++) {                                   
                    AsyncTask.execute(new Runnable() {                           
                        @Override                                                
                        public void run() {                                      
                            try {                                                
                                Thread.sleep(2000);                              
                                System.out.println(System.currentTimeMillis());  
                            } catch (InterruptedException e) {                   
                                e.printStackTrace();                             
                            }                                                    
                        }                                                        
                    });                                                          
                }                                                                
            } catch (Exception e) {                                              
                e.printStackTrace();                                             
            }                                                                    
        }                                                                                                                                                                                                                                 
    }                                                                            
    
    I/System.out: 1526473373076
    I/System.out: 1526473375081
    I/System.out: 1526473377085
    I/System.out: 1526473379090
    I/System.out: 1526473381093
    I/System.out: 1526473383094
    I/System.out: 1526473385096
    I/System.out: 1526473387098
    I/System.out: 1526473389099
    I/System.out: 1526473391101
    

    打印如上,无需多言。

总结

  1. AsyncTask是Android官方提供的用于执行低耗时的工具类
  2. AsyncTask内部使用线程池执行任务
  3. AsyncTask实现多任务串行执行的模式
  4. AsyncTask能将任务执行的各个状态在方法中体现出来
  5. AsyncTask可以被取消,取消方式与FutureTask一致
  6. AsyncTask可以指定任务执行的线程池
  7. 多个AsyncTask的实例对象,默认是串行执行的
  8. 调用静态方法执行多个Runnable也是串行的
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容