欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

详解Android中用于线程处理的AsyncTask类的用法及源码

程序员文章站 2024-02-29 21:56:16
为什么要用asynctask 我们写app都有一个原则,主线程不能够运行需要占用大量cpu时间片的任务,如大量复杂的浮点运算,较大的磁盘io操作,网络socket等,这些...

为什么要用asynctask
我们写app都有一个原则,主线程不能够运行需要占用大量cpu时间片的任务,如大量复杂的浮点运算,较大的磁盘io操作,网络socket等,这些都会导致我们的主线程对用户的响应变得迟钝,甚至anr,这些会使应用的用户体验变差,但是有时又的确需要执行这些耗时的任务,那么我们通常可以使用asynctask或者new thread
来处理,这样把任务放入工作线程中执行,不会占用主线程的时间片,所以主线程会及时响应用户的操作,如果使用new thread来执行任务,那么如果需要中途取消任务执行或者需要返回任务执行结果,就需要我们自己维护很多额外的代码,而asynctask是基于concurrent架包提供的并发类实现的,上面的二个需求都已经帮我们封装了,这也是我们选择asynctask的原因。

怎么用asynctask
我们还是简单介绍下asynctask一些使用示例。我们先新建一个类demoasynctask继承asynctask,因为asynctask是抽象类,其中doinbackground方法必须重写。

private class demoasynctask extends asynctask<string, void, void> {
 @override
 protected void onpreexecute() {
  super.onpreexecute();
 }

 @override
 protected void doinbackground(string... params) {
  return null;
 } 

 @override
 protected void onpostexecute(void avoid) {
  super.onpostexecute(avoid);
 }

 @override
 protected void onprogressupdate(void... values) {
  super.onprogressupdate(values);
 }

 @override
 protected void oncancelled(void avoid) {
  super.oncancelled(avoid);
 }

 @override
 protected void oncancelled() {
  super.oncancelled();
 }
}

demoasynctask task = new demoasynctask();
task.execute("demo test asynctask");
//task.executeonexecutor(asynctask.serial_executor, "test");
//mytask.executeonexecutor(asynctask.thread_pool_executor, "test");

简单分析下
上面就是asynctask最简单的使用方法,我们上面重写的方法中,oninbackground方法运行在工作线程,其他的方法全部运行在主线程,另外它的运行方式android提供给我们2个方法,上面都列出了。

1.第一个方法会使用默认的executor执行我们的任务, 其实也就是serial_executor,serial_executor我们其实也是可以通过方法去自定义的,android帮我们的默认实现是逐个执行任务,也就是单线程的,关于asynctask的任务执行是单线程实现还是多线程实现还有一段很有意思的历史,较早的版本是单线程实现,从android2.x开始,google又把它改为多线程实现,后来google发现,多线程实现的话,会有很多需要保证线程安全的额外工作留给开发者,所以从android3.0开始,又把默认实现改为单线程了,今天我们演示的是framwork代码版本是21(android5.0)。
2.同时也提供了多线程实现的接口,即我们上面写的最后一行代码 asynctask.thread_pool_executor。

public final asynctask<params, progress, result> execute(params... params) {
 return executeonexecutor(sdefaultexecutor, params);
}
private static volatile executor sdefaultexecutor = serial_executor;

其实相信大家平时工作学习中经常使用asynctask,我们直接去看asynctask类源码(插一句题外话,平时大家也可以把自己工作学习中的心得体会总结一下,记下来~~)

public abstract class asynctask<params, progress, result> {
....
}

asynctask抽象类,有三个泛型参数类型,第一个是你需要传递进来的参数类型,第二个是任务完成进度的类型一般是integer,第三个是任务完成结果的返回类型,有时这些参数不是全部需要,不需要的设为void即可,另外result只有一个,但params可以有多个。
我们可以看到asynctask的默认构造器初始化了二个对象,mworker和mfuture。

private final workerrunnable<params, result> mworker;
private final futuretask<result> mfuture;
 mworker = new workerrunnable<params, result>() {
  public result call() throws exception {
   mtaskinvoked.set(true);

   process.setthreadpriority(process.thread_priority_background);
   //noinspection unchecked
   return postresult(doinbackground(mparams));
  }
 };

 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 occured while executing doinbackground()",
      e.getcause());
   } catch (cancellationexception e) {
    postresultifnotinvoked(null);
   }
  }
 };

mwoker实现了callback接口,callback接口是jdk1.5加入的高级并发架包里面的一个接口,它可以有一个泛型返回值。

public interface callable<v> {
/**
 * computes a result, or throws an exception if unable to do so.
 *
 * @return computed result
 * @throws exception if unable to compute a result
 */
v call() throws exception;
}

futuretask实现了runnablefuture接口,runnablefuture接口是jdk提供的,看名称就知道它继承了runnable和future接口,mfuture是futuretask的一个实例,可以直接被executor类实例execute。我们来继续看asynctask的execute方法。

public final asynctask<params, progress, result>  execute(params... params) {
 return executeonexecutor(sdefaultexecutor, params);
}
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;
}

先调用onpreexecute()方法,此时还在主线程(严格来说是调用asynctask执行的线程),然后exec.execute(mfuture),把任务交给exec处理,execute mfuture其实就是invoke mwoker,然后调用postresult(doinbackground(mparams)),此时已经运行在工作线程池,不会阻塞主线程。然后给mhandler发送message_post_result消息,然后调用finish方法,如果iscancelled,回调oncancelled,否则回调onpostexecute。

private result postresult(result result) {
 @suppresswarnings("unchecked")
 message message = shandler.obtainmessage(message_post_result,
   new asynctaskresult<result>(this, result));
 message.sendtotarget();
 return result;
}
private static final internalhandler shandler = new internalhandler();
private static class internalhandler extends handler {
 @suppresswarnings({"unchecked", "rawuseofparameterizedtype"})
 @override
 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;
   case message_post_progress:
    result.mtask.onprogressupdate(result.mdata);
    break;
  }
 }
}
private void finish(result result) {
 if (iscancelled()) {
  oncancelled(result);
 } else {
  onpostexecute(result);
 }
 mstatus = status.finished;
}

现在其实我们已经把asynctask整个执行任务的过程走完了,其中暴露给我们的那几个回调方法也都走到了。现在我们回过头来看,asynctask其实只是对jdk 1.5提供的高级并发特性,concurrent架包做的一个封装,方便开发者来处理异步任务,当然里面还有很多细节处理的方法值得大家学习,如任务执行进度的反馈,任务执行原子性的保证等,这些留给大家自己学习了。

源码分析
下面我们再深入一些,来看asynctask的源码。下面分析这个类的实现,主要有线程池以及handler两部分。

线程池
当执行一个asynctask的时候调用的是execute()方法,就从这个开始看:

public final asynctask<params, progress, result> execute(params... params){
 return executeonexecutor(sdefaultexecutor, params);
}
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
 onpreexecute(); 
 
 mworker.mparams = params; 
 
 exec.execute(mfuture); 
 return this; 
} 

execute方法会调用executeonexecutor。在这个方法中先检查任务是否已经执行或者执行结束,然后把任务标记为running。最开始执行的是onpreexecute,接着把参数赋值给mworker对象。这个mworker是一个callable对象,最终被包装为futuretask,代码如下:

private static abstract class workerrunnable<params, result> implements callable<result> { 
 params[] mparams; 
} 

mworker = new workerrunnable<params, result>() { 
  public result call() throws exception { 
   mtaskinvoked.set(true); 

   process.setthreadpriority(process.thread_priority_background); 
   //noinspection unchecked 
   return postresult(doinbackground(mparams)); 
  } 
 };
 
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 occured while executing doinbackground()", 
     e.getcause()); 
  } catch (cancellationexception e) { 
   postresultifnotinvoked(null); 
  } 
 } 
}; 

   
从上面的代码可以看出,在mworker对象中的call()方法会调用doinbackground,返回值交给postresult方法,这个方法通过handler发送消息,这一点稍后再详细分析。

在mworker对象被封装成futuretask之后交由线程池执行,从execute方法可以看出,使用的是sdefaultexecutor,它的值默认为serial_executor,也就是串行执行器,实现如下:

 

private static class serialexecutor implements executor { 
 //线性双向队列,用来存储所有的asynctask任务 
 final arraydeque<runnable> mtasks = new arraydeque<runnable>(); 
 //当前正在执行的asynctask任务 
 runnable mactive; 

 public synchronized void execute(final runnable r) { 
  //将新的asynctask任务加入到双向队列中 
  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); 
  } 
 } 
}

public static final executor thread_pool_executor 
  = new threadpoolexecutor(core_pool_size, maximum_pool_size, keep_alive, 
    timeunit.seconds, spoolworkqueue, sthreadfactory); 

在上面的代码中,如果有任务执行,那么serialexecutor的execute方法会被调用,它的逻辑是把runnable对象加入arraydeque队列中,然后判断mactivie是否为空。第一次执行时mactive当然为空,所以执行schedulenext,其实就是取出任务队列中的第一个任务交给线程池(thread_pool_executor)执行。加入mtask队列的runnable对象的run方法里最终一定会调用schedulenext,那么又会从任务队列中取出队头任务执行。这样便实现了单线程顺序执行任务,所以在asynctask中默认启用的是单线程执行,只有上一个任务执行后才会执行下一个任务。如果想要启用多线程执行任务,可以直接调用 executeonexecutor(executor exec, params... params),这里的executor参数可以使用asynctask自带的thread_pool_executor,也可以自己定义。

handler
asynctask内部用handler传递消息,它的实现如下:

private static class internalhandler extends handler { 
 @suppresswarnings({"unchecked", "rawuseofparameterizedtype"}) 
 @override 
 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; 
   case message_post_progress: 
    result.mtask.onprogressupdate(result.mdata); 
    break; 
  } 
 } 
} 

如果消息类型是任务执行后的返回值(message_post_result)将调用finish()方法:

private void finish(result result) { 
 if (iscancelled()) { 
  oncancelled(result); 
 } else { 
  onpostexecute(result); 
 } 
 mstatus = status.finished; 
} 

从上面可以知道,如果任务取消了,将调用oncancelled,否则调用onpostexecute,所以一个asynctask任务如果取消了,那么onpostexecute将不会得到执行。

如果消息类型是执行进度(message_post_progress)将调用onprogressupdate,这个方法默认是空方法,我们可以根据自己的需要重写。

总结
asynctask的主要逻辑就如上面所分析的,总结几个需要注意的地方:

  • asynctask的类必须在ui线程加载(从4.1开始系统会帮我们自动完成)
  • asynctask对象必须在ui线程创建
  • execute方法必须在ui线程调用
  • 不要手动调用onpreexecute()、doinbackground、onprogressupdate方法
  • 一个任务只能被调用一次(第二次调用会抛出异常)