【Android源码】Handler 机制源码分析

为什么要使用Handler

  1. 因为在Android中访问UI只能在主线程中进行,如果在子线程中运行,则程序会抛出异常。

    // ViewRootImpl.java
    void checkThread() {
       if (mThread != Thread.currentThread()) {
           throw new CalledFromWrongThreadException(
                   "Only the original thread that created a view hierarchy can touch its views.");
       }
    }
    
  2. 为什么不允许在子线程中访问UI?

    因为Android的UI并不是线程安全的,如果在多线程中执行UI的操作,那么UI的状态是不可控的,这个时候就会出现各种问题。
    那么最好的办法就是只能在一个线程中执行UI的操作。
    而Android主线程中又不能执行耗时操作,因为那样就会导致程序的ANR。
    所以Android提供了Handler这样的机制,用来在子线程中执行耗时操作之后,发送消息给主线程,主线程再执行UI的更新操作。

Handler机制原理分析

这里我们以Android UI线程的Handler来分析。

Handler的创建

Android的应用入口是ActivityThread.main()函数,UI线程的Handler就是在这个函数中创建的。

public static void main(String[] args) {
    Looper.prepareMainLooper();
    thread.attach(false);
    ActivityThread thread = new ActivityThread();
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    Looper.loop();
}

当main函数执行之后,应用就启动了,从这个时候开始,Looper就会一直从消息队列中取出消息并处理。而应用会通过Handler来不断的添加消息给消息队列。通过不断的添加消息和取出消息,整个消息机制就被运转起来了。

Looper

从上面可以看到在Handler创建前后,通过Looper的prepareloop方法,创建了Looper对象和开启消息循环。

  1. 构造函数

    private Looper(boolean quitAllowed) {
       mQueue = new MessageQueue(quitAllowed);
       mThread = Thread.currentThread();
    }
    
    1. 创建了MessageQueue对象,这个对象是消息队列,主要用来存放我们的消息,会在下面具体分析。
    2. 获取当前的线程并保存起来
  2. prepare

    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));
    }
    
    1. 判断ThreadLocal中在当前线程是否已经存在了Looper对象,如果存在,则抛出异常,所以可以得出在同一个线程中只能存在一个Looper对象。
    2. 在ThreadLocal存入当前线程的Looper对象。
    3. ThreadLocal对象是一个线程内部的数据存储类,通过它可以在指定的线程存储数据,当存储数据之后,就只能在指定的线程中获取到存储的数据,其他线程是不能获取到数据的。
  3. loop

    public static void loop() {
       final Looper me = myLooper();
       // 获取消息队列
       final MessageQueue queue = me.mQueue;
        // 死循环来轮询的从消息队列中获取消息
       for (;;) {
           Message msg = queue.next(); // might block
           if (msg == null) {
               return;
           }
           
           try {
               msg.target.dispatchMessage(msg);
           } finally {
               if (traceTag != 0) {
                   Trace.traceEnd(traceTag);
               }
           }
    
           msg.recycleUnchecked();
       }
    }
    
    1. loop函数会不断的从消息队列中取出消息,当queue.next()为空的时候就直接阻塞住
    2. msg不为空,调用msg.target.dispatchMessage(msg)处理消息,而msg.target其实就是Handler对象,取出消息之后就交给Handler来处理发送的消息了。
  4. quit

    public void quit() {
       mQueue.quit(false);
    }
    public void quitSafely() {
        mQueue.quit(true);
    }
    

    调用MessageQueue的quit方法来退出Looper。其中上面的是直接退出,下面的是安全的退出,也就是只标记一下,当消息队列中的消息全部执行完成之后安全的退出。

    当我们在子线程中创建Looper和Handler的时候,如果我们不调用quit方法的话,子线程就会一直处于等待状态,所以我们需要调用quit方法退出Looper。

MessageQueue

在Looper中大量的使用到了MessageQueue,而MessageQueue主要就是两个操作插入消息和读取消息并删除消息,我们来一起分析下:

  1. 构造函数

    MessageQueue(boolean quitAllowed) {
       mQuitAllowed = quitAllowed;
       mPtr = nativeInit();
    }
    

    在构造函数中,调用了nativeInit方法在Native层创建了NativeMessageQueue和Looper,并将Looper设置给当前的线程。这个时候Java层和Native层都有了MessageQueue和Looper。
    在Native层的Looper中,创建了一个管道,本质上就是一个文件,一个管道中有读和写两个文件操作符,通过读和写来将消息写入和读取。

  2. enqueueMessage 插入消息

    boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
           msg.markInUse();
           msg.when = when;
           Message p = mMessages;
           boolean needWake;
           if (p == null || when == 0 || when < p.when) {
               // New head, wake up the event queue if blocked.
               msg.next = p;
               mMessages = msg;
               needWake = mBlocked;
           } else {
               needWake = mBlocked && p.target == null && msg.isAsynchronous();
               Message prev;
               for (;;) {
                   prev = p;
                   p = p.next;
                   if (p == null || when < p.when) {
                       break;
                   }
                   if (needWake && p.isAsynchronous()) {
                       needWake = false;
                   }
               }
               msg.next = p; // invariant: p == prev.next
               prev.next = msg;
           }
    
           // We can assume mPtr != 0 because mQuitting is false.
           if (needWake) {
               nativeWake(mPtr);
           }
       }
       return true;
    }
    

    enqueueMessage其实就是构建好msg,并插入到单链表中。

  3. next 读取消息并删除消息

    Message next() {
        for (;;) {
            nativePollOnce(ptr, nextPollTimeoutMillis);
        }
    }
    

    next方法是一个无限循环方法,当消息队列中没有消息的时候,next方法会被阻塞在这里,当有新的消息的时候,next方法会最终返回这条消息并从单链表中移除。

Handler

  1. 构造函数

    public Handler(Callback callback, boolean async) {
    
       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;
    }
    
    1. 首先获取Looper对象,如果Looper对象为空,则抛出异常,所以当我们在子线程中创建Handler的时候,首先要通过Looper.prepare()来创建子线程的Looper对象。
    2. 通过Looper获取到MessageQueue
  2. 发送消息

    Handler的发送消息主要是post的方法和send的方法,而他们最终调用的都是:

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

    Handler主要就是向消息队列中插入了一条消息,这个时候MessageQueue就会调用插入的方法来插入消息。

  3. 读取消息

    当Handler发送消息之后,MessageQueue就会通过next方法读取出这个消息,那么Looper的loop就不会被阻塞住,取出消息之后就调用了msg.target.dispatchMessage(msg)方法,最终交给Handler的dispatchMessage来执行。

    /**
    * Handle system messages here.
    */
    public void dispatchMessage(Message msg) {
       if (msg.callback != null) {
           handleCallback(msg);
       } else {
           if (mCallback != null) {
               if (mCallback.handleMessage(msg)) {
                   return;
               }
           }
           handleMessage(msg);
       }
    }
    
    1. 检查Message的callback是否为空,不为空则通过handleCallback来处理,而callback就是通过post传递过来的Runnable对象。直接调用run方法执行。

      private static void handleCallback(Message message) {
          message.callback.run();
      }
      
    2. 检查mCallback是否为空,不为空则调用mCallback.handleMessage方法处理消息。而mCallback就是我们在创建Handler的时候传递过去的callback。

      public interface Callback {
          public boolean handleMessage(Message msg);
      }
      public Handler(Callback callback) {
          this(callback, false);
      }
      
    3. 调用handleMessage(msg)处理消息。

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

推荐阅读更多精彩内容