星期三, 十一月 28, 2012

Android,不再为线程烦恼

不再为线程烦恼


当你第一次开始一个Android应用的时候,有一个名字为"main"的线程就自动被系统建立了.这个main线程也被叫做 UI 线程,也就是用户界面线程,UI线程是非常重要的,它负责将事件也包括绘制(Drawing)事件分派到恰当的widgets;它也是你和Android Widgets之间互动的线程.
例如,当你触摸屏幕上的按钮,UI线程就将触摸事件分派到该widget,然后该widget设置状态为按下状态并发送一个invalidate请求到事件队列内.
UI线程出队该请求并通知widget来重绘自己.

这种单一线程模式将造成可怜的性能问题,因此在Android应用中一般是不考虑的。当所有事情都在单一线程中发生的时候,将造成线程长时间操作,比如网络访问或数据库查询,会造成这个
线程阻塞所有的用户接口.当长时间操作进行时,没有事件可以被分派,包括绘制(Drawing)事件.
从用户的角度来看,应用明显挂住了.甚至更糟,如果UI被阻塞了超过数秒(大约5秒)用户将得到声名狼藉的 ANR-程序没有响应.application not responding" 对话框。

如果你想试试这看起来有多糟糕,你可以写一个简单的应用一个按钮的onClickListener方法调用Thread.sleep(2000).
这个按钮将保持按下的状态大约2秒钟然后才返回到正常状态。当按钮按下时,用户轻易发现应用很慢.

现在你知道在UI线程内应该避免冗长的操作了,必须正确的使用额外的线程(后台工人线程worker thread)来正确的执行这些操作.
还是用一个例子:点击然后从网络上下载一个图像并显示在ImageView里.

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

初看起来,这段代码似乎给出了一个不阻塞UI线程的一个非常好的解决方法.但很不幸,它又违背了单一线程模式:
Android UI 工具集不是线程安全(thread-safe)的,必须一定要在UI线程里来操作.
这段代码内,ImageView被工人线程操作,这会造成一些奇怪的问题,并且让跟踪和修正错误变得困难和费时。

Android 提供了几种方法从其他线程里访问UI线程.罗列以下组件及方法:

这些方法或者类都能正确的使用我们先前的例子:

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 通讯的问题.
AsyncTask在Android 1.0和1.1也存在不过叫做UserTask,提供了一样的API你可以copy你的应用的源带码使用.

AsyncTask的目标是替你来管理thread.我们先前的例子可以很容易重写成AsyncTask的例子:

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

private class DownloadImageTask extends AsyncTask {
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}

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


如你所见,AsyncTask必须用做它的子类.最重要的是要记住AsyncTask实例必须在UI线程内建立并只能一次执行一次
你可以浏览 AsyncTask documentation 来完全理解它是如何工作的,这里给出一个快速工作原理浏览:


除了官方文档,你也可以看到几个负载的例子的源代码比如(ShelvesActivity.java AddBookActivity.java) 和 Photostream (LoginActivity.java, PhotostreamActivity.java和 ViewPhotoActivity.java). 我高度推荐读读Shelves 源代码来看看如何永久化任务访问配置改变并如何在Activity被放弃的时候放弃属性.

无论是否你要使用AsyncTask,永远记住这种线程模型的2个规则:不要阻塞UI线程并且确定Android UI 工具箱只能在UI线程内访问。
AsyncTask 是这两个要求很容易实现。

如果你想学习更多很Cool的技术,来加入我们 Google I/O. Android 团队成员在这里会给你 series of in-depth technical sessions 并回答你的问题.