前言
Android中,线程是操作系统调度的最小单位。线程分为主线程和子线程。主线程用来处理界面的交互,而耗时操作(网络请求,复杂的数据库查询)必须在子线程中来完成。通过Handler消息机制完成主线程和子线程之间的通信。
每个任务都需要一个线程去执行,但是不可能每个任务的执行都是伴随着线程的销毁和重新创建,十分的耗费性能。所以用线程池去缓存一定数目的线程,由线程池来管理执行任务的线程,避免了频繁的创建和销毁。Android中提供了AsyncTask类,内部就是由线程池和Handler实现的。各位大佬,这篇看了包会!
(一) AsyncTask使用
public abstract class AsyncTask
AsyncTask是一个抽象类,如果使用的话需要自定义一个类继承它。它提供了4个核心的方法。这里用一个示例来做介绍(Kotlin环境下)。
一.使用和核心方法介绍
1.1 自定义类继承AsyncTask
/*静态属性声明处,此处MyAsyncTask为静态类*/
companion object {
class MyAsyncTask(private var taskName: String) : AsyncTask<String, Int, String>() {
/*核心方法1*/
override fun onPreExecute() {
super.onPreExecute()
Log.d("MyAsyncTask", taskName + " OnPreExecute")
}
/*核心方法2*/
override fun doInBackground(vararg params: String?): String {
try {
Thread.sleep(5000)
} catch (e: Exception) {
e.printStackTrace()
}
/*辅助方法*/
publishProgress(50)
Log.d("MyAsyncTask", taskName + " doInBackground")
return taskName
}
/*核心方法3*/
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
Log.d("MyAsyncTask",
"I am Task" + result + " executed at " + SimpleDateFormat("yyyy-mm-dd HH:mm:ss").format(Date()))
}
/*核心方法4*/
override fun onProgressUpdate(vararg values: Int?) {
super.onProgressUpdate(*values)
Log.d("MyAsyncTask", taskName + " onProgressUpdate")
}
}
}
声明好了静态内部类MyAsyncTask后,小手一点Button执行异步任务。
/*execute是Button控件的id,这里没有使用findViewById()*/
execute!!.setOnClickListener({
MyAsyncTask("AsyncTask1").execute()
})
介绍一下上面点击按钮执行任务操作,在项目中是没有写findViewById()去初始化Button,通过Kotlin Android Extensions拓展插件直接可以使用xml布局里Button的id来调用相应的方法,两个字:简便。有兴趣的同学可以去Kotlin官网了解一波。下面是MyAsyncTask执行的结果。
根据图示介绍一下AsyncTask的核心方法
1-2.onPreExecute()
在主线中执行,用于异步任务执行之前做一些准备工作,比如初始化加载进度条之类。
1-3.doInBackground(vararg params: String?)
在子线程中执行,真正执行异步任务的方法,参数是异步任务输入参数。在此方法中可以通过 publishProgress()方法来更新异步任务当前的进度,调用publishProgress()方法会同步调用onProgressUpdate()。
1-4. onProgressUpdate(vararg values: Int?)
在主线程中执行,此方法用来实时显示当前任务的执行进度。作用是可以在此处更新UI上的进度条进度或者做其他操作。
1-5. onPostExecute(result: String?)
在主线程中执行,异步任务执行结束之后会执行此方法。参数是doInBackground()的返回值。
AsyncTask的核心方法就是上面所述,执行任务是在子线程中,而任务准备工作,进度更新等全是在主线程中完成。AsyncTask实例的创建也是要在主线程中去执行。
二.源码解析
根据代码执行顺序,我们首先看下AsyncTask的 execute()方法。
/*注解,主线程中执行*/
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
@MainThread
public final AsyncTask 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 后台执行任务所需参数*/
mWorker.mParams = params;
/*线程池执行线程任务,mFuture封装了开启线程的mWorker*/
exec.execute(mFuture);
return this;
}
我们看下上面executeOnExecutor()方法中几个重要的属性。exec →* SerialExecutor*,mWorker → WorkerRunnable和mFuture → FutureTask。
WorkerRunnable:任务执行返回的结果
这里mWorker对象的创建是在AsyncTask的构造函数中,本质是开启一个线程,call()方法将任务执行完的结果返回。
mWorker = new WorkerRunnable() {
public Result call() throws Exception {
//标记任务已经被调用过
mTaskInvoked.set(true);
Result result = null;
try {
//将线程的优先级设置为后台,为了执行完任务方便回收
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//后台执行任务
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
//有异常将此任务取消
mCancelled.set(true);
throw tr;
} finally {
//任务结束
postResult(result);
}
return result;
}
};
FutureTask:线程任务(理解为要执行的任务)
这里将mWorker任务的参数封装成FutureTask对象,按照中式英语理解的话就是将来要执行的任务。本质是一个Runnable对象。
mFuture = new FutureTask(mWorker) {
@Override
/*任务完成后的回调*/
protected void done() {
try {
/*调用下方postResult()方法*/
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) {
/*调用下方postResult()方法*/
postResultIfNotInvoked(null);
}
}
};
当线程任务执行结束后,调用postResult(Result result)通过InternalHandler将线程池(子线程任务)切换到主线程中。
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult(this, result));
message.sendToTarget();
return result;
}
getHandler()获取的就是InternalHandler对象,这是一个静态内部类。切换线程作用。
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@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;
}
}
}
exec:线程池执行类
exec是SerialExecutor串行线程池对象,主要作用是将要执行的任务(mFutureTask)通过此类被插入到任务队列mTasks中,整合任务执行顺序作用。会不断从队列中取出任务来执行,如果当前任务执行完毕,mTasks会查找队列顶端是否有mFutureTask对象。有的话就执行。没有任务结束。
/*源码中不是这样写的,这里为了方便理解*/
public static final Executor exec = new SerialExecutor();
private static class SerialExecutor implements Executor {
/*任务队列*/
final ArrayDeque mTasks = new ArrayDeque();
/*活跃的任务,理解为正在执行得任务*/
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() {
/*队列最上方的一个元素不为null即队列顶端有空闲的任务*/
if ((mActive = mTasks.poll()) != null) {
/*真正执行任务的线程池:THREAD_POOL_EXECUTOR核心类ThreadPoolExecutor对象*/
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
其实真正执行任务的线程池是:ThreadPoolExecutor核心类的对象THREAD_POOL_EXECUTOR。
THREAD_POOL_EXECUTOR.execute(mActive);
目前 AsyncTask在Android3.0以上版本的执行顺序是串行的,有个多个任务添加到mTasks队列中,执行的顺序是从队列顶端往下依次执行。如果想实现多线程的并行任务。只要将
MyAsyncTask("AsyncTask1").execute()
替换为下述即可:
MyAsyncTask("AsyncTask1").executeOnExecutor()
结尾
Android中多线程任务虽然没有Java中使用频率高,但是是一个比较重要的知识点。AsyncTask可以在很多多线程任务中发挥好的作用,比如项目中的在线更新等。线程池也是特别重要的知识点,几乎是面试必问,接下来将会介绍线程池的工作原理等相关知识。