Android Handler 梳理

只要是做过Android开发的,想必对Handler都不陌生。在我刚工作的时候就去看过Handler的源码,但当时局限于对多线程的理解并没有那么深刻,所以对Handler的理解也就仅仅停留在表面。现在有时间重看Handler源码,猛然发现:我擦,Handler这么简单,竟然好多面试都去问,好多人都是马马虎虎,一知半解。所以我想在这篇博客里,把Handler彻底讲透了,以后妈妈再也不用担心我对Handler的理解了。

其实很多人对Handler的不理解,关键还在于对线程的不理解,所以我决定先简单说下线程的相关的知识。我在这也不能跟你胡说,在Java里一切都是对象,我们先看下Java源码里线程类的注释:

/**
 * A {@code Thread} is a concurrent unit of execution. It has its own call stack
 * for methods being invoked, their arguments and local variables. Each application
 * has at least one thread running when it is started, the main thread, in the main
 * {@link ThreadGroup}. The runtime keeps its own threads in the system thread
 * group.
 *
 * <p>There are two ways to execute code in a new thread.
 * You can either subclass {@code Thread} and overriding its {@link #run()} method,
 * or construct a new {@code Thread} and pass a {@link Runnable} to the constructor.
 * In either case, the {@link #start()} method must be called to actually execute
 * the new {@code Thread}.
 *
 * <p>Each {@code Thread} has an integer priority that affect how the thread is
 * scheduled by the OS. A new thread inherits the priority of its parent.
 * A thread's priority can be set using the {@link #setPriority(int)} method.
 */

我在这也不逐行去翻译了,英语能看懂的就看看,按我的理解就是:线程就是一个执行序,有自己的执行栈。完了,就这么多。我知道这么说对于初学者者来说,肯定会在下面骂了,妈蛋,什么是执行序啊?先别急,把刀放下,咱慢慢说。我们写代码时,这个方法,那个对象,一堆乱七八糟的东西,要执行出一个确定的结果,肯定是要一个确定的顺序。包括多线程,只要逻辑上有先后顺序的信息,也一定要保证按确定的顺序来执行。

现在来想一下我们平时听了八百遍的多线程实现方式,实现 Runnable 接口,或者 new Thread() ,一个新的线程,我们不要忘了去调它的 start() 方法。这时才真正的启动了一个线程,去执行我们写在 run() 方法里的代码。其实从Android APP开发者的角度来讲,Thread的 run()就相当于Java框架给我们的一个执行代码的入口,让CPU来执行我们的代码(这么说当然是扯淡,但是你仅从APP开发者敲代码的角度去看,把编译,链接,虚拟机,操作系统全看成透明的,也就是这么个理)。

我们只要写好自己的代码,代码放进去 run() 的肚子里,然后按下开关(执行 start() 方法),他就一直按我们写好的顺序调下去,直到所有代码执行结束。必须明白的一点是线程就是一个执行序,在Java里,他是一个类,我们要执行的代码要放到他的 run() 的肚子里面。然后调 start() 告诉 Java虚拟机,给我开一线程的执行环境,开启后来执行我写在 run() 里的代码。

有了这个认知以后我们再来看Thread的源码:

public class Thread implements Runnable

这里看到的是Thread实现了Runnable接口,接着看下Runnable接口里面都定了哪些抽象方法:

public interface Runnable {

    /**
     * Starts executing the active part of the class' code. This method is
     * called when a thread is started that has been created with a class which
     * implements {@code Runnable}.
     */
    public void run();
}

整个Runnable接口就这么可怜的一个 run() 方法。继续看Thread怎么实现的 run() 方法。

Runnable target;
public void run() {
    if (target != null) {
        target.run();
    }
}

这里可以很清楚的看出来,无论是我们继承Runnable接口,还是直接new Thread实际上是没有区别的。都是最终要把需要执行的代码放到 Thread run() 的肚子里。然后调 start() 方法

public synchronized void start() {
    checkNotStarted();

    hasBeenStarted = true;

    nativeCreate(this, stackSize, daemon);
}

start()这里就很显了 nativeCreate() 看名字就知道是调用C或C++去创建一个线程了,而创建线程以后呢,自然去调 run() 方法肚子里我们写的代码来执行了。

这时我们已经理解了我们Java层线程的意义。至于什么去stop线程啦,设置线程优先级,让线程去sleep啦,和start()基本一样的,是交给Java框架(这里框架这个词可能不太好理解,容易误解为J2EE的框架。其实这样说是相当于把JDK当作一种框架来看,就像Android里用的FrameWork框架。)来处理的。不是我们调了这个方法就执行的,而是我们只能通过框架给我们写好的,暴露出来的接口,去跟框架交互,至于它背后是同步,异步,或者等两秒,或者怎样搞,这些只是线程具体实现上的事,跟我们理解这个线程是一个执行序没有一毛钱的关系。如果想对线程有更深入的了解,理解线程特性,可以去看一些JVM的书(比如,深入理解Java虚拟机),里面有具体的线程特性。

到了这里发现简单说下线程,似乎说了好多,并没有收住,还将继续说下去,其实还是想尽力说的明白些,因为从个人的经验来看,对线程的理解直接决定了对Handler的理解,懂线程的根本就不会不懂Handler,懂Handler不懂线程的,绕了几圈还是会晕。而且,我以前在学习Android的时候也被好多不负责任的博客折磨的死去活来。所以我最讨厌那种上来就甩你一脸定义,代码,专有名词,乱七八糟的东西,老子懂这些的话还看你在这逼逼?

扯远了,继续说线程,刚刚说线程就是执行序,那么主线程呢?(在Android里一般主线程,UI线程,main线程都是在说一个玩意:主线程)。主线程既然是线程,它也是一个执行序,它也是由Java框架创建一个线程,然后执行写在它的 run() 方法肚子里的代码。现在要注意了,重点来了:

当我们点击桌面图标,桌面实际也是一个APP,上面放几个View,这些View的Image资源都是每个APP的图标,点击后就去执行的代码就是new一个进程,进程的资源什么乱七八糟的东西有一堆是根据你在APP的Manifest文件里面配置生成的。这一部分在这不讲,只需要知道,进程的必然要有一个主线程来执行APP里面写的代码吧。

在这里我们不去装逼的拿源码去讲什么第一个Activity的启动,还是按照上面的一个线程创建出要执行 run() 肚子里的代码。假如我们在里面写怎样去加载View,怎样去绘制View,然后展示出来,再然后呢,当View也绘制好了,展示出来了,界面都呈现出来了,这时代码执行序不就结束了么,按道理APP也就关掉了啊,不可能等在那让我们点击啊。其实这就是关键了,我们要想让这个主线程不执行结束,不死掉,应该还是很好解决的。比如直接在前面代码执行完以后,View显示出来了,让主线程去执行一个Whlie(true)的循环,这时不就万事大吉了么。View显示出来,一直显示着。

但是这样还有一个问题,就是主线程一直在while循环里跑,用户无法交互,主线程也无法继续执行这代码。其实这时候我们自己都能想到了,让主线程跑在while循环里,让他每次循环都去访问某个地方,看看有没有什么事要做,有就拿来做了再跑循环,没有就继续跑循环直到有事情做。其实Android整个UI机制就是这样的,装逼点说就是基于事件的机制,Windows其实也是这样的。

主线程跑在一个循环里面,而这个循环就是Looper的 loop() 方法。是时候让主角之一登场了:

说实话Looper的源码加注释真的写的相当清楚明白,而且代码量极少,我们直接看最前面关于Looper的注释:

/**
  * Class used to run a message loop for a thread.  Threads by default do
  * not have a message loop associated with them; to create one, call
  * {@link #prepare} in the thread that is to run the loop, and then
  * {@link #loop} to have it process messages until the loop is stopped.
  *
  * <p>Most interaction with a message loop is through the
  * {@link Handler} class.
  *
  * <p>This is a typical example of the implementation of a Looper thread,
  * using the separation of {@link #prepare} and {@link #loop} to create an
  * initial Handler to communicate with the Looper.
  *
  * <pre>
  *  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();
  *      }
  *  }</pre>
  */

最前面很显的写着:Looper这个类一般用于让线程去做一个消息的循环,线程里面本来是没有一个消息循环和线程相关联的。如果要创建一个有loop循环的线程,就要在线程的run方法里去调执行loop,让它在loop的过程中去处理消息。怎么样,和我前面讲的基本一样,我以宋吉吉的人格发誓,我之前没仔细看过这段注释,果然英雄所见略同。然后,注释又写了个例子,教你怎样写一个有Looper的线程。抬眼一瞅,这不就是在run方法的肚子里执行 Looer.loop() 么。。。

根据我们上面讲的线程的原理,即使不看这个 loop() 方法,先来猜一发,看看它里面会怎么写,无非是一个 while(ture) 循环,然后在里面每次有一个去消息,把消息里的代码执行的操作,好,上源码,接下来就是见证奇迹的时刻:

 public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();
    }
}

额额,loop() 方法是。。。日,竟然是个 for(;;) ,啪啪,这个不算打脸吧,和while(true) 一样嘛。继续看for循环里面的代码:

Message msg = queue.next();

这行就是从MessageQueue里面取一个Message,也就是从消息队列里取一个消息。如果消息是null,就丢弃掉,然后接下来自然不出我们所料要处理消息了:

msg.target.dispatchMessage(msg);

这行自然就是处理消息了,这里面为什么这样写也是有技巧的,让我来解释一下(装一下逼):
因为线程在执行 loop() 的时候,并不知道要在每次循环去执行什么代码,所以在定义这个google在定义这个框架的时候,必须留一个接口,以你来写代码,框架来调用的形式定义。不看源码一切都是瞎逼逼,直接看源码,这个msg是一个Message对象,也就是一个消息,这没什么好说的。Message里有一个叫target的属性是Handler类型的:

 /*package*/ Handler target;

在Handler发送这个消息的时候会把Handler会把自己的引用放到这个消息的target属性里:

 public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

所以最终loop循环如果在消息队列里取到一个消息,就去执行发送这个消息的Handler的 dispatchMessage(msg) 方法,如果我们在去看这个方法,他一定会预留一个接口让我去写代码:

 public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

看到这最后调的这个方法我们应该就很熟悉了,我们平时new一个Handler一般都是这两种方式嘛:

Handler handler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        return false;
    }
});

或者

Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};

这两种方法并没有什么鸟区别,一个用了接口,都是在 handleMessage(Message msg) 方法肚子里写我们要执行的代码。

再回到前面说的,线程在loop中循环,每次循环,没有消息就继续循环,取到消息就执行执行发送该消息的Handler的 dispatchMessage(Message msg) ,而这个方法最终执行的是我们填入代码的 handleMessage(Message msg) 方法。这样就实现了预留出接口,通过循环,让主线程一直取消息,然后我们通过发消息到MessageQueue,并写好收到消息后要执行的操作,让主线程来调,然后在循环过程中执行不同的代码,实现交互。

其实明白了主线程在loop循环的过程中是怎么工作的,Handler也就简单的一笔了,但是它里面还是有一些设计思想在里面。我准备多写几篇,具体到每行代码,把MessageQueue,Message,Handler的源码都拿过来晒晒,然后理解每一行代码的作用。虽然可能开发中使用不需要这么细致的理解,但是Android源码里面的设计思想还是有很多值得借鉴的地方,看着还是挺有意思的。

如果在这篇有没有看懂的地方也不用着急,因为后面会更细致的把整个涉及的知识讲完。

(本篇完,下篇待续)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值