【Android译文】Painless Thread

原文:http://developer.android.com/resources/articles/painless-threading.html

安全多线程

本文将要讨论Android应用程序所使用的线程模型以及Android应用程序是怎样通过产生工作线程来完成那些耗时的工作,而不是在主线程里处理这些工作来保证UI的性能的。同时本文还会对 应用程序与主线程中的Android UI toolkit组件交互以及产生托管工作线程的API进行解释。

Android UI线程

  当一个应用程序运行时,Android系统自动的为该应用程序创建一个叫做“Main”的主线程。“Main”线程,也叫UI线程,是一个十分重要的线程,他负责把包括UI绘制在内的所有事件分发到适当的Widget(UI组件)上面。同时,它还是你的应用程序与Android UI toolkit中运行的组件进行交互的线程。

  例如,我们在屏幕上对某一个按钮进行了触摸,主(UI)线程就会把这个事件分发给Button(button是一个widget),此时该按钮就会变为被按下的状态并且向事件队列中发送一个重绘请求。主线程从队列中取出该请求然后告诉按钮重绘自己。(这样按钮就表现为被按下的状态了)

  除非你的应用正确的实现了,否则这样的单线程模式可能使应用程序产生很坏的表现。特别是,如果把所有的事件都放在主线程中来处理,需要长时间来处理的事件比如网络访问或者数据库查询,会阻塞整个UI。此时,在那些耗时的操作结束之前UI都不会分发任何事件,包括绘制事件。从用户的角度来看,应用程序就是挂掉了,不再响应了。更坏的是,如果应用程序UI线程被阻塞超过几秒钟(现在大概是5秒),用户就会看到一个让人不高兴的“应用程序没有响应”(ANR)对话框。

  如果你想看看这样到底有多坏,写一个简单的按钮,并在其单击事件中添加Thread.sleep(2000)这段代码。此时按下按钮后2秒钟内按钮都会保持按下的状态。当发生这样的事情时,用户很容易认为这个应用程序是一个十分慢的应用。

总而言之,保证你的UI不被阻塞对于应用程序的响应来说是十分重要 的。如果有耗时的操作,你应该把这些操作放在另外的线程中进行处理(后台线程或者工作线程)。

下面是一个通过点击下载图片并显示到ImageView中的例子:

public void onClick(View v) {

new Thread(new Runnable() {

public void run() {

Bitmap b
= loadImageFromNetwork();

mImageView.setImageBitmap(b);

}

}).start();

}

乍一看,上面的代码似乎没有什么问题,因为他并没有阻塞UI线程。但不幸的是,上面的代码违背了UI线程的单线程模式:Android UI toolkit不是线程安全的,所以对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的操作中这样的操作将会更加的复杂和难以理解。

为了解决这样的问题,Android在1.5以及以后的版本中提供了一个辅助类:AsyncTask。这个类简化了创建需要与UI长时间交互的线程的过程。

在Android1.0和1.1中也有一个与AsyncTask相同的类,UserTask,它提供了与AsyncTask相同的API。

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。同时,AsyncTask实例只能在UI线程中创建且只能执行一次。你可以通过阅读AsyncTask documentation 来完全的了解AsyncTask类,下面是一个关于他怎样工作的概述。

l  你可以定义AsyncTask的参数类型和最终的返回值类型。

l  方法doInBackground()会自动的在工作线程中执行。

l  onPreExecute(),onPostExecute()以及onProgressUpdate()都是在主线程中调用的。

l  doInBackground()的返回值被发送到onPostExecute()中。

l  你可以在doInBackground()的任何时间调用publishProgress()来在主线程执行onProgressUpdate().

l  你可以在任何时间,通过任何线程结束这个任务。

做为官方文件的补充,你可以在Shelves (ShelvesActivity.java and AddBookActivity.java) 和Photostream(LoginActivity.javaPhotostreamActivity.java and ViewPhotoActivity.java)的源代码中阅读几个复杂的例子。我们强烈推荐您阅读 Shelves 的源码来了解怎样在配置改变的情况下保证任务执行以及怎样在Activity被销毁时正确的取消这些任务。

无论您是否使用 AsyncTask,,都要记住单线程模型的两个规则:

  1. 不要阻塞UI线程
  2. 保证只在UI线程中访问Android UI toolkit.

AsyncTask类只是把上面的两件事情变得简单了。

posted @ 2011-04-12 11:46  Cat_Lee  阅读(522)  评论(0编辑  收藏  举报