基于移动客户端的软件特别强调实时性,Android程序更是如此,任何一个程序超过5s没有响应,都会被系统强制杀掉。而且Android也不允许在UI线程中进行任何网络操作,否则就会产生NetworkOnMainThreadException 异常。因此,凡是耗时的操作,都不应该直接出现在UI线程中。今天,我通过最简单直观地示例总结下Android开发中最常用的两种处理耗时操作的方法:一个是线程,另一个是异步任务。


首先,看看示例效果,点击Download后,进度条每1秒中增加1%,直到增加到100%。


203620491.png


我将分别用两种方式实现这个功能。


首先,给出 XML 的布局文件:


<LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
                                                                           
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="Download"
        android:onClick="onClickDownLoad"/>
                                                                      
    <TextView
        android:id="@+id/TextShow"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:text="0%"/>
                                                                                                 
    <ProgressBar
        android:id="@+id/ProgressBar"
        android:layout_margin="10dp"
        android:layout_width="match_parent"
        android:layout_height="30dp"                  
       style="@android:style/Widget.ProgressBar.Horizontal"/>
</LinearLayout>


(1) 线程(Thread,Runnable)


第一种方法是通过线程的形式来实现,代码如下:


public class DownloadRunnable implements Runnable {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
    private RunnableStateListener mStateListener;
    private String mURL;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
    public static interface RunnableStateListener {
        public void onRunnableUpdate(int progress);
        public void onRunnableComplete(boolean isSuccess);
    }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
    public DownloadRunnable(RunnableStateListener listener, String url ) {
        mStateListener = listener;
        mURL = url;
    }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
    @Override
    public void run() {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
        Log.d("DownloadTask", "Begin download, the URL is " + mURL );
        for( int i=1; i<=100; i++ ) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
            mStateListener.onRunnableUpdate(i);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
            try {
                Thread.sleep(500);
            }
            catch (InterruptedException e) {         
                e.printStackTrace();
                mStateListener.onRunnableComplete(false);
                return;
            }
        }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
        mStateListener.onRunnableComplete(true);
    }
}


Android/Java中,线程是通过 new Thread(Runnable runnable).start(); 来创建和执行的,所以可以先定义一个类实现 Runnalbe 接口,在 run 函数中完成耗时的操作。本类中,定义RunnableStateListener ,是为了方便线程与外界(调用者)交流,将线程中的任务运行状态传递到外界。


(2) 异步任务(AsyncTask)


另一种方法则是采用异步任务来实现,代码如下:


public class DownloadTask extends AsyncTask<String,Integer,Boolean> {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
    private TaskStateListener mTaskStateListener;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
    public static interface TaskStateListener {
        public void onTaskUpdate(int progress);
        public void onTaskComplete(boolean isSuccess);
    }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
    public DownloadTask(TaskStateListener listener) {
        mTaskStateListener = listener;
    }
                     
    @Override
    protected Boolean doInBackground(String ... params )
                                        
        Log.d("DownloadTask", "Begin download, the URL is " + params[0] );
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
        for( int i=1; i<=100; i++ ) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
            //会回调onProgressUpdate
            super.publishProgress(i);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
            try {
                Thread.sleep(500);
            }
            catch (InterruptedException e) {         
                e.printStackTrace();
                return Boolean.FALSE;
            }
        }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
        return Boolean.TRUE;
    }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
    @Override
    protected void onProgressUpdate(Integer... values)
    {         
        mTaskStateListener.onTaskUpdate(values[0]); 
        super.onProgressUpdate(values);
    }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
    @Override
    protected void onPostExecute(Boolean result) {
        mTaskStateListener.onTaskComplete(result);
    }
}


异步任务与线程的实现方式有很大不同,异步任务主要通过实例化AsyncTask类来实现,该类有三个接口,doInBackground,该函数是任务的主体部分,将耗时的操作可以放在这里;onProgressUpdate是由系统回调的函数,当doInBackground主体中调用了publishProgress后,则会进入onProgressUpdate更新当前任务的状态,因此,在这里可以通过本类定义的TaskStateListener将状态传递给外界(调用者);而onPostExecute则是在doInBackground主体任务return(结束)后由系统回调。


AsyncTask的原型定义如下:AsyncTask<Params, Progress, Result>,有点像C++里的模板,子类可以实例化这三个参数,依次对应 doInBackground 的参数,onProgressUpdate的参数,onPostExecute的返回值。


AsyncTask 通过 new AsyncTask().execute() 来启动,其中 execute 的参数会被传递给 doInBackground 函数。AsyncTask可以通过 getStatus 来获取当前任务的执行状态,通过 cancel来取消。


(3) MainActivity 的实现


MainActivity 的代码如下,我把两种方式的代码都集成在里面了。


public class MainActivity extends Activity implements TaskStateListener,RunnableStateListener {
                                                                                                                                                                                                                                                 
    private TextView mProgressShow;
    private ProgressBar mProgressBar;
                                                                                                                                                                                                                                                   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
                                                                                                                                                                                                                                                        
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
                                                                                                                                                                                                                                                       
        mProgressShow = (TextView)findViewById(R.id.TextShow);
        mProgressBar  = (ProgressBar)findViewById(R.id.ProgressBar);
    }
                                                                                                                                                                                                                                                   
    public void onClickDownLoad(View v) {
                                                                                                                                                                                                                                                       
        //new DownloadTask(this).execute("blog.ticktick.51cto.com");
                                                                                                                                                                                                                                                    
        new Thread(new DownloadRunnable(this,"blog.ticktick.51cto.com")).start();
    }
                                                                                                                                                                                                                                                       
    @Override
    public void onTaskUpdate(int progress) { 
                                                                                                                                                                                                                                                 
        mProgressShow.setText(progress+"%");
        mProgressBar.setProgress(progress);
    }
                                                                                                                                                                                                                                                            
    @Override
    public void onTaskComplete(boolean isSuccess) {
                                                                                                                                                                                                                                                        
        if( isSuccess ) {
            Toast.makeText(this,"Download Complete",Toast.LENGTH_LONG).show();
        }
        else {
            Toast.makeText(this,"Download Failed",Toast.LENGTH_LONG).show();
        }
    }
                                                                                                                                                                                                                                                    
    @Override
    public void onRunnableUpdate(final int progress) {
                                                                                                                                                                                                                                                       
        this.runOnUiThread(new Runnable() {  
                                                                                                                                                                                                                                                     
            @Override
            public void run() {      
                mProgressShow.setText(progress+"%");
                mProgressBar.setProgress(progress);
            }
        });      
    }
                                                                                                                                                                                                                                                      
    @Override
    public void onRunnableComplete(final boolean isSuccess) {
                                                                                                                                                                                                                                                        
        this.runOnUiThread(new Runnable() {  
                                                                                                                                                                                                                                                           
            @Override
            public void run() {      
                                                                                                                                                                                                                                                             
                if( isSuccess ) {
                    Toast.makeText(MainActivity.this,"Download Complete",Toast.LENGTH_LONG).show();
                }
                else {
                    Toast.makeText(MainActivity.this,"Download Failed",Toast.LENGTH_LONG).show();
                }
            }
        });      
    }
}


这里注意,由线程类回调的onRunnableUpdateonRunnableComplete函数中,通过this.runOnUiThread的方式在更新UI,而由AsynTask回调的则不需要采用这种方式,因为Android不允许非UI线程改变UI元素,所以必须通过runOnUiThread的方式来更新,而AsynTask则是在内部通过handle收发消息的方式自动切换到了UI线程,所以可以直接更新UI。


关于Android开发中常用的两种耗时操作的处理方式就总结到这儿了,主要通过一个简单的示例程序示范了Runnable和AsynTask的使用方法,工程代码见文章后面的附件。有不清楚的地方,欢迎留言或者来信lujun.hust@gmail.com交流,或者关注我的新浪微博 @卢_俊 获取最新的文章和资讯。