Android开发——AsyncTask的使用以及源码解析

1AsyncTask使用介绍

 转载请标明出处:Android开发——AsyncTask的使用以及源码解析_SEU_Calvin的博客-CSDN博客

AsyncTask封装了ThreadHandler,通过AsyncTask可以很方便地在执行完后台任务后更新UI。如果不太清楚AndroidHandler机制,可以查看此篇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需要了解的方法:

(1)onPreExecute():运行在主线程。通常用来完成一些初始化的准备工作。本例中是显示一个进度条。

(2)doInBackground(Params…):运行在子线程。执行比较耗时的操作。在执行过程中可以调用publishProgress(Progress…values)来更新任务的进度,参数对应AsyncTask的第二个参数。doInBackground的参数对应AsyncTask的第一个参数,返回值对应AsyncTask的第三个参数。

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

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

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

task.cancle(true); 

1.4 使用AsyncTask的注意事项

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

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

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

4Android1.6之前,AsyncTask是串行执行任务的,之后采用线程池处理并行任务,又从3.0开始为了避免并发错误,用一个线程来串行执行任务。因此在3.0以后就不可以用AsyncTask并行执行任务了吗,当然不是,我们仍然可以通过executeOnExecutor方法执行并行任务

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

2AsyncTask源码解析

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

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用于保存我们传入的参数。在WorkerRunnable实例化的时候实现了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;  
    }  

在execute()中可以看出onPreExecute()是首先被执行的,然后将传入的参数通过mWorker封装为FutureTask对。接着调用了一个默认线程池的execute()方法。

从上面的代码中我们看到exec就是sDefaultExecutor,继续研究sDefaultExecutor

【拓展:FutureTask是什么】

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

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

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对象(充当了Runnable的作用)插入到任务队列mTasks的尾部。新建的Runnable对象判空后进入scheduleNext方法。然后通过mTasks.poll()被取出,然后交给一个叫THREAD_POOL_EXECUTOR的线程池去执行(前面的SerialExecutor用于任务的排队)

第二个任务继续入列,刚才新建的Runnable对象mActive不为null,并不会执行scheduleNext()。所以如果第一个任务比较慢,很多个任务都会进入队列等待。

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

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

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

所以说执行mFuture的run()方法最终会调用AsyncTask构造方法中的实例化WorkerRunnable时里面的call()方法。将参数传递给doInBackground()方法,然后将返回值传给postResult()方法。

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

2.5 postResult的实现

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

在postResult()方法中使用了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;    
        }    
    }    
}  

收到消息后,switch/case中有两种消息,一个是MESSAGE_POST_RESULT,一个是MESSAGE_POST_PROGESS。

前者就执行finish(),继而查看finish的实现。

2.7 MESSAGE_POST_RESULT消息的详细处理

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

也就是说收到POST_RESULT消息后,先判断是否当前任务被取消掉了,就会调用onCancelled()方法,如果没有被取消,则调用onPostExecute()方法,这样当前任务的执行就全部结束了

2.8 MESSAGE_POST_PROGRESS消息的发出

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

收到POST_PROGRESS的消息后,调用的正是进度相关的onProgressUpdate()方法,这个消息是在publishProgress()方法中发出的。

可以看出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个任务在执行。如果不想使用默认的线程池,还可以自定义线程池进行配置。

比如使用如下的代码允许在同一时刻有12个任务正在执行,并且最多能够存储100个任务。

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

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

转载请标明出处:Android开发——AsyncTask的使用以及源码解析_SEU_Calvin的博客-CSDN博客

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值