关于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交流,一起探讨和完善这种异步任务线程。