1.AsyncTask使用介绍

 转载请标明出处:http://blog.csdn.net/seu_calvin/article/details/52172248

AsyncTask封装了Thread和Handler,通过AsyncTask可以很方便地在执行完后台任务后更新UI。如果不太清楚Android的Handler机制,可以查看此篇Android消息机制详解

1.1  AsyncTask实例使用

下面是一个使用AsyncTask的实例,通过指定URL利用网络下载资源(此例模拟资源为字符串),以模拟耗时任务。在下载过程中,会通过进度条对话框向用户展示进度。在完成任务后将字符串展示在TextView上。具体实现细节后面会加以讲述,顺便引出AsyncTask的知识。

public class MainActivity extends Activity{
private TextView show;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
show = (TextView) findViewById(R.id.show);
}
//按钮事件响应方法 URL可自定义
public void download(View source) throws Exception{
DownTask task = new DownTask(this);
task.execute(new URL(URL));
}
class DownTask extends AsyncTask<URL, Integer, String>{ //自定义Task类继承AsyncTask
ProgressDialog pdialog;
int hasRead = 0;
Context mContext;
public DownTask(Context ctx){
mContext = ctx;
}
@Override
protected String doInBackground(URL... params){ //doInBackground方法在子线程执行耗时任务
StringBuilder sb = new StringBuilder();
try{
URLConnection conn = params[0].openConnection();
BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "utf-8"));
String line = null;
while ((line = br.readLine()) != null){
sb.append(line + "\n");
hasRead++;
publishProgress(hasRead);
}
return sb.toString();
}
catch (Exception e){
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(String result){ //主线程执行
// 展示下载下来的字符串 并将进度条对话框dismiss
show.setText(result);
pdialog.dismiss();
}
@Override
protected void onPreExecute(){ //主线程执行
pdialog = new ProgressDialog(mContext);
pdialog.setTitle("任务正在执行中");
pdialog.setMessage("请等待...");
// 设置对话框不能用“取消”按钮关闭
pdialog.setCancelable(false);
pdialog.setMax(MAX);
// 设置对话框的进度条风格
pdialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
// 设置对话框的进度条是否显示进度
pdialog.setIndeterminate(false);
pdialog.show();
}
@Override
protected void onProgressUpdate(Integer... values){ //主线程执行
// 更新进度
show.setText("已经读取了" + values[0] + "行");
pdialog.setProgress(values[0]);
}
}
}

1.2  AsyncTask参数介绍

看了上面的例子,我们会解释例子中涉及到的AsyncTask知识,首先是参数介绍:

AsyncTask定义了三种泛型类型 Params,Progress和Result。也是可以指定为空的,如 AsyncTask<Void, Void, Void>

Params:启动任务执行的输入参数的类型,本例中为URL。

Progress:后台任务执行的进度值类型,这里为Integer。

Result:后台执行任务最终返回的结果类型,这里为字符串String。

1.3  AsyncTask回调方法介绍

下面是介绍使用AsyncTask需要了解的方法:

onPreExecute():运行在主线程。调用excute()方法时执行,当任务执行之前调用此方法。通常用来完成一些初始化的准备工作。本例中是显示一个进度条。

doInBackground(Params…):运行在子线程。执行比较耗时的操作。在执行过程中可以调用publishProgress(Progress…values)来更新任务的进度。

doInBackground的参数对应AsyncTask的第一个参数,publishProgress(Progress…)的参数对应AsyncTask的第二个参数,其返回值对应AsyncTask的第三个参数。

onPostExecute(Result result) :运行在主线程。相当于Handler 处理UI的方式, doInBackground 执行完毕后回调该方法,参数为doInBackground的返回值。

onProgressUpdate(Progress…)  :运行在主线程。用于显示任务执行的进度,在doInBackground方法中调用publishProgress更新进度时会回调此方法。

onCancelled() :运行在主线程。用户调用了cancel(true)取消操作。

task.cancle(true); 

1.4 使用AsyncTask的注意事项

(1)AsyncTask必须在主线程中创建,execute方法也必须在UI线程调用,原因后面分析源码自然会明白。

(2)不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...),onProgressUpdate(Progress...)这几个方法。

(3)task只能被执行一次,多次调用时将会出现异常。

(4)Android1.6之前,AsyncTask是串行执行任务的,1.6之后采用线程池处理并行任务,又从3.0开始为了避免并发错误,又采用了一个线程来串行执行任务。

因此在3.0以后就不可以用AsyncTask并行执行任务了吗,当然不是,我们仍然可以通过executeOnExecutor方法执行并行任务。

下面的源码解析基于Android4.0版本。

2.AsyncTask源码解析

AsyncTask在Android中是如何实现的,下面进行源码分析。

2.1 AsyncTask的构造函数

public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return postResult(doInBackground(mParams));
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
...
}
};
}

构造函数中创建了WorkerRunnable和FutureTask两个实例,并把mWorker传递给了mFuture。让我们看一下WorkerRunnable类。

2.2 WorkerRunnable抽象类

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}

WorkerRunnable是Callable的子类,且包含一个mParams用于保存我们传入的参数。在AsyncTask中构造方法中完成了初始化,并且因为是一个抽象类,在这里new了一个实现类,并且实现了call方法,call方法中设置mTaskInvoked=true,且最终调用doInBackground(mParams)方法,并返回Result值作为参数给postResult方法。


2.3 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) { //switch意味着不能重复执行execute方法
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()是首先被执行的,然后将参数通过mWorker封装为FutureTask对象。接着调用了exec.execute(),从上面的代码中我们看到exec就是sDefaultExecutor,继续研究sDefaultExecutor。

【拓展:FutureTask是什么】

FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

2.4 实际上执行的是SerialExecutor的execute()方法

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
……
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; 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);
}
}
}

通过以上代码可以很明显的看出,实际上执行的是SerialExecutor的execute()方法。上面也分析过了,我们的参数被封装为了FurtherTask对象,并在这里充当了Runnable的作用。

execute首先将FurtherTask对象插入到任务队列mTasks中。

第一个任务入队,调用offer方法将传入的Runnable对象添加到队列尾部。判空后进入scheduleNext方法。然后在mActive =mTasks.poll()) != null被取出,从队列的头部取值,并且赋值给mActivte,然后交给线程池THREAD_POOL_EXECUTOR去执行(而SerialExecutor用于任务的排队)。

然后第二个任务入队,继续入列,但是此时mActive并不为null,并不会执行scheduleNext()。所以如果第一个任务比较慢,很多个任务都会进入队列等待。

真正执行下一个任务的时机是,线程池执行完成第一个任务以后,调用Runnable中的finally代码块中的scheduleNext。

这样就形成了一种链状调用结构,只要mTasks里面还有任务,就会不断逐一调用,如果后面有任务进来,就只要添加到mTasks里面即可。

这里给execute()传递的参数是mFuture,所以会执行到mFuture的run()方法,而run()方法最终会调用callable.call(),而callable就是mWorker,便回到了我们在2.1中看到的代码。

 public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return postResult(doInBackground(mParams));
}

doInBackground()方法将它的返回值传给了postResult。继续查看postResult的实现。

2.5 postResult的实现

private Result postResult(Result result) {
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}

原来是使用了Handler机制发送消息,那我们看下处理消息的地方。不懂Handler机制的可以查看之前写过的一篇文章Android消息机制详解

2.6 消息处理

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:
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}

收到MESSAGE_POST_RESULT就执行finish(),继而查看finish的实现。

2.7 MESSAGE_POST_RESULT消息的详细处理

private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}

可以看到,如果当前任务被取消掉了,就会调用onCancelled()方法,如果没有被取消,则调用onPostExecute()方法,这样当前任务的执行就全部结束了。

我们注意到还有一种MESSAGE_POST_PROGRESS的消息类型,这种消息是用于当前进度的,调用的正是onProgressUpdate()方法,那很自然想到的是publishProgress()方法。查看该方法源码。

2.8 MESSAGE_POST_PROGRESS消息的发出

protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}

可以看出AsyncTask也是使用的异步消息处理机制,只是做了非常好的封装而已。
所以在doInBackground()方法中调用publishProgress()方法才可以从子线程切换到UI线程,从而完成对UI元素的更新操作。

3 . Android3.0以前(1.6以后)

Android 3.0之前是并没有SerialExecutor这个类的,那个时候是直接在AsyncTask中构建了一个sExecutor常量,并对线程池总大小,同一时刻能够运行的线程数做了规定,参数设置如下。

private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 10;
……
private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);

3.0以前规定同一时刻能够运行的线程数为5个,线程池总大小为128,排队等待数量10个。也就是说当我们启动了10个任务时,只有5个任务能够立刻执行,另外的5个任务则需要等待,当有一个任务执行完毕后,第6个任务才会启动,以此类推。而线程池中最大能存放的线程数是128个,当我们尝试去添加第129个任务时,程序就会崩溃,发出java.util.concurrent.RejectedExecutionException异常。

上面通过源码也分析过,3.0之后的AsyncTask同时只能有1个任务在执行。如果不想使用默认的线程池,还可以自由地进行配置。比如使用如下的代码,不是使用SerialExecutor,允许在同一时刻有12个任务正在执行,并且最多能够存储100个任务。

Executor exec = new ThreadPoolExecutor(12, 100, 10,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
new DownloadTask().executeOnExecutor(exec);

至此关于AsyncTask的内容总结完毕,

转载请标明出处:http://blog.csdn.net/seu_calvin/article/details/52172248

Android开发——AsyncTask的使用以及源码解析的更多相关文章

  1. Android开发学习之路-Volley源码解析

    从简单的StringRequest入手看看Volley的工作机制. 先简单说下Volley的用法: ① 获取一个RequestQueue mRequestQueue = Volley.newReque ...

  2. 50个Android开发人员必备UI效果源码[转载]

    50个Android开发人员必备UI效果源码[转载] http://blog.csdn.net/qq1059458376/article/details/8145497 Android 仿微信之主页面 ...

  3. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  4. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  5. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  6. Android进阶:五、RxJava2源码解析 2

    上一篇文章Android进阶:四.RxJava2 源码解析 1里我们讲到Rxjava2 从创建一个事件到事件被观察的过程原理,这篇文章我们讲Rxjava2中链式调用的原理.本文不讲用法,仍然需要读者熟 ...

  7. [转载] 50个Android开发人员必备UI效果源码

    好东西,多学习! Android 仿微信之主页面实现篇Android 仿微信之界面导航篇Android 高仿QQ 好友分组列表Android 高仿QQ 界面滑动效果Android 高仿QQ 登陆界面A ...

  8. Android状态机StateMachine使用举例及源码解析

    Android frameworks源码StateMachine使用举例及源码解析 工作中有一同事说到Android状态机StateMachine.作为一名Android资深工程师,我居然没有听说过S ...

  9. Android八门神器(一): OkHttp框架源码解析

    HTTP是我们交换数据和媒体流的现代应用网络,有效利用HTTP可以使我们节省带宽和更快地加载数据,Square公司开源的OkHttp网络请求是有效率的HTTP客户端.之前的知识面仅限于框架API的调用 ...

随机推荐

  1. ruby 正则表达式 匹配中文

    1.puts /[一-龥]+/.match("this is 中文")                 =>中文 2.str2="123中文"puts / ...

  2. Java的API及Object类、String类、字符串缓冲区

    Java 的API 1.1定义 API: Application(应用) Programming(程序) Interface(接口) Java API就是JDK中提供给开发者使用的类,这些类将底层的代 ...

  3. str中的join方法,fromkeys(),set集合,深浅拷贝(重点)

    一丶对之前的知识点进行补充 1.str中的join方法.把列表转换成字符串 # 将列表转换成字符串,每个元素之间用_拼接 s = "_".join(["天",& ...

  4. 监听textarea数值变化

    监听textarea数值变化 $('#id').bind('input propertychange', function(){ //TODO });

  5. Got error 28 from storage engine的错误处理

    早上例行检查数据库,发现Got error 28 from storage engine这个错误,天那,我的数据.心里哇凉....备份的时间还是很久以前.最近更新了不少,麻烦大了. 好在找到了解决方法 ...

  6. 大家一起和snailren学java-(序)

    由于最近在面试实习的时候,发现自己的java基础还是不是特别的扎实.因此再重新深入学习一下java.每天学习一点,都能进步一些. 用书<Thinking in java><effec ...

  7. (原创)linux下Microsoft/cpprestsdk支持https(server)

    原创,转载请标明源地址 之前看网上一堆的资料说Microsoft/cpprestsdk不支持https或者说只支持window下的https,差点就被误导了,没办法,只好自己去翻了下源代码 先说明下l ...

  8. java uuid第一次性能

    在java中产生uuid的方式是使用java.util.UUID. UUID.randomUUID().toString(); 我在测试redis性能时,使用uuid产生测试数据,发现多线程测试red ...

  9. javaSe-SimpleDateFormat

    SimpleDateFormat呢是一种可以将字符串转为日期或者日期转换成字符串的功能强大的不得了的类: import java.text.ParseException;import java.tex ...

  10. jsp四大作用域之page

    <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding= ...