android之handler的刨根问底

对于handler,不管处于哪个层次,这个都是必问的面试题,这也是过来人的一个总结。我换工作还是比较频繁的,曾辗转无锡、苏州、南京、上海,虽然我只工作不到四年,呵呵,信息量好像有点大,但其中问到最多的就是handler,首屈一指。现在,我们一点点去刨开它。
首先从表面的开始解剖,在离开第一家的时候,那时候经验非常不足,不到一年的的工作经验,对于handler,我所知道的就是有四个部分,分别是:
·handler:先进先出原则。
·messagequeue:消息队列,用来存放消息。
·looper:有且仅有一个,由它来管理此线程里的MessageQueue。
·线程:UI thread 通常就是main thread,而Android启动程序时会替它建立一个Message Queue。
那时候的我就知道这点了,而且还是从网上搜来的,对这些东西还是一知半解,对于一个新手,面试官不会问的太深,抱着这种态度去面试,居然真的遇到这个问题了,居然混过关了,哈哈。

随着时间的推移,渐渐对handler有了一些新的了解,用生活中一个例子说明,或许更有利于对handler的理解。
上午十点的时候,某公司的HR通过打电话通知五个应聘者下午四点之前来公司面试,有一个面试者就在附近,立即马上以380码的速度来到公司面试,一个叫做looper的工作人员把应聘者带到一间小会议室,填好资料后开始和面试官面对面交流。
其他四位应聘者,在两点左右的时候到了三个,HR让他们先填好表,按照先后顺序排队(messageQueue),looper首先把下午第一个先来的喊出来(通过handlerMessage(Message msg)取出message),面试官面试完后,looper喊来第二个,最后面试第三个,三人面试结束后,面试官便去忙自己的事情,looper在此处等待是否有其它面试者到来,过了十几分钟,最后一位面试者到来,looper再次把面试者带到小会议室,通过callback通知面试官前来面试。
通过上述例子,我们首先绘制一张图片,如下所示:
这里写图片描述
在很多书籍上面提到handler的时候,都会写到handler是主线程和子线程通信的使者,由此可见,handler是一个异步操作,通过上述例子可知,当HR打完电话后便会把电话挂了,而不是等面试者面试结束后再把电话挂了,或许此处的事例不是非常好,再说个更容易理解的例子,寄信的时候,我们只要把写好的信放进邮箱就可以回去了,而不是等到邮递员把信送到了我们再回去。
继续上述的例子,当下午三个面试者在相同时间段到来的时候,这时就需要给他们排成一个消息队列,按照先进先出的原则,looper对象不断循环遍历取出,一一处理,当处理结束后,不是直接销毁,而是在此等待,当再有面试者(message)到来的时候,looper再次去处理。
叙述到这里,有一个很必要的问题需要阐述一下,那就是Java标准线程模型和Android消息驱动机制之间的关系,Android在Java标准线程模型的基础上,提供了消息通信机制,用于多线程之间的通信,基于消息驱动机制的线程通信模型称为线程消息通信。在标准线程模型中,线程执行完毕便会退出,而Android扩展了线程退出机制,在启动线程时,首先在县城内部创建一个消息队列,然后让线程进入一个无限循环中,在这个无限循环中,会不断检查是否有消息,如果需要执行某个消息,便会向线程的消息队列中发送对应的该任务的消息,线程在无限循环的过程中检查到便会获取到该消息,进而执行该消息对应的处理流程,如果线程的消息队列中没有消息,线程便会进入等待状态,等待消息的到来。这样便可以通过消息控制线程的执行,因此也称为消息驱动机制。
下面再通过代码分析Java标准线程模型和Android消息驱动机制的差异,首先先看下Java标准线程模型的一个示例代码,如下是1.6中的示例代码:

class PrimeThread extends Thread {
         long minPrime;
         PrimeThread(long minPrime) {
             this.minPrime = minPrime;
         }

         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }

接下来,我们再看下Android23中的示例代码:

class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();

          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();
      }
  }

现在一目了然了,Android在Java标准线程模型的基础上增加了handler和looper,有一点需要强调的是,默认情况下android中新诞生的线程是没有开启消息循环的。主线程除外,主线程系统会自动为其创建Looper对象,开启消息循环。 Looper对象通过MessageQueue来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。

Looper.prepare();

          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();

对于这一串代码,虽然短小,但信息量还真不少,首先说下Looper.prepare(),因为是在子线程中直接new Handler(),这种情况下会报Java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare() 原因是非主线程中默认没有创建Looper对象,需要先调用Looper.prepare()启用Looper。Looper.prepare()进行了怎么样的准备?现在我们再去分析下源码,如下:

public final class Looper {
    private static final String TAG = "Looper";

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;
    final Thread mThread;

    private Printer mLogging;

    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    public static Looper myLooper() {
        return sThreadLocal.get();
    }
}

prepare方法会调用其重载方法创建一个Looper对象,并将其存入线程局部变量sThreadLocal中。sThreadLocal是ThreadLocal类型的变量,保证每个线程中都有该对象的独立副本。创建Looper对象时,会首先判断sThreadLocal线程局部变量中是否已经存在一个Looper对象,如果存在便会抛出运行时异常,因此在当前线程中多次调用prepare方法会导致线程异常退出,所以Looper对象是线程唯一的。如果要访问这个Looper对象,只需要调用myLooper方法。

那么另一个问题接踵而来,刚刚说了在子线程中操作不当会有问题,那么在主线程中会是什么样情况呢?Looper对象是否直接存在呢?我们先看下源码,源码是不会撒谎的,打开activity,你会找到这么一行代码,final Handler mHandler = new Handler();Handler和Looper的核心代码如下:

Handler类

public Handler() {
        this(null, false);
    }

    ..............省略.....................

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

Looper类

/**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

    /** Returns the application's main looper, which lives in the main thread of the application.
     */
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

Handler在主线程中定义,那么它是与主线程的Looper绑定。 mainHandler = new Handler() 等价于new Handler(Looper.myLooper());Looper.myLooper();获取当前进程的looper对象,类似的 Looper.getMainLooper() 用于获取主线程的Looper对象。 通常是通过Handler对象来与Looper进行交互的。Handler可看做是Looper的一个接口,用来向指定的Looper发送消息及定义处理方法。默认情况下Handler会与其被定义时所在线程的Looper绑定。
接着刚刚LooperThread类中的问题,Looper.loop(); 让Looper开始工作,从消息队列里取消息,处理消息。写在Looper.loop()之后的代码不会被执行,这个函数内部应该是一个循环,当调用mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。

结合以上代码可知,Looper线程需要继承Thread类,并实现run方法,这样便可以像启动标准线程模型那样启动Looper线程。Looper线程启动后进入run方法,通过调用Looper.prepare方法进入Looper线程准备阶段,创建Handler对象用于实现消息处理器,消息处理器负责发送和处理消息,最后通过调用Looper.loop方法进入线程循环阶段。

总结,Android消息驱动机制源于Java标准线程模型,Java线程模型在执行完任务后便退出,当再有消息进来的时候,便会重新创建,如果GC不及时回收,便会不断产生垃圾,Android在这基础上增加了消息队列、looper循环遍历处理,当执行完消息队列的任务时,边处于等待状态,等待消息到来。

最近在看《Android的设计与实现卷1》,对于handler中底层实现的部分还在学习中,等学习完后再去写一篇底层方面的博客,敬请期待,^_^

参考文献:《Android的设计与实现卷1》

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值