关于Android的异步操作,我在文章《Android开发实践:线程与异步任务》中介绍了两种方法,一种是采用线程,另一种是采用AsyncTask,今天再深入探讨下另一种模型:命令式的异步任务管理。
一般而言,单一的异步操作,比如从指定的网址下载一个图片采用线程或者AsyncTask是很方便的;如果异步任务是需要循环处理,比如定时更新某个UI控件,那么,可以通过定时器或者线程+Sleep循环来实现。那么,还有这么一种需求,需要你通过命令来与你的异步线程进行交互,比如你要远程操作一台设备,可以执行:打开、获取参数、设置参数、发送命令、关闭等等,这样的需求下,你的确可以通过对每一个命令开启一个线程的方式来实现,然而还有更好地方法么?
针对这种需求,我们来一起设计了一个基于命令模式的异步任务线程,在该线程启动后,在你没有发送命令的情况下,休眠等待;你可以连续向该线程发送一系列的命令,该线程会唤醒,并依次执行你所发送的命令,并在需要的情况下回调通知命令的发生者。
1. 命令模式
根据需求,我们可以知道,这样的设计采用命令模式最好不过了,所谓命令模式,就是将一个请求或者操作封装到一个对象中,命令的管理者不需要知道每个命令的具体参数和执行方法,只需要通过队列将命令对象缓冲起来,并在需要的时候依次调用命令对象的执行接口即可。
在这里,我们的任务就可以抽象为命令模式中的“命令”,首先设计任务的接口:
public interface Tasker {
public void execute();
}
2. 任务的定义
根据命令模式的定义,每个任务都可以设计为一个类,实现该Tasker的接口,作为示例,我实现了Tasker接口的对象的两个简单的任务类:
(1)“空任务”类,啥都不做
public class EmptyTasker implements Tasker {
@Override
public void execute() {
}
}
(2)“延时任务”类,负责在指定的延时之后执行任务
public class DelayTasker implements Tasker {
private final long mDelayTime;
private final TimeArrivedListener mListener;
//回调函数,通知外界延时时间到
public interface TimeArrivedListener {
public void onDelayTimeArrived();
}
//通过构造函数传递所需的参数
public DelayTasker( long delayTime, TimeArrivedListener listener){
mDelayTime = delayTime;
mListener = listener;
}
@Override
public void execute() {
try {
Thread.sleep(mDelayTime);
mListener.onDelayTimeArrived();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3. 任务管理者
任务管理者负责管理任务队列,实现任务的执行,同时向使用者提供可访问的接口,主要包括如下实现:
(1) 任务执行线程
负责从任务队列取任务对象,并且调用任务的执行函数执行任务代码。
(2) 任务队列
以先进先出的形式管理任务队列,并且要注意多线程的读写安全。
(3) 调用接口
一般包括:初始化(开启任务线程)、逆初始化(关闭任务线程)、添加命令
我设计的任务管理者代码如下,你可以根据自己的需求添加/修改/删除接口或者实现细节。
public class TaskExecutor {
//控制线程执行/退出的标志变量
private volatile boolean mIsRunning = true;
//锁和条件变量
private Lock mLock = new ReentrantLock();
private Condition mCondition = mLock.newCondition();
//任务列表
private Queue<Tasker> mTaskerQueue = new LinkedList<Tasker>();
//初始化函数,开启任务等待线程
public void initilize() {
new Thread(new WorkRunnable()).start();
}
//销毁函数,关闭任务等待线程
public void destroy() {
//线程退出命令
mIsRunning = false;
//添加一个任务,唤醒mCondition.await
addTasker(new EmptyTasker());
}
//添加一个新任务
public void addTasker( Tasker tasker ) {
mLock.lock();
mTaskerQueue.add(tasker);
mCondition.signal();
mLock.unlock();
}
//获取下一个任务,如果任务列表为空,则阻塞
private Tasker getNextTasker() {
mLock.lock();
try {
//如果任务队列为空,则阻塞等待
while( mTaskerQueue.isEmpty() ) {
mCondition.await();
}
//返回队列首任务,并从队列删除掉
return mTaskerQueue.poll();
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally {
mLock.unlock();
}
return null;
}
//任务等待/执行线程
private class WorkRunnable implements Runnable {
@Override
public void run() {
while(mIsRunning) {
Tasker tasker = getNextTasker();
tasker.execute();
}
Log.d("TaskExecutor", "WorkRunnable run exit ! ");
}
}
}
4. 使用案例
这里,我简单地应用上述异步任务线程完成了一个app示例,该app只有一个Button,每点击一次就延时1s弹出一个Toast消息,当你连续点击Button的时候,就会从1往后不断加1,连续的显示。
该app的MainActivity代码如下:
public class MainActivity extends Activity implements TimeArrivedListener {
private TaskExecutor mTaskExecutor;
private int mClickTimes = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTaskExecutor = new TaskExecutor();
mTaskExecutor.initilize();
}
@Override
protected void onDestroy() {
mTaskExecutor.destroy();
super.onDestroy();
}
public void onClickDelay(View v) {
//每点击一次按钮,则延时1s,弹出消息提示
mTaskExecutor.addTasker(new DelayTasker(1000, this));
}
@Override
public void onDelayTimeArrived() {
mClickTimes++;
this.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this,"This is the " + mClickTimes + " times click" , Toast.LENGTH_SHORT).show();
}
});
}
}
基于命令模式的异步任务线程就探讨到这里了,在你的Android工程中可以很容易地移植TaskExecutor类,本工程的项目代码见博文后面的附件,有任何疑问欢迎留言或者来信lujun.hust@gmail.com交流,一起探讨和完善这种异步任务线程。