基于移动客户端的软件特别强调实时性,Android程序更是如此,任何一个程序超过5s没有响应,都会被系统强制杀掉。而且Android也不允许在UI线程中进行任何网络操作,否则就会产生NetworkOnMainThreadException 异常。因此,凡是耗时的操作,都不应该直接出现在UI线程中。今天,我通过最简单直观地示例总结下Android开发中最常用的两种处理耗时操作的方法:一个是线程,另一个是异步任务。
首先,看看示例效果,点击Download后,进度条每1秒中增加1%,直到增加到100%。
我将分别用两种方式实现这个功能。
首先,给出 XML 的布局文件:
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Download"
android:onClick="onClickDownLoad"/>
android:id="@+id/TextShow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="0%"/>
android:id="@+id/ProgressBar"
android:layout_margin="10dp"
android:layout_width="match_parent"
android:layout_height="30dp"
style="@android:style/Widget.ProgressBar.Horizontal"/>
(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 {
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,有点像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();
}
}
});
}
}
这里注意,由线程类回调的onRunnableUpdate和onRunnableComplete函数中,通过this.runOnUiThread的方式在更新UI,而由AsynTask回调的则不需要采用这种方式,因为Android不允许非UI线程改变UI元素,所以必须通过runOnUiThread的方式来更新,而AsynTask则是在内部通过handle收发消息的方式自动切换到了UI线程,所以可以直接更新UI。
关于Android开发中常用的两种耗时操作的处理方式就总结到这儿了,主要通过一个简单的示例程序示范了Runnable和AsynTask的使用方法,工程代码见文章后面的附件。有不清楚的地方,欢迎留言或者来信lujun.hust@gmail.com交流,或者关注我的新浪微博 @卢_俊 获取最新的文章和资讯。