Android的程序启动时,会同时启动一条主线程,又称为UI线程,主要负责UI相关的操作,包括:按键事件、触屏事件以及屏幕绘图事件等等。UI线程应该尽量避免耗时操作,否则将出现著名的ANR异常(Application Not Reponse)。
但是,有时候需要实现UI的动态更新,而更新操作可能又是耗时的。此时,耗时操作则必须放在新线程里进行。下面以一个简单例子,讨论一下UI动态更新的方法。
一、Handler + Thread
直接上代码
package com.dale.uiupdate;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ImageView;
public class MainActivity extends Activity {
ImageView mIv;
Handler mHandler1 = new Handler() {
@Override
public void handleMessage(Message msg) {
mIv.getDrawable().setLevel(msg.what);
}
};
class Thread1 extends Thread {
@Override
public void run() {
try {
for(int i=0; i!=10; i++) {
mHandler1.sendEmptyMessage(i%3);
sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mIv = (ImageView) findViewById(R.id.iv);
//通过新开线程发送消息给Handler,由Handler来更新当前UI状态
new Thread1().start();
}
}
界面布局时分简单,仅一个ImageView
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.dale.uiupdate.MainActivity" >
<ImageView
android:id="@+id/iv"
android:layout_width="80dp"
android:layout_height="200dp"
android:src="@drawable/img_view" />
</RelativeLayout>
这个ImageView加载的是一个LevelListDrawable,img_view.xml
<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:drawable="@drawable/red"
android:maxLevel="0"/>
<item
android:drawable="@drawable/green"
android:maxLevel="1"/>
<item
android:drawable="@drawable/blue"
android:maxLevel="2"/>
</level-list>
用到的资源为ColorDrawable,drawables.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<drawable name="red">#f00</drawable>
<drawable name="green">#0f0</drawable>
<drawable name="blue">#00f</drawable>
</resources>
程序启动时,通过新建独立线程(理解为处理耗时操作的线程),并不断发送UI更新消息,由Handler来处理,根据参数不断改变LevelListDrawable的层级,从而实现UI动态更新。
二、post + Thread
package com.dale.uiupdate;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ImageView;
public class MainActivity extends Activity {
ImageView mIv;
int mnCur;
Runnable run = new Runnable() {
@Override
public void run() {
mIv.getDrawable().setLevel(mnCur%3);
}
};
class Thread2 extends Thread {
@Override
public void run() {
for(int i=0; i!=10; i++) {
mnCur = i;
mIv.post(run);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mIv = (ImageView) findViewById(R.id.iv);
//通过新开线程来更改当前level值mnCur,经由mIv调用post来更改UI状态
new Thread2().start();
}
}
同样,此方法创建了一个新线程来处理耗时操作,但是更新UI,则利用了View的post方法。官的说明如下:
public boolean post(Runnable action)
/**
* Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.
*
* @param action The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*
*/
即,将一个Runnable对象添加到消息队列,此Runnable将会运行在UI线程。
耗时操作由新建线程实现,处理结果则由post来通知到UI线程,成功实现UI动态更新。
三、AsyncTask
通过异步任务类AsyncTask实现UI更新,代码如下
package com.dale.uiupdate;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.ImageView;
public class MainActivity extends Activity {
ImageView mIv;
class AsyncTaskHandler extends AsyncTask<ImageView, Integer, Boolean> {
ImageView param;
@Override
protected void onProgressUpdate(Integer... values) {
param.getDrawable().setLevel(values[0]);
}
@Override
protected void onPostExecute(Boolean result) {
}
@Override
protected Boolean doInBackground(ImageView... params) {
param = params[0];
int i=0;
while(i <= 10) {
try {
Thread.sleep(1000);
int cur = param.getDrawable().getLevel();
publishProgress((cur+1) % 3);
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
}
return true;
}
}
AsyncTaskHandler mAsyncTaskHandler = new AsyncTaskHandler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mIv = (ImageView) findViewById(R.id.iv);
//通过实现AsyncTask来处理UI更新
mAsyncTaskHandler.execute(mIv);
}
}
AsyncTask<Params, Progress, Result>是一个抽象类,其中的三个泛型类型:
-
Params: 启动任务执行的输入参数类型
-
Progress: 后台任务完成的进度值类型
-
Result: 后台执行任务完成后的返回结果类型
代码实现了的三个方法:
-
doInBackground(Params... params):后台线程将要完成的任务,调用publishProgress来更新任务运行进度
-
onProgressUpdate(Progress... values):在doInBackground中调用了publishProgress将会触发此方法,更新处理进度
-
onPostExecute(Result result):doInBackground执行完后,系统自动调用此方法,并将doInBackground的返回值传入此方法
实现了AsyncTask抽象类并重写需要的方法后,即可在UI线程中调用execute方法,开启后台耗时任务。通常AsyncTask适用于“秒级耗时任务”,即在几十秒以内为宜,过长的耗时任务,官方建议采用其他方案。
四、结语
第一种和第二种方案,归根结底,都是利用新建线程处理耗时任务,然后将耗时任务的处理结果通知到UI线程(一个通过UI线程的Handler,一个通过UI组件)。而AsyncTask具体实现原理,我未作深入研究,但从原理来讲,应该也是类似的。总之一句话:UI线程不能执行耗时操作,否则出现响应异常问题。三种方案对比来看,AsyncTask不需要借助线程和Handler,实现起来较简单,推荐使用。