Android中的线程模型

这篇文章将讨论Android应用程序中使用的线程模型,并讨论如何确保应用程序最佳的UI呈现(通过创建工作者线程来处理耗时的操作,而不是在主线程里处理)。这篇文章还将阐述与运行在主线程中的UI组件交互的API以及创建托管的工作者线程的API。

UI线程

当应用程序启动后,系统创建了一个叫做“main”的线程。主线程,也叫UI线程,非常重要,因为它负责分发事件给构件,包括绘制事件。也是这个线程,在这里才能与Android UI工具包中的组件进行交互。

例如,当你触摸屏幕上的一个按钮时,UI线程会分发一个触摸事件给构件,然后,构件会设定自己为被按下的状态,并抛出一个显示无效的请求给事件队列。UI线程队列请求并通知构件绘制自己。

单线程模型会导致性能低下,除非你的程序很好地实现。特别是,当所有的操作都在单一的线程中进行,耗时的操作(如网络访问、数据查询)会阻塞UI。在耗时操作执行时,没有任何事件可以分发,包括绘制的事件。从用户的视觉来看,应用程序被挂起了。更糟糕的是,如果UI线程阻塞超过一定的时间(现在大约是5秒钟),系统会给用户呈现一个糟糕的“应用程序无响应”(ANR)对话框。

如果你想看这有多糟糕,你可以写一个简单的应用程序,在一个Button的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线程。遗憾的是,它违背了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。

为了解决这个问题,Android 1.5和它之后的平台提供了一个通用的类——AsyncTask,其简化了长时间运行任务的创建过程,而这些任务还能做到与UI进行交互。

在Android 1.0和1.1上,也有与AsyncTask类似的东西,叫做UserTask。它提供了相同的API,而你需要做的只是拷贝其中的代码。

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的文档来全面了解如何使用,但这里,只是简要地描述它以及它的工作过程:

· 你可以使用泛型来指定参数、进度值和最终结果的类型

· doInBackground() 方法自动在工作者线程中执行

· onPreExecute(),onPostExecute()和onProgressUpdate()在主线程中调用

· doInBackground()中返回的值会发送给onPostExecute()方法

· 你可以在doInBackground()中随时调用publishProgress()来执行onProgressUpdate()

· 你可以任何时候从任何线程中取消任务

除了官方的文档,你还可以参考几个复杂例子的源代码,如Shelves(ShelvesActivity.java和AddBookActivity.java)和Photostream(LoginActivity,PhotostreamActivity.java和ViewPhotoActivity.java)。我们强烈地建议你阅读Shelves的源代码,来了解配置变更时任务的保存以及Activity销毁时如何正确地取消任务。

不管你是否使用AsyncTask,在单线程模型中始终要记住两条法则:

1. 不要阻塞UI线程

2. 确保只在UI线程中访问Android UI工具包

更多相关文章

  1. android中的HandlerThread类的学习
  2. Android线程优先级设置方法
  3. Android应用项目绑定appcom_v7打包时,出现错误:"XXX"isnottransla
  4. Android(安卓)应用程序之间数据共享—ContentProvider 保时被访
  5. Android(安卓)之 Looper、MessageQueue、Handler 与消息循环
  6. android操作sim卡联系人信息
  7. Android(安卓)之 Handler总结
  8. Android开发学习笔记之一
  9. Android(安卓)onTouchEvent, onClick及onLongClick的调用机制

随机推荐

  1. Android SDK Manager 更新方法
  2. 替换Android自带apk
  3. android电源管理简要
  4. Android日期空间(DatePickerDialog)中的年
  5. Android 4.0 横竖屏切换注意事项
  6. Android Studio 1.0 官网下载链接
  7. Android拍照,上传,预览综合【修改】
  8. Android Maven 采用第三方jar包,程序运行
  9. Android ExpandableListView开发简介
  10. Ubuntu下android studio 编译报错A probl