Android 阅读源码,让你彻底理解Handler、Message、Looper之间的关系

        说到Handler,我相信大家都用过,而且经常用,而且在面试中,面试官会经常问这个东东,因为这个东西在Android中非常重要。Handler的使用非常简单,可是呢,原理能回答的出来的真是少之又少,至少我面试的时候,回答的出来的真的好少,而且简历上面写的都是2年以上的经验,甚至3年的。据我的估计,他们想的是,能用就行了,里面的原理没有必要去关心,因为你去关心了,你自己不会再去写这样的东西,也没必要。但是我要告诉大家的是,这种想法是错误的,因为了解原理,对我们的使用非常有帮助,并且同时也增强了你的阅读源码的能力,这样能更好的让你成长。

        在分析他们之间的关系之前,我希望大家对链表不理解多的,可以去看看这篇数据结构之双向链表,理解它,相信对你理解Handler非常有好处。好了,下面我们开始进入主题,来详细聊聊Android的消息机制Handler,首先我们在说这个消息机制Handler的时候,你应该知道这三个是什么的东西?它们各自的所做的工作是什么?很多人说,它是用来更新UI界面的,要这么说也没错,但是更新UI仅仅是Handler的一个特殊使用场景。具体来说是这样的:有时候我们需要在子线程中做一些耗时的I/O操作,可能是读取文件或者是访问网络之类的当耗时操作完成以后可能需要在UI上做一些改变,但是由于Android开发规范的限制,我们不能直接在子线程中访问UI控件,否则将会触发程序异常,这个时候通过Handler就可以将更新UI的操作切换到主线程中执行。因此,本质上来说,Handler并不是专门用于更新UI 的,它只是常被开发者用来更新UI。

Android中的消息机制主要是指Handler的运行机制,而这个Handler的运行,需要底层Looper、MessageQueue来维护,MessageQueue翻译过来就是消息队列,但是他内部并不是真正的采用队列,而是采用了单链表的数据结构来存储消息,但是MessageQueue它只是消息的存储单元,并不会去处理消息,而这个Looper的出现,就帮助解决了这个问题,这个Looper是不停从队列里面取消息,如果没有消息,将会阻塞,否则,就会唤醒,从MessageQueue里面取消息。Message翻译过来就是消息,从字面上来看,比MessageQueue少了一个Queue,相信大家也都知道了,对了,他就是一个消息,消息单元,消息将会通过Handler中的发送消息的方法,会将该消息,压入MessageQueue中。

上面我们说了,Handler、Message、Looper它们所做的基本工作内容,那么下面我就来逐步分析各自的内部实现吧:

首先,我们来看一下Message:

消息是一个单链表,一个消息单元,我们在使用的时候,会通过Message对象附带一个数据。

其次,Looper:

这个Looper是用来从轮训消息队列里面的消息的,它里面有个无限的for循环,不停地从MessageQueue里取出消息。

最后,Handler:

这个用来处理消息的,至于怎么个处理法,全由开发者自行处理。

下面就让我们来阅读源码,彻底了解里面到底是如何实现的:

一、Looper的工作原理:

通过阅读源码,我们知道,每个线程可以有并且只能有一个Looper实例,如果我们在子线程中,如果我们直接创建一个Handler实例,运行时,我们的程序将会抛出一个   Can't create handler inside thread that has not called Looper.prepare()异常

所以,我们需要在子线程中去显示的调用Looper.prepare()方法。上面说道,每个线程中只能有一个Looper实例,为什么只能有一个呢?不要着急,下面我们来瞧瞧Looper中的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));
}


通过prepare()方法,我们就能够一清二楚的地知道,为什么每个线程中只能有一个实例了吧,当然,MessageQueue对于每个线程来说也是唯一的,我们来看看Looper的构造方法。

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


我们通过他的构造方法,我们知道在Looper类中,声明了一个MessageQueue成员变量,来指向这个消息队列,所以说,这个消息队列对于每个线程来说也是唯一的,总结来说,就是一个线程可以并且仅仅只有这一个Looper实例和一个MessageQueue实例。在完成实例的创建之后,我们需要调用Looper类中的loop()方法,来轮询MessageQueue中的消息,loop()方法中调用了MessageQueue中的next()方法来取出消息。

二、MessageQueue原理:

①:我们知道,我们通过Handler类中相关的发送消息的方法,我们将消息,压入消息队列中,但是在Handler提供的相关发送消息的方法中(Message中的sendToTarget()方法也可以发送消息),可以不发送延迟消息跟延迟消息,那么它们在消息队列中,是如何来处理不延迟消息跟延迟消息呢?实际上发送的消息,会将消息按照时间来进行排序,下面我们来看看MessageQueue中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 {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                ......
            }
            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;
}


前面我们说过,它并不是一个真正的队列,而是采用了单链表来存储消息,这段代码就说明了一切,证明我所言非虚吧。哈哈!好,我们从第一个if语句来看,如果之前没有消息,当前进来一个消息,他会把当前这个消息作为一个新的头,然后唤醒那么else里面呢,如果走到else面,就证明,消息队列里不只一个消息。它会根据Message中的when属性来进行判断,然后对消息的逐个排序。不过这么说,相对来说,是有点晦涩难懂,下面我将会给出一幅图,我相信更好理解一些。


三、Handler运行机制原理:

①:获取一个消息,获取一个消息一般有这么几

Message msg = new Message();       handler.obtainMessage();       Message.obtain();

②:我们开发人员在开发的时候一般怎么去创建一个Handler呢?

private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {

            //TODO 处理消息
        }
    };
但是这么创建会有隐患,他会造成内存泄漏,当你这么创建的时候,会出现黄色警告,这里暂时不说怎么去解决,到后面将Android中的消息机制说完,我会写出,将如何去处理这个内存泄漏的问题以及它们为什么会报出这个异常,这里我先给出这段警告语,希望大家保留这个印象,到后面我们会有用处。

③:当我们创建了Handler实例,那么接下来,我就需要通过handler这个实例发送消息的相关方法,将消息发送出去,这里发送出去,指的是将消息压入MessageQueue中。下面我们看看他发送消息的流程:

Message msg = Message.obtain();
handler.sendMessage(msg);——>sendMessageDelayed(msg, 0);——>sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);——>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);
}


在上面我们说道,每个线程可以有并且只能有一个Looper实例,但是Handler实例是可以有多个的,对吧。既然这样,那么问题就来了,①:我之前说,在子线程中直接创建一个Handler实例,会报出一个异常,那为什么在主线程中创建就不会呢?②:可以创建很多handler,那么怎么知道msg由那个Handler去处理呢?

好,下面就来解答你们比较关注的问题:

①:我们在主线程中创建Handler实例不会报异常,那是因为,我们应用在启动(ActivityThread中的入口main函数)的时候,就已经通过调用Looper.prepareMainLooper()方法来创建Looper实例,其实他内部也是调用了Looper.prepare()方法,这个方法接收的是一个boolean的值,主线程调用给的是一个false值,因为,主线程是不能退出消息队列的,只有子线程才可以,随后调用了Looper.loop()方法,来轮训消息。

源码:

/**
     * This manages the execution of the main thread in an
     * application process, scheduling and executing activities,
     * broadcasts, and other operations on it as the activity
     * manager requests.
     *
     * {@hide}
     */
    public final class ActivityThread {

        ........


        private class ApplicationThread extends ApplicationThreadNative {

            ......


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

            Looper.prepareMainLooper();//看看是不是在函数的入口调用了
            

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



②:我们在调用enqueueMessage()方法的时候,里面做了是什么?msg.target = this; 那么这个target是什么类型的呢?其实就是Handler类型的,我们在把消息压入到消息队列的时候,handler就跟这个你发送的msg绑定了,那么下次在分发的时候,就知道是由那个来处理了,不行我们看看Looper.looop()方法:

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);//是不是msg.tartget中分发消息的方法来进行分发的

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


终于分析完了他们的实现,那么接下我们来分析,得到消息以后,我们如何处理的。下面我把源码贴出来:

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


发现,我们之前他们的处理,怎么还有个callback,mCallBack,这两个成员变量是个什么东西啊?靠!说到这里,我们就来看看与这个相关的方法:

先说说callBack这个成员变量吧,这种情况,是我们通常调用post()方法来发送消息的。其实这个callBack其实就是个Runnable实例,如果callback不为空,他将拥有优先处理权。接着调用handleCallback()方法,其实他内部调用的就是runnable的run方法。

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


其次是mCallback,那么说到这个mCallback,那么这种情况呢,其实是Handler提供了一个传入callback接口一个构造方法,好,那么我来看看这个构造方法:

public Handler(Callback callback, boolean async) {
    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 = callback;
    mAsynchronous = async;
}


看到没有,如果实现了这个接口,那么他就可以去拦截消息,因为他的处理级别高于handlerMessage()方法,那么这个消息如何处理,由你开发者来决定。下面给出一个消息处理流程图,来帮大家理解。



注意:

我们在创建Handler时,不管是Eclipse还是Android Studio上,他们会报出一个黄色警告,这个警告说什么呢?之前我贴过一张警告的面板,他的意思是:

In Android, Handler classes should be static or leaks might occur. Messages enqueued on the application thread's MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class. 

大体翻译如下:

Handler 类应该应该为static类型,否则有可能造成泄露。在程序消息队列中排队的消息保持了对目标Handler类的应用。如果Handler是个内部类,那 么它也会保持它所在的外部类的引用。为了避免泄露这个外部类,应该将Handler声明为static嵌套类,并且使用对外部类的弱应用。

它说,有可能造成内存泄漏,是的,在Java中,默认的非静态内部类会持有外部类的引用,我们想象一下,如果我们在activity里创建一个非静态内部类,然后发送一个延迟消息的话,我们关闭activity时,就会造成gc无法回收,因为,handler持有外部类的引用,从而导致内存泄漏。那么如何处理呢?处理的方法有这么两种:

定义一个静态内部类

static class MyHandler extends Handler {
    WeakReference<Activity> mActivity;

    MyHandler(Activity activity) {
        mActivity = new WeakReference<Activity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        Activity theActivity = mActivity.get();
        switch (msg.what) {
            case 0:
                theActivity.popPlay.setChecked(true);
                break;
        }
    }
};



这样就可以解决这个内存泄漏的问题,还有一种就是在activity的onDestroy()方法里调用

handler.removeCallbacksAndMessages(null);


这个方法,其实就是清除队列里面所有的消息,但是这种不推介,还是用上面的那种比较妥当。

好了,说到这里,该说了基本都说到了,现在肚子饿死了,我不行了,我脑子严重缺氧,最后再坚持说一句,欢迎大家拍砖、吐槽。我喜欢你们拍砖吐槽。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值