线程
Android 的单线程模式必须遵守两条规则:
- 不要阻塞 UI 线程(主线程)
- 不要在 UI 线程之外访问 Android UI 工具包(来自
android.widget
和android.view
软件包的组件)
工作线程
根据上述单线程模式,要保证应用 UI 的响应能力,关键是不能阻塞 UI 线程。如果执行的操作不能很快完成,则应确保它们在单独的线程(“后台”或“工作”线程)中运行。
例如,以下代码演示了一个点击侦听器从单独的线程下载图像并将其显示在 ImageView
中:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork("http://example.com/image.png");
mImageView.setImageBitmap(b);
}
}).start();
}
乍看起来,这段代码似乎运行良好,因为它创建了一个新线程来处理网络操作。 但是,它违反了单线程模式的第二条规则:不要在 UI 线程之外访问 Android UI 工具包—此示例从工作线程(而不是 UI 线程)修改了 ImageView
。这可能导致出现不明确、不可预见的行为,但要跟踪此行为困难而又费时。
为解决此问题,Android 提供了几种途径来从其他线程访问 UI 线程。以下列出了几种有用的方法:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
例如,您可以通过使用 View.post(Runnable)
方法修复上述代码:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
}
}).start();
}
现在,上述实现属于线程安全型:在单独的线程中完成网络操作,而在 UI 线程中操纵 ImageView
。
但是,随着操作日趋复杂,这类代码也会变得复杂且难以维护。 要通过工作线程处理更复杂的交互,可以考虑在工作线程中使用 Handler
处理来自 UI 线程的消息。当然,最好的解决方案或许是扩展 AsyncTask
类,此类简化了与 UI 进行交互所需执行的工作线程任务。
使用 AsyncTask
AsyncTask
允许对用户界面执行异步操作。它会先阻塞工作线程中的操作,然后在 UI 线程中发布结果,而无需您亲自处理线程和/或处理程序。
要使用它,必须创建 AsyncTask
子类并实现 doInBackground()
回调方法,该方法将在后台线程池中运行。要更新 UI,必须实现 onPostExecute()
以传递 doInBackground()
返回的结果并在 UI 线程中运行,这样,您即可安全更新 UI。稍后,您可以通过从 UI 线程调用 execute()
来运行任务。
例如,您可以通过以下方式使用 AsyncTask
来实现上述示例:
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
/** The system calls this to perform work in a worker thread and
* delivers it the parameters given to AsyncTask.execute() */
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
/** The system calls this to perform work in the UI thread and delivers
* the result from doInBackground() */
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
现在 UI 是安全的,代码也得到简化,因为任务分解成了两部分:一部分应在工作线程内完成,另一部分应在 UI 线程内完成。
下面简要概述了 AsyncTask 的工作方法,但要全面了解如何使用此类,您应阅读 AsyncTask
参考文档:
- 可以使用泛型指定参数类型、进度值和任务最终值
- 方法
doInBackground()
会在工作线程上自动执行 -
onPreExecute()
、onPostExecute()
和onProgressUpdate()
均在 UI 线程中调用 -
doInBackground()
返回的值将发送到onPostExecute()
- 您可以随时在
doInBackground()
中调用publishProgress()
,以在 UI 线程中执行onProgressUpdate()
- 您可以随时取消任何线程中的任务
注意:使用工作线程时可能会遇到另一个问题,即:运行时配置变更(例如,用户更改了屏幕方向)导致 Activity 意外重启,这可能会销毁工作线程。
要了解如何在这种重启情况下坚持执行任务,以及如何在 Activity 被销毁时正确地取消任务,请参阅书架示例应用的源代码。