例如,当用户触摸屏幕上的按钮时,应用的 UI 线程会将触摸事件分派给小部件,而小部件反过来又设置其按下状态,并将失效请求发布到事件队列中。 UI 线程从队列中取消该请求并通知小部件应该重绘自身

如果 UI 线程需要处理所有任务,则执行耗时很长的操作将会阻塞整个 UI。 一旦线程被阻塞,将无法分派任何事件,包括绘图事件。如果 UI 线程被阻塞超过特定时间(目前大约是 5 秒钟),用户就会看到一个显示“应用无响应”(ANR) 文本的对话框

此外,Android UI 工具包并非线程安全工具包。因此不能通过工作线程操纵 UI,而只能通过 UI 线程操纵用户界面。 因此,Android 的单线程模式必须遵守两条规则:

  • 不要阻塞 UI 线程
  • 不要在 UI 线程之外访问 Android UI 工具包

四、工作线程

根据上述单线程模式,要保证应用 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 工具包。在工作线程中修改了 ImageView, 这将使应用运行时出现错误

为解决此问题,Android 提供了几种途径来从其他线程访问 UI 线程:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable, long)

可以将以上代码修改为

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

此外,也可以使用AsyncTask来完成任务。AsyncTask 允许对用户界面执行异步操作。它会先阻塞工作线程中的操作,然后在 UI 线程中发布结果

要使用 AsyncTask ,必须创建 AsyncTask 的子类并实现 doInBackground() 回调方法,该方法将在后台线程池中运行。 要更新 UI,应该实现 onPostExecute() 方法以传递 doInBackground() 返回的结果并在 UI 线程中运行,以便安全地更新 UI

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 工作方法如下所示:

  • 使用泛型指定参数类型、进度值和任务最终值
  • doInBackground() 方法会在工作线程上自动执行
  • onPreExecute()、onPostExecute() 和 onProgressUpdate() 均在 UI 线程中调用
  • doInBackground() 返回的值将发送到 onPostExecute()
  • 可以随时在 doInBackground() 中调用publishProgress()方法,以便在 UI 线程中执行 onProgressUpdate()
  • 可以随时取消任何线程中的任务

这里提供本系列文章所有的 IPC 示例代码:IPCSamples

作者:叶志陈