1. 介绍
在AsyncTask源码分析中解释了AsyncTask不能在子线程中创建的原因,其实并不是AsyncTask只能在UI线程创建,而是因为UI线程中有其他一般线程所没有的东西Looper和与配对出现的MessageQueue。现在开始制作一个基于Handler的任务模型,它可以让多个任务在另外开辟的一个子线程中同步执行,这样可以避免多线程同步问题,在某些情况下使用起来还是很方便的。
2. 使用实例
TaskTool.getInstance(this).postTask(new TaskInterface() {
@Override
public Integer doInBackground(Integer... param) {
// 在开辟的子线程中执行
int result = param[0];
// 耗时操作.......
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
@Override
public void onPostExecute(Integer result) {
// 在UI主线程中执行
Toast.makeText(ActivityMain.this, "result:" + result + ",isUiThread:" + isUIThread(),
Toast.LENGTH_SHORT).show();
}
}, 100);
这样TaskTool对创建它的线程环境是没有要求的,只要传递Context就好了,其实传递Context的目的还是在于传递ActivityThread中mainLooper,这是我们要求部分函数要在UI环境所必须要的东西。
3. 制作流程
- 定义回调接口
首先模仿AsyncTask定义任务回调接口
/**
* 任务回调接口
*/
public interface TaskInterface<Param, Result> {
/**
* 在子线程中运行
*
* @param param 需要的参数
* @return 耗时操作的结果
*/
Result doInBackground(Param... param);
/**
* 在Ui线程运行
*
* @param result 结果
*/
void onPostExecute(Result result);
}
这里仅仅是说明原理,AsyncTask中的进度提示接口函数没有加,需要的可以自行增加
- 初始化环境
要完成我们的需求,我们会需要这些要素 1.一个异于UI线程的开辟的子线程和依附于该线程的Handler任务处理模型 2. 基于mainLooper创建的Handler用来将指定操作中转到UI环境中。所以有
private Handler taskHandler;
@SuppressWarnings("rawtypes")
private UiHandler mainHandler;
private static final int MSG_TASK_WHAT = 0;
@SuppressWarnings("rawtypes")
private Map<TaskInterface, TaskRequest> cachedTaskRequestMap = new ConcurrentHashMap<TaskInterface, TaskRequest>();
@SuppressWarnings("rawtypes")
private TaskTool(Context context) {
HandlerThread thread = new HandlerThread("thread-task");
thread.start();
taskHandler = new Handler(thread.getLooper());
mainHandler = new UiHandler(context.getMainLooper());
}
其中的HandlerThread在 IntentService源码分析中已经介绍过,该线程内部维持了一个Handler消息处理模型,开启该线程的任务就是用来分发我们自己的任务消息和处理耗时操作,各个任务在该线程中是同步的。
- 任务投递
/**
* 将要执行的任务投递到任务队列中,结果通过 {@link TaskInterface}返回
* @param taskInterface
* @param params
*/
public <Param, Result> void postTask(TaskInterface<Param, Result> taskInterface, Param... params) {
TaskRequest<Param, Result> taskRequest = new TaskRequest<Param, Result>(taskInterface, mainHandler, params);
cachedTaskRequestMap.put(taskInterface, taskRequest);
taskHandler.post(taskRequest);
}
内部将我们任务封装为一个任务请求对象
@SuppressWarnings("rawtypes")
private class TaskRequest<Param, Result> implements Runnable {
private TaskInterface<Param, Result> taskInterface;
private UiHandler uiHandler;
private Param[] params;
public TaskRequest(TaskInterface<Param, Result> taskInterface, UiHandler uiHandler, Param[] params) {
super();
this.taskInterface = taskInterface;
this.uiHandler = uiHandler;
this.params = params;
}
@Override
public void run() {
if (taskInterface == null) {
return;
}
Result result = taskInterface.doInBackground(params);
if (result != null) {
Message msg = uiHandler.obtainMessage(MSG_TASK_WHAT, new TaskData<Param, Result>(taskInterface, result));
msg.sendToTarget();
}
}
}
将我们组织成的任务请求对象通过taskHandler投递到HandlerThread对应的MessageQueue中,等到MessageQueue取出该Message从而执行
TaskRequest的run()方法,从而执行doInBackground方法,该方法在HandlerThread线程中执行,然后将执行结果通过UiHandler投递到UI主线对应的mainLooper的MessageQueue,在看下UiHandler中的定义
private class UiHandler<Param, Result> extends Handler {
public UiHandler(Looper looper) {
super(looper);
}
@SuppressWarnings("unchecked")
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MSG_TASK_WHAT:
TaskData<Param, Result> data = (TaskData<Param, Result>) msg.obj;
Result result = data.getResult();
TaskInterface<Param, Result> taskInterface = data.getTaskInterface();
if (taskInterface != null) {
taskInterface.onPostExecute(result);
}
break;
default:
break;
}
}
}
在UiHandler的handleMessage()方法在UI线程中执行,这里把刚才在HandlerThread中的任务结果通过 onPostExecute传递,这样就完成了一个任务的声明周期。
3. 总结
这个过程涉及两个线程和两个线程对应的MessageQueue消息处理模型,反正就是想要在子线程中执行的动作就放在HandlerThread对应的MessageQueue中,想要在UI线程执行的动作就投递到UI线程对应的MessageQueue,项目的源码见 TaskTool