Android要点:理解Loopers 和 Handlers

Jul 13 2014
技术

原文:http://mindtherobot.com/blog/159/android-guts-intro-to-loopers-and-handlers/

我喜欢Android的原因之一就是它有很多非常有用的小工具,其中很多还是与平台无关的。今天,我想介绍两个非常nice 的类——LooperHandler,他们用于Android UI,并且对我们开发者也可用,我们就可以用它们来做很多非常棒的事情了。

那么,我们能用LooperHandler做什么呢?首先,这两个类实现了一种通用的并发模型,我把它叫做:Pipeline 线程。它是这样工作的:

  1. Pipeline 线程持有一个任务队列(a queue of tasks),这些任务就是一些可以执行的工作单元(units of work)
  2. 其他线程可以自由的将任务加到Pipeline线程的任务队列中去
  3. Pipeline线程就按次序一个一个执行任务,如果任务队列中没有任务了,它就会自动阻塞直到有任务到来
  4. 有些时候,任务可以叫做消息(messages)或者其他名字

这个架构有一些很好的特征,并且这个架构已经被用于其他平台的框架或应用中了。

这篇文章中,我们会新建一个简单的app来模拟一个下载任务队列,同时在UI界面显示任务的状态,我们就直接使用Looper和Handler了。(源代码可以去原文中下载)

但是,我们在开始前先来看一下Pipeline线程,以及Looper和Handler的原理。

###Pipeline线程的用途
Pipeline线程的用途可以在几乎所有的UI框架中找到,包括Swing,SWT,Adobe Flex,以及Android Activity。Pipeline线程模式经常被用于处理UI事件(如按钮的点击事件,手指移动,屏幕方向改变,屏幕重新绘制等等),它可以让你在改变一个按钮文字的时候同时不用担心用户会点击这个按钮(译者注:这两个事件不会并发执行,处理UI事件是单线程的)。

另一方面,这就迫使你在UI线程中执行较快的操作——任何一个开发都知道如果你在一个按钮的OnClick方法中去下载一个文件会发生什么事情。

其他Pipeline线程模式的通用操作有:

  1. 执行一个到远程服务的request(通常你希望它们一个一个按序执行)
  2. 上传一个图片到http服务器
  3. 缩放以及剪裁图片
  4. 下载操作

通常,使用一个Pipeline线程而不是为每个后台操作都新建一个线程的好处就是,这样你可以控制每个后台任务的加载以及顺序(优先级)。此外,你也可以开启多个Pipeline线程,把他们当做一个线程池,这样你就可以一次同时执行多个操作。

Looping和Handling

Looper类可以将一个线程转换成Pipeline线程,而Handler提供了一种机制,你可以通过它将任务添加到Pipeline线程中。Looper之所以这么命名是因为它实现了循环——取一个task执行,然后再取下一个task执行,如此循环;Handler如此命名是因为他们无法想出一个更好的名字了~(译者注:囧…)

下面就是你需要添加到Thread类的run方法中的代码来创建一个你自己的Pipeline线程,然后可以将这个Pipeline线程添加到Handler对象中:

@Override
public void run() {
  try {
    // preparing a looper on current thread     
    // the current thread is being detected implicitly
    Looper.prepare();

    // now, the handler will automatically bind to the
    // Looper that is attached to the current thread
    // You don't need to specify the Looper explicitly
    handler = new Handler();

    // After the following line the thread will start
    // running the message loop and will not normally
    // exit the loop unless a problem happens or you
    // quit() the looper (see below)
    Looper.loop();
  } catch (Throwable t) {
    Log.e(TAG, "halted due to an error", t);
  } 
}

然后,你只要将这个handler对象传到其他任何线程中去,它有一个线程安全的接口,包括了很多操作,但是最主要的操作就是postMessage()以及相关的方法了。

Notes:Handler类包含了很多非常棒的方法,特别是与消息传递有关的,本文不会写与此相关的内容。

举个栗子:想象一下,一个线程A持有了handler对象的引用,此handler是在Pipeline线程中创建的,下面代码就可以让这个线程A在Pipeline线程中执行操作了:

handler.post(new Runnable() {
  @Override
  public void run() {       
    // this will be done in the Pipeline Thread       
  }
});

在下载的栗子中,我们就会在用户点击按钮(在UI线程中处理)的时候,用这个模板来创建一个下载任务(在下载Pipeline线程中)。我们还用另外一种方式用它——当下载线程下载完成以后会通知Activity,因此在创建下载线程的时候,我们会将Activity中的UI线程handler对象传给它。

此外,UI线程拥有一个Looper(译者注:可以通过Looper.getMainLooper()方法获取,判断一个线程是否为主线程可以使用Looper.getLooper() == Looper.getMainLooper()来判断)。所以,你可以在Activity的onCreate()方法中直接新建一个handler对象:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // Create the Handler. It will implicitly bind to the Looper
    // that is internally created for this thread (since it is the UI thread)
    handler = new Handler();
}

结论

Looper和Handler可以让你很方便的做很多事情,然而他们与并发相关,这就会变得很复杂。

译者注:

Looper和Handler是非UI线程更新界面的重要方式,在非UI线程中通过下面代码:

new Handler(Looper.myLooper()).post(new Runnable() {
    @Override
    public void run() {
        //在UI线程中处理 
    }
});

就可以轻松的将一些工作放到UI线程中处理,比如进度条刷新等等。

附件:完整源代码