Handler源码解析,深入浅出

Handler机制

Handler介绍:
我们都知道Android的主线程不能做耗时任务,否者会导致ANR.但是界面的更新又必须在主线程中进行,这样我们就必须在子线程中处理耗时的任务,然后在主线程中更新UI.


但是,我们怎么知道子线程何时完成任务,又应该什么时候更新UI,更新什么内容,为了解决这个任务,Android为我们提供了一个消息机制即Handler机制.


源码分析:

创建Handler时无参构造函数内调用了两个参数的构造函数,而在两个参数的构造函数中就是将一些变量进行赋值.

通过Looper中的myLooper方法来获得Looper实例的,如果Looper为null的话就会抛无法在未调用Looper.prepare()的线程内创建handler异常

    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.myLooper()之前必须先调用Looper.prepare()方法

    public static void prepare() {
        prepare(true);//prepare()方法调用了prepare(boolean quitAllowed)方法
    }
    
    private static void prepare(boolean quitAllowed) {

        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //如果Looper为空,实例化了一个Looper,然后将Looper设置进sThreadLocal中
        sThreadLocal.set(new Looper(quitAllowed));
    }

从上面代码中可以看到,prepare()方法调用了prepare(boolean quitAllowed)方法,实例化了一个Looper,然后将Looper设置进sThreadLocal中,由此可见Looper是唯一的,多次调用Looper.prepare()会报异常

什么是ThreadLocal:

Threadlocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说无法获取到数据.


虽然在不同线程中访问的是同一个ThreadLocal对象,但是它们通过ThreadLocal获取到的值却是不一样的.


一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal

知道了ThreadLocal.set()方法的作用,则Looper.prepare()就是将Looper与当前线程进行绑定(当前线程就是调用Looper.prepare方法的线程)

Looper.prepare()方法在主线程中Android系统已经帮我们在ActivityThread类的main方法中调用Looper.prepareMainLooper();从当前线程中的ThreadLocal中取出Looper实例

    public static void prepareMainLooper() {
    
        prepare(false);//这里调用了prepare方法
        
        synchronized (Looper.class) {
            if (sMainLooper != null) {
            
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            
            sMainLooper = myLooper();
        }
    }

    public static @Nullable Looper myLooper() {
            return sThreadLocal.get();//从当前线程中的ThreadLocal中取出Looper实例
         }
    private Looper(boolean quitAllowed) {
    
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

通过上面的代码,我们可以知道mQueue就是MessageQueue,在我们调用Looper.prepare方法时就将mQueue实例化了,拿到Looper中的mQueue这个成员变量,然后再赋值给Handler中的mQueue

mQueue = mLooper.mQueue;

Handler明明是在子线程发送的消息怎么会跑到主线程中?

    public final boolean sendMessage(Message msg){
            return sendMessageDelayed(msg, 0);
     }
    
    public final boolean sendMessageDelayed(Message msg, long delayMillis){
            if (delayMillis < 0) {
                 delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
     }
    
     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);
        }

其实Handler.sendMessage()、sendEmptyMessage等方法最终都是调用sendMessageAtTime(),返回enqueueMessage()方法

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

msg.target = this; 就是将当前的Handler对象赋值给了Message中的target变量,从而将调用sendMessage()方法的Handler与message进行绑定.

queue.enqueueMessage(msg,uptimeMillis)就是调用了MessageQueue中的enqueueMessage(),将消息放入到消息队列中,如果消息成功放入消息队列,返回true,失败返回false,而失败的原因通常是因为处理消息队列正在退出.

Handler在sendMessage时会将自己设置给Message的target变量即将自己与发送的消息绑定。Handler的sendMessage是将Message放入MessageQueue中。


怎样从MessageQueue中获取Message

在ActivityThread类main()中,结尾处有Looper.loop();

    public static void loop() {
        final Looper me = myLooper();//通过myLooper方法拿到与主线程绑定的Looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        
        final MessageQueue queue = me.mQueue;//从Looper中得到MessageQueue
        
        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;
        }
    
        try {
            //这句代码是重点
            msg.target.dispatchMessage(msg);

Looper.loop();就是拿到用与主线程绑定的Looper对象,从Looper对象中得到MessageQueue,死循环从queue.next()不断取出消息,若队列中无消息,则阻塞等待.


msg.target.dispatchMessage(msg);在msg.target就是发送消息的那个Handler,所以这句代码本质就是调用了Hadler的dispatchMessage()方法

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
                handleCallback(msg);
        } else {
            //if中的代码其实是和if (msg.callback != null) {handleCallback(msg);} 
            //原理差不多的,只不过mCallback是Handler中的成员变量。
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                        return;
                }
            }
                //当上面的条件都不成立时,就会调用这句代码
                handleMessage(msg);
        }
    }

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

msg.callback就是Runnable对象,当msg.callback不为null时会调用 handleCallback(msg)方法

那什么情况下if (msg.callback != null)这个条件成立呢!就是调用Handler的post方法呀,最后在Runnable的run方法中来处理

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

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

post方法最终也是将Runnable封装成消息,然后将消息放进MessageQueue中

    public void handleMessage(Message msg) {
        //它就是一个空方法,具体的代码是我们实例化Handler重写handleMessage()这个方法进行处理的
    }

总结

Hanler机制主要是四个类

  • Handler :负责发送和处理消息
  • Message:用来携带数据
  • MessageQueue:消息队列
  • Looper:消息泵,负责不断的从MessageQueue中取Message
image
Handler
  1. 在实例化Handler的时候要先调用Looper.prepare()

Looper.prepare()方法就是实例化了一个Looper,然后将Looper设置进ThreadLocal中,将Looper与当前线程绑定


但是,在主线程中Android系统已经帮我们调用了Looper.prepare方法可以看下ActivityThread类中的main方法


Looper.prepareMainLooper();这句话的实质就是调用了Looper的prepare方法

  1. 在实例化Handler的时候,通过Looper.myLooper()获取Looper,然后在获取Looper中的MessageQueue.

Looper.myLooper()就是从ThreadLocal中获取Looper对象,Looper构造中创建MessageQueue

  1. 在子线程中调用Handler的sendMessage方法时会将自己设置给Message的target变量即将自己与发送的消息绑定,将Message放入MessageQueue中,然后调用Looper.loop()方法来从MessageQueue中取出Message,执行msg.target.dispatchMessage(msg)

Handler的无论时sendMessage、sendEmptyMessage等方法最后调用了sendMessageAtTime()方法,sendMessageAtTime()方法最后返回的是enqueueMessage()


enqueueMessage()方法最后是MessageQueue中的enqueueMessage方法,将消息放进消息队列中,如果消息已成功放入消息队列,则返回true。失败时返回false,而失败的原因通常是因为处理消息队列正在退出。

  1. 通过Looper.loop()从MessageQueue中取出消息

ActivityThread类中的main方法结尾处,Looper.loop()方法,通过myLooper()方法拿到与主线程绑定的Looper,从Looper中得到MessageQueue,通过next()方法获取Message,没有消息时阻塞


msg.target.dispatchMessage(msg);说明已经从消息队列中拿到了消息,msg.target也就是发送消息的那个Handler,所以这句代码的本质就是调用了Handler中的dispatchMessage(msg)方法


dispatchMessage(msg)中判断是post()还是send(),如果是post()就是调用Runnable中的run()方法,如果是send()及调用handlerMessage(Message msg)方法,handlerMessage()方法是空方法,自己重写进行处理


在子线程中使用Handler

在子线程中使用Handler的方式如下

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

推荐阅读更多精彩内容