Android UI Operation in Thread - Painless Threading (无痛苦

本文讨论Android应用程序的线程模型以及应用程序应该如何创建工作线程而不是使用主线程来处理长期运行的操作, 以得到好的UI性能. 本文还解释了你可以用来和Android UI组件交互以及创建线程的 API.

The UI thread UI 线程
当一个应用程序启动时, 系统创建一个叫做"main"的线程. 这个主线程也叫UI线程, 它非常重要, 因为它负责将事件分发给合适的widget来处理, 包括绘图事件等. 它也是你的应用程序和运行中的Android UI组件进行交互的线程. 例如, 如果你按下屏幕上的按钮, UI线程会将这个触摸事件分配给该widget, 而该widget将设置它的按下状态并向事件队列一个重绘请求. UI线程从队列中取出请求并通知widget来重画自身.

单线程模型可能会导致性能很差, 除非你的程序被正确的实现. 特别的, 如果每件事都在同一个线程中进行, 那么耗时长的操作(例如网络访问和数据库查询)将阻塞UI线程, 从而使整个UI无法交互. 在耗时操作进行时, 包括绘画事件在内的所有事件都无法进行分配. 在用户看来, 该应用程序停止了响应. 更糟糕的是, 如果UI线程被阻塞超过一段时间(当前的限制是5秒钟), 那么用户将看到"应用程序无响应"的对话框.

如果你希望看看这是多么的糟糕, 那么你可以写一个程序, 创建一个按钮, 它的OnClickListener中调用Thread.sleep(2000). 那么该按钮在按下去以后要等2秒才会谈起, 这时候用户是非常容易感觉到程序很慢.

总之, 应用程序UI的响应速度是很重要的, 所以应该避免阻塞UI线程. 如果你需要进行耗时操作, 那么你应该在其他的线程中进行(后台或者工作线程).

下面的例子演示了如何实现一个单击监听器来下载一个图片并将它显示在 ImageView中:

public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork();
mImageView.setImageBitmap(b);
}
}).start();
}

一开始, 这段代码看起来是个不错的解决方案, 因为它不会阻塞线程. 不幸的是, 它破坏了UI的单线程模型: Android UI工具不是线程安全的, 必须在UI线程中操作. 在上面的代码段中, ImageView在一个工作线程中操作, 这回导致很奇怪的问题. 追踪和解决这样的bug是非常困难的.

Android提供了一些从其它线程中访问UI线程的方法. 你可能已经对它们之中的某些比较熟悉了:

Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
Handler
你可以使用上面的任意一种类和方法来改正之前的错误:

public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap b = loadImageFromNetwork();
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(b);
}
});
}
}).start();
}

不幸的是, 这些类和方法也会使你的代码更加复杂. 如果你需要实现频繁的UI更新的话就更糟糕了.

为解决这个问题, Android1.5以上的平台提供了一个叫做AsyncTask的工具类.

Android1.0和1.1上也有和它等效的类叫做UserTask, 它提供了完全一样的接口.

AsyncTask的作用是帮你来管理线程. 我们将之前的例子用AsyncTask来重写:

public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask<string, void,="" bitmap=""> {
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}

protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}

AsyncTask必须使用创建子类的方式来使用. AsyncTask实例只能在UI线程中创建,并且只能运行一次. AsyncTask documentation 中有它的详细介绍, 这里简单介绍一下:

你可以使用泛型来指定任务的参数的类型, 过程值和最终值. (不知道理解的对不对 You can specify the type, using generics, of the parameters, the progress values and the final value of the task.)
 doInBackground() 方法自动运行在一个工作线程中.
onPreExecute(), onPostExecute() 和onProgressUpdate() 都在UI线程中工作.
 doInBackground() 的返回值被传给 onPostExecute()

你可以在任何时间在doInBackground()中调用publishProgress() 来在 UI 线程中运行 onProgressUpdate() .
你可以在任何线程中取消该工作.
无论你是否使用 AsyncTask, 记住以下关于单线程模型的两条规则:

不要阻塞UI线程, 以及
确定你只在UI线程中访问Android UI 工具.
AsyncTask 只是让上面两件事情变得更加方便.

相关推荐