Handler知识梳理

Handler无论是在代码的使用中还是面试,应用的频率都非常高,这就要求我们将Handler的使用及原理研究透彻。下面根据博客以及相关的一些资料,根据自己对Handler的理解,写下这篇文章用于记录,并加深印象;如有错误之处,请谅解。

看下面这个Handler简单使用的例子:

        private Handler mHandler = new Handler(){//新建Handler并处理消息
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 1000:
                        Log.e("LHC", "Handler Msg.");
                        break;
                    default:
                        break;
                }
            }
        };

        new Thread(new Runnable() {//建立子线程,并在线程中发送消息
            @Override
            public void run() {
                mHandler.sendEmptyMessage(1000);
            }
        }).start();

这样就完成了一个Handler的简单使用。
下面我们来一步步分析,这个过程是怎么实现的。先来看Handler的创建,如下:

1    public Handler() {
2       this(null, false);
3  }

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

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

创建过程其实执行的是Handler(Callback callback, boolean async)方法。来具体看这个方法。

  • 5~10行,执行了一个if语句,目的是为了判断新建的Handler对象是否是非静态的,如果是就显示警告;
  • 11行,生成了一个新的Looper对象。
  • 12~14行,判断生成的mLooper是否为空,为空是抛出异常,表示进行创建Handler的线程,此线程还没有调用Looper.prepare()进行Looper初始化。
  • 15行,初始化MessageQueue对象mQueue。这个是将Looper中的mQueue赋值给它的。
  • 16行,初始化Callback接口对象mCallback
  • 17行,初始化是否异步标记。

第11行代码,生成了一个新的Looper对象,我们看看其内部实现,代码如下:

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

sThreadLocalThreadLocal<Looper>对象,通过get方法就可获取线程中对应的Looper。如果返回值为null,就表示线程还没有关联Looper,也就是还没有初始化一个Looper。初始化的方法如下:

1    public static void prepare() {
2        prepare(true);
3    }

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

这就是Looper的两个初始化方法,具体看下带参数的方法:

  • 5~7行,在if中判断获取的Looper对象时否为空,不为空时,抛出异常,表示一个线程只能初始化一个Looper
  • 8行,调用Looper的构造方法生成一个对象,并将其设置到sThreadLocal中。

那么在构造方法中有进行了什么操作呢?看代码:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

在这个构造方法中完成了MessageQueue的创建,及当前线程的保存。这样在调用new Handler()时,通过代码mQueue = mLooper.mQueue;将此时新建的mQueue赋值给了Handler中的mQueue了。
通过上面的解释,我们也就明白了为什么在子线程中不能直接new Handler(),而是需要先进行调用Looper.prepare()方法了。那么在UI线程中为什么有可以了呢?这是因为在ActivityThread类中已经初始化了,具体下面在说。

Handler建立后,就是消息的发送了,如例子代码:

        mHandler.sendEmptyMessage(1000);

我们进入看看其具体的实现,代码如下:

    public final boolean sendEmptyMessage(int what) {
        return sendEmptyMessageDelayed(what, 0);
    }

    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

sendEmptyMessageDelayed(int what, long delayMillis)方法中,将我们传入的what封装成了一个Message信息,然后在传给方法sendMessageDelayed(msg, delayMillis)。我们继续深入,看代码:

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

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

封装的Message经过传递到了Handler中的enqueueMessage(queue, msg, uptimeMillis)方法,在方法中给target进行了赋值(这里的target就是Message中的Handler对象),并进行了异步判断,最终将Message传递给了方法MessageQueue类中的enqueueMessage(msg, uptimeMillis)。那么在这个方法中做了哪些处理呢?看代码:

    boolean enqueueMessage(Message msg, long when) {
        ......
        synchronized (this) {
            ......
            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;
            ......
        }
        return true;
    }

这个方法就实现了将封装好的Message消息,加入到了MessageQueue消息队列当中。MessageQueue是按照Message触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序.

那么我们是在哪里进行取消息的呢?是在ActivityThread之中,我们来看代码:

    public static void main(String[] args) {
        ......
        Looper.prepareMainLooper();
        ......
        Looper.loop();

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

可以看到在ActivityThread中,我们调用了Looper.prepareMainLooper();进行了初始化,生成了主线程Looper,在通过调用Looper.loop();来进行消息获取。代码如下:

    public static void loop() {
        ......
        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);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
         ......
        }
    }

在方法中用for生成了一个死循环,在里面循环调用queue.next()获取Message,当获取的消息为空时,就跳出死循环;不为空时,就通过代码msg.target.dispatchMessage(msg);去分配消息。那么在next()中是如何不断的获取消息的,看代码:

    Message next() {
        ......
        for (;;) {
            ......
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
        ......
            }
        ......
        }
    }

这个方法中也是使用for生成了一个死循环,用于获取MessageQueue队列中的消息,消息获取成功后退出死循环,等待下次的调用。这样就完成了循环取消息的过程。

消息获取到之后,使用代码msg.target.dispatchMessage(msg);来进行消息分配处理,看具体代码:

 1   public void dispatchMessage(Message msg) {
 2       if (msg.callback != null) {
 3           handleCallback(msg);
 4       } else {
 5           if (mCallback != null) {
 6               if (mCallback.handleMessage(msg)) {
 7                   return;
 8               }
 9           }
10           handleMessage(msg);
11      }
12    }
  • msg.callback不为空时,调用的是静态方法handleCallback(msg)
  • msg.callback为空时且mCallback不为空时,调用的是Callback接口中的handleMessage(msg);
  • msg.callback为空时且mCallback为空时,调用Handler中的自己的handleMessage(msg);

我们通过上面的分析,可以得到这样的结论:

  • 线程默认是没有Looper的,如果需要使用Handler,就必须先创建Looper。
  • ActivityThread被初始化的是时候就创建了Looper,并运行了loop()方法,这也就是UI线程中能直接使用Handler的原因。
  • Handler创建的时候会采用当前线程的Looper来构造消息循环系统,Looper在哪个线程创建,就跟哪个线程绑定,并且Handler是在他关联的Looper对应的线程中处理消息的。

我们调用post方法来实现更新UI的操作,代码如下:

        postHandler = new Handler();

        new Thread(new Runnable() {
            @Override
            public void run() {
                //在这里进行耗时操作...
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                postHandler.post(new Runnable() {
                    @Override
                    public void run() {//在这里进行UI更新
                        Log.e("LHC", "Handler Post...");
                    }
                });
            }
        });

在这里需要注意的是:post方法传入的是Runnable,看着是新建了一个子线程,那么在子线程中怎么能进行UI更新呢?首先,我们要明确一点,一个线程的启动需要调用start()方法,但是这里并没有调用。在JAVA中,直接调用run()方法相当于调用了一个普通方法,当然了还是当前线程执行。我们来看下post里面代码的具体实现:

    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方法之中,将参数r传递给sendMessageDelayed方法,然后在方法getPostMessage(r)中,将r赋值给了m.callback并封装成了Message返回。而sendMessageDelayed方法以后的处理流程在上面已经讲过了,这里就不在重复了。
我们在来回过头来看看上面提到的方法dispatchMessage(msg),代码中当msg.callback不为空时,调用的是静态方法handleCallback(msg)。那么这个静态方法中执行的是什么呢?看代码:

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

这里调用的是Messagecallback下的run()方法,而这个callback就是我们在调用post(Runnable r)时的传入的参数r

所以post方法中就不存在新建线程,方法post运行在调用它的Handler所在的线程中,而这个Handler运行在主线程中,所以在主线程中更新UI肯定没有问题了。
  • 那么系统为什么不允许在子线程访问UI呢?摘自<Android 开发艺术与探索>

这是因为Android的UI线程不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。

  • 那么系统为什么不对UI控件加上锁机制呢?摘自<Android 开发艺术与探索>

缺点有两个:首先,加上锁机制会让UI访问的逻辑变的复杂;其次,锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。

在看下面的代码:

        Handler h = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                Log.e("LHC", "Handler.Callback Msg:"+ msg.what);
                return true;
            }
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
                h.sendEmptyMessage(1);
            }
        }).start();

这里使用的是构造方法Handler(Callback callback)进行生成Handler,具体实现代码:

    public interface Callback {
        public boolean handleMessage(Message msg);
    }

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

将传入的Callback接口对象参数callback赋值给了Handler中的成员变量mCallback。在回过头来看处理消息方法dispatchMessage(msg),代码中当msg.callback为空时且mCallback不为空时,调用的是Callback接口中的handleMessage(msg)
到这里就将分发消息方法中的情况都介绍清楚了。

  • 为什么Looper.loop()死循环不会导致APP ANR?

ActivityThread的main方法主要作用就是做消息循环,一旦退出消息循环,主线程运行完毕,那么你的应用也就退出了。
Android是事件驱动的,在Loop.loop()中不断接收事件、处理事件,而Activity的生命周期都依靠于主线程的Loop.loop()来调度,所以可想而知它的存活周期和Activity也是一致的。当没有事件需要处理时,主线程就会阻塞;当子线程往消息队列发送消息,并且往管道文件写数据时,主线程就被唤醒。
真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

上面我们基本讲解了Handler的流程,下面我们整体上来总结一下:

Handler机制主要涉及了这四个类:HandlerLopperMessageMessageQueue
  • 新建Handler,通过Handler的post或者sendMessage方法发送消息,Handler通过sendMessageDelayed方法,将Message交给MessageQueue.
  • MessageQueue通过调用enqueueMessage(msg, uptimeMillis)方法,将Message以链表的方式加入队列当中.
  • Looper类中的loop()方法循环调用MessageQueue.next()方法获取消息,并调用Handler的dispatchMessage(msg)方法处理消息.
  • 在dispatchMessage(msg)方法中,根据判断msg.callback,mCallback是否为空来执行相对应的回调,如果都为空就执行Handler的自身handlerMessage(msg)回调.

在使用Handler过程当中,我们还会遇到这种情况,看代码:

        Handler mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
               //TODO 自己的代码逻辑
            }
        };

这次这样写的时候,代码new Handler()中的Handler报警告The following Handler class should be static or leaks might occur,警告的意思也就是说Handler类应当是静态的,否则可能会造成内存泄露。那么为什么会出现这个警告呢?Handler会在一个后台线程中处理耗时操作,处理完成后将结果发送给UI线程进行保存或者显示。那么在后台线程执行的过程中,如果Activity关闭了,在正常情况下,Activity会在GC检查时被回收掉,但是由于后台线程还在执行,它持有Handler的引用,而Handler持有Activity的引用,这样造成Activity不会被回收掉,直到后台线程执行完成。这样就造成了内存泄露。那么我们如何解决呢?看下面代码:

    private static class MyHandler extends Handler{
        private WeakReference<Activity> weakReference;
        MyHandler(Activity activity){
            weakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            Activity activity = weakReference.get();
            if (activity == null || activity.isFinishing()){
                return;
            }

           //TODO msg消息的处理
           ......
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        myHandler.removeCallbacksAndMessages(null);//Activity退出时,将还未执行的消息处理掉。免得引发异常
    }

通过上面的这种写法,就解决了内存泄露的问题。

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

推荐阅读更多精彩内容