Android 中轻松使用线程


 当你第一次启动一个 Android 程序的时候,一个被称为 "main" 的线程就被自动创建了。它被称为主线程或者UI线程,它是非常重要的,因为负责分发事件给对应的widget,还包含画图的事件。

主线程贯穿用户和 Android widget 的交互的整个过程。例如,你触摸了屏幕上的按钮(Button),UI 线程派发(dispatch) 触摸(touch)事件给 widget,widget 设置为按下状态并向事件队列发送一个无效的请求。UI 线程把这个请求弹出栈并且通知 widget 去重画它自己。


单线程模型导致 Android 程序低效。因为每次单线程去执行长时间的操作如网络请求,数据库查询和drawing event(绘图事件),在这个过程中会阻塞程序的界面(UI)。当长时间的任务正在执行的时候,没有事件(Event)会被派发(dispatch),包括drawing event(绘图事件)。从用户的角度来看,该程序出现了终止。甚至更糟糕的是,如果UI程序被阻塞几秒后(大约5s)就会出现臭名昭著的ANR 对话框。

如果想看一下有多糟糕,你可以写一个简单的带有按钮的程序,按钮的点击事件执行 Thread.sleep(2000)  代码。该按钮将会保持 2 秒的按下状态然后恢复正常的状态。这样很容易让用户感到程序很慢。

现在你知道了一定要避免在主线程中执行长时间的操作,你可能会使用额外的线程(后台线程或者工作线程)去执行操作。让我们来看看点击从网络下载图片到ImageView里的例子:


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


首先,代码看上去很好的解决我们的问题,因为它不会阻塞UI线程,不幸的是,它违背了单一线程模型:Android UI工具箱(toolkit)不是一个线程安全的,并且它总是被放在主线程上操作。


这个 ImageView 被一个工作线程操作,这导致非常不可思议的问题。跟踪和修复这样一个bug很难并且也耗时。

Android提供了几种从其他线程访问主线程的方式。你可能已经很清楚如何使用他们,但是这里是齐全的列表:

 

  • 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();
}

不幸的是,这些类和方法导致我们的代码变得复杂和可读性差。当你实现复杂的操作来频繁的更新界面,使用这种方式变得更加糟糕。为了解决这个问题,Android1.5 提供了一个公共类叫做 AsyncTask,它简化了任务线程和主线程之间的通信。

在Android1.0 和 1.1 也可使用 AsyncTask 只不过它的名字为 UserTask。

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 实例必须在主线程创建并且只能被执行一次。完全理解和使用这个类,你可以阅读 AsyncTask 文档。这里快速的说一下 AsyncTask 是怎么工作的:

  1. 可以通过泛型指定它的类型:参数,进度值,任务的结果值。
  2. doInBackGround()方法自动在工作线程中只想能够。
  3. onPreExecute(),onPostExecute(),onProgressUpdate()方法都在UI线程中执行。
  4. doInBackground()方法返回的值被当作参数传递给onPostExecute()方法。
  5. 你能够在doInBackground()方法里任何时候调用publishProgress()方法在UI线程中去执行onProgressUpdate()方法。

除了官方文档,你可以阅读几个复杂的例子源代码如

  • Shelves(ShelvesActivity.java、AddBookActivity.java) 
  • Photostream(LoginActivity.java、PhotostreamActivity.java、ViewPhotoActivity.java)。

我强烈建议阅读 Shelves 的源代码,看它在配置改变(configuration changes)的时候是如何保存任务的 (persist task),当Activity 销毁的时候是怎样取消任务的。


不要管它是否使用 AsyncTask,总之要记住单线程模型的两个原则(rule):

  • 不要阻塞(block)UI线程;
  • 确保Android UI toolkit 只能在UI线程中被访问(access)。

AsyncTask使得做这些事情变得更简单。

如果你想学习更酷的技术,加入 Google I/O(http://www.google.com/intl/zh-CN/events/io/2011/)。

Android 团队的成员将在这里进行一些列深层次技术的会议 (http://www.google.com/intl/zh-CN/events/io/2010/sessions.html),并且回答你所有的问题。

翻译自: http://android-developers.blogspot.jp/2009/05/painless-threading.html


 

 

 

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页
实付 39.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值