Handler运行机制

问题

  1. Handler. post(Runnable r)的消息是如何传递的,传递了什么消息。
    Post内部将r对象转化为消息发送到Handler所在线程的消息队列中,执行Looper.loop()方法的线程,将直接执行r中的run()方法(注意没有开启新线程,而是在Handler所在的线程中执行的r中的run()方法)

  2. Handler在哪个线程创建,就在哪个线程运行handleMessage(Message msg)。如何实现的绑定。
    Looper.prepare()方法中将出初始化一个线程池,它能保证Looper与当前线程一一对

  3. Looper的运行机制
    Looper.prepare()方法中进行了线程池的初始化、创建消息队列并将消息放在消息队列中等操作(其中msg.targe属性保存了Handler的对象),在调用Looper.loop()方法从消息队列中取消息并调用Handler的dispatchMessage(Message msg)方法,在该方法中执行handleMessage(Message msg)方法处理消息

  4. 主线程如何利用Handler消息机制运行handleMessage(Message msg)。
    在创建Handler对象的线程中,Looper.loop()方法调用了Handler的dispatchMessage(Message msg)方法,在该方法中执行了handlemessage(Message msg)

  5. 主线程的Looper在哪定义的,handleMessage(Message msg)如何运行到主线程中的。
    ActivityThread的main()方法中首先执行了Looper.prepare()方法,并在main()方法的最后执行了Looper.loop()方法(该方法中执行了handleMessage(Message msg)方法)

Handler的创建

Handler的默认构造方法如下:

public Handler() {
        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 = null;
}

在Handler中没有看到与当前线程相关的绑定操作,唯一可能与此有关的便是mLooper = Looper.myLooper();这一句。于是查看Looper.myLooper()方法,其代码如下:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static Looper myLooper() {
        return sThreadLocal.get();
}

这里主要用到的就是一个ThreadLocal,ThreadLocal是在Java.lang包里定义的一个工具类,其主要功能是为本地所有线程保存一个数据。ThreadLocal保证每个线程之间的数据相互独立,一个线程相关数据的改变不会影响其他的线程数据。

在Looper类中,当ThreadLocal被有效初始化后,myLooper()方法将能够有效的获取当前线程所对应的Looper对象。那么Looper的静态方法能够通过其所执行的线程方便的获取此线程相关的Looper对象,使得Looper类使用各静态方法便能够获得与使用其对象一样方便的使用方式。
ThreadLocal的初始化在Looper.prepare()中进行。
在Looper. prepare()中将当前线程相关的唯一的一个Looper对象与当前线程进行相关联,其代码如下:

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

因此,在使用Looper之前进行prepare()(里面有ThreadLocal的初始化操作)将保证当前线程拥有并只有一个Looper的对象与之相对应,在Handler创建时将获取与此Handler想对应的Looper
至此,Handler与执行线程的问题解决。

Looper的运行

虽然解决了Handler与线程以及Looper之间的关联关系,但HandleMessage()究竟如何被相关线程执行的问题仍然没有得到解决。
在Looper的API文档中有如下记述:
一个典型的Looper线程如下所示。

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

在执行handleMessage(Message msg)线程中应先执行Looper.prepare()方法,并创建Handler对象,最后执行Looper.loop()方法。因此,handleMessage(Message msg)方法应该在Looper.loop()方法中以某种方式运行。
Looper.loop()方法的定义如下:

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

注意:调用loop()方法的线程与创建Handler对象的线程是同一线程。或者说,Handler创建的线程在没有改变其指向的情况下,只能由该线程的loop()方法来处理传递给Handler的消息。
在通过myLooper()方法获取了当前线程对应的Looper对象me后,使用me获取了当前线程所对应的MessageQueue。然后进入了一个由for定义的死循环。
在获取了即将要处理的Massage msg后,执行了msg.target.dispatchMessage(msg)。

msg.target是定义在Massage中的成员变量,类型为Handler。指代此Message应该由哪个Handler处理。因此,当msg.target.dispatchMessage(msg)被执行,将执行此Message所对应的Handler的dispatchMessage(Message msg)方法。

Handler中dispatchMessage(Message msg)方法定义如下:

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

在不考虑msg拥有callback的情况下,要么执行Handler的Callback接口中的handleMessage(msg),要么执行Handler复写的handleMessage(msg)。
至此,handleMessage(Message msg)方法的调用地点可以确定。应该是在与Handler对象创建的同一线程中,由其后的Looper.loop()方法进行的调用

主线程中的Looper

在正常编程的情况下,通常遇到的都是Activity的onCreate()方法、onStart()方法、onDestroy()方法等,他们就代表的主线程的使用,在这些方法中进行UI界面的修改是被允许的,但主线程究竟是如何调用Looper.loop()的却不得而知,因此需要查找Activity的实际启动地点才能得知主线程的真实样貌及Looper.loop()方法调用位置。
在android.app包中查找ActivityThread类,其中在其源代码最后定义了如下方法:

public static void main(String[] args) {
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        AsyncTask.init();

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

在看到main方法的定义后,终于看到了Activity主线程的真实面目。这里对Looper进行了当前主线程的prepare并且在main方法的最后执行了Looper.loop()方法,实现对当前线程Handler.HandleMessage(Message msg)的调用。
至此,主线程的Handler.HandleMessage(Message msg)的调用问题解决。

Handler. post(Runnable r)

关于pos()t方法究竟做了些什么,怎么以一个Runnable接口当做Message消息发送给其它线程以及如何执行。这些问题还需要查看Handler. post(Runnable r)的源代码,其源代码如下:

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

根据源代码所述,其post()方法也是通过某种形式将Runnable转换为Message进行消息的发送。而消息的获取方式为getPostMessage(r),同时Runnable接口也是在这个方法中进行处理的。getPostMessage(Runnable r)方法定义如下:

 private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

其中Message.obtain()是在消息池中获取一条新的消息,即消息本身是空的。而Runnable接口r紧紧是将其设置给刚刚创建的新消息m的callback。
在获取了消息并设置了callback后就用将这条消息send给消息队列。这条消息的处理同样在Looper.loop()方法中调用了Handler的dispatchMessage(Message msg)。再次给出dispatchMessage(Message msg)方法的代码:

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

可以看到,当msg.callback非空时,将会调用handleCallback(msg)。一下给出handleCallback(Message message)的代码:

private static void handleCallback(Message message) {
        message.callback.run();
    }

在handleCallback(Message message)中直接调用了callback的run()方法。即在Looper.loop()方法执行的线程中,直接执行了Runnable所定义的线程。需要注意的是,这里并没有启动新的线程来运行Runnable接口,而是在Handler所在的线程中运行

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容