美文网首页MobDevGroupandroidAndorid的好东西
Android 消息处理机制(Looper、Handler、Me

Android 消息处理机制(Looper、Handler、Me

作者: Kelin | 来源:发表于2016-08-19 17:27 被阅读15019次

    Android 消息处理机制估计都被写烂了,但是依然还是要写一下,因为Android应用程序是通过消息来驱动的,Android某种意义上也可以说成是一个以消息驱动的系统,UI、事件、生命周期都和消息处理机制息息相关,并且消息处理机制在整个Android知识体系中也是尤其重要,在太多的源码分析的文章讲得比较繁琐,很多人对整个消息处理机制依然是懵懵懂懂,这篇文章通过一些问答的模式结合Android主线程(UI线程)的工作原理来讲解,源码注释很全,还有结合流程图,如果你对Android 消息处理机制还不是很理解,我相信只要你静下心来耐心的看,肯定会有不少的收获的。

    概述#


    1、我们先说下什么是Android消息处理机制?

    消息处理机制本质:一个线程开启循环模式持续监听并依次处理其他线程给它发的消息。

    简单的说:一个线程开启一个无限循环模式,不断遍历自己的消息列表,如果有消息就挨个拿出来做处理,如果列表没消息,自己就堵塞(相当于wait,让出cpu资源给其他线程),其他线程如果想让该线程做什么事,就往该线程的消息队列插入消息,该线程会不断从队列里拿出消息做处理。

    2、Android消息处理机制的工作原理?

    打个比方:公司类比App

    • PM 的主要工作是设计产品,写需求文档,改需求,中途改需求,提测前改需求...
    • UI 主要工作是UI设计,交互等。
    • RD 工作我就不说了
    • CEO 不解释。

    公司开创之后(App启动),那么CEO开始干活了(主线程【UI线程】启动),这时候CEO开启了无限循环工作狂模式,自己的公司没办法啊(相当于UI主线程转成Looper线程【源码里面有】)CEO招了一名RD(new Handler 实例)并把告诉PM和UI,如果你们有什么任务和需求就让RD(Handler实例)转告给我(CEO)。RD会把PM和UI的需求(Message)一条条记到CEO的备忘录里(MessageQueue)。CEO 无限循环的工作就是不断查看备忘录,看有什么任务要做,有任务就从备忘录一条一条拿出任务来,然后交给这一名RD(Handler 实例)去处理(毕竟CEO 不会写代码 囧...)。当然如果备忘录都做完了,这时候CEO就会去睡觉(线程堵塞【简单理解成线程wait】,让出CPU资源,让其他线程去执行)。但是这个备忘录有个特殊的功能就是没有任务的时候突然插入第一条任务(从无到有)就会有闹钟功能叫醒CEO起床继续处理备忘录。 整个消息处理机制的工作原理基本就是这样的。后面会有源码分析,你再来结合这个场景,会更好理解一些。

    这里先给一张Android消息处理机制流程图和具体执行动画,如果看不懂没事,接着往下看(后面会结合Android UI主线程来讲解),然后结合着图和动画一块看更能理解整个机制的实现原理。



    3、LooperHandlerMessageQueue,Message作用和存在的意义?

    • **Looper **
      我们知道一个线程是一段可执行的代码,当可执行代码执行完成后,线程生命周期便会终止,线程就会退出,那么做为App的主线程,如果代码段执行完了会怎样?,那么就会出现App启动后执行一段代码后就自动退出了,这是很不合理的。所以为了防止代码段被执行完,只能在代码中插入一个死循环,那么代码就不会被执行完,然后自动退出,怎么在在代码中插入一个死循环呢?那么Looper出现了,在主线程中调用Looper.prepare()...Looper.loop()就会变当前线程变成Looper线程(可以先简单理解:无限循环不退出的线程),Looper.loop()方法里面有一段死循环的代码,所以主线程会进入while(true){...}的代码段跳不出来,但是主线程也不能什么都不做吧?其实所有做的事情都在while(true){...}里面做了,主线程会在死循环中不断等其他线程给它发消息(消息包括:Activity启动,生命周期,更新UI,控件事件等),一有消息就根据消息做相应的处理,Looper的另外一部分工作就是在循环代码中会不断从消息队列挨个拿出消息给主线程处理。

    • **MessageQueue **
      MessageQueue 存在的原因很简单,就是同一线程在同一时间只能处理一个消息,同一线程代码执行是不具有并发性,所以需要队列来保存消息和安排每个消息的处理顺序。多个其他线程往UI线程发送消息,UI线程必须把这些消息保持到一个列表(它同一时间不能处理那么多任务),然后挨个拿出来处理,这种设计很简单,我们平时写代码其实也经常这么做。每一个Looper线程都会维护这样一个队列,而且仅此一个,这个队列的消息只能由该线程处理。

    • Handler **
      简单说Handler用于同一个进程的线程间通信。Looper让主线程无限循环地从自己的MessageQueue拿出消息处理,既然这样我们就知道
      处理消息肯定是在主线程中处理的,那么怎样在其他的线程往主线程的队列里放入消息呢?其实很简单,我们知道在同一进程中线程和线程之间资源是共享的,也就是对于任何变量在任何线程都是可以访问和修改的,只要考虑并发性做好同步就行了,那么只要拿到MessageQueue 的实例,就可以往主线程的MessageQueue放入消息,主线程在轮询的时候就会在主线程**处理这个消息。那么怎么拿到主线程 MessageQueue的实例,是可以拿到的(在主线程下mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),但是Google 为了统一添加消息和消息的回调处理,又专门构建了Handler类,你只要在主线程构建Handler类,那么这个Handler实例就获取主线程MessageQueue实例的引用(获取方式mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),Handler 在sendMessage的时候就通过这个引用往消息队列里插入新消息。Handler 的另外一个作用,就是能统一处理消息的回调。这样一个Handler发出消息又确保消息处理也是自己来做,这样的设计非常的赞。具体做法就是在队列里面的Message持有Handler的引用(哪个handler 把它放到队列里,message就持有了这个handler的引用),然后等到主线程轮询到这个message的时候,就来回调我们经常重写的Handler的handleMessage(Message msg)方法。

    • **Message **
      Message 很简单了,你想让主线程做什么事,总要告诉它吧,总要传递点数据给它吧,Message就是这个载体。

    源码分析#


    接下来我们会结合App主线程(UI线程)来讲解,从App启动后一步一步往下走分析整个Android的消息处理机制,首先在ActivityThread类有我们熟悉的main的函数,App启动的代码的入口就在这里,UI线程本来只是一个普通线程,在这里会把UI线程转换成Looper线程,什么是Looper线程,不急往下看就知道了。

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

    首先执行的是 Looper.prepareMainLooper() 我们来看下Looper里面的这个方法做了什么?

    注:看之前先稍微了解下ThreadLocal是什么?
    ThreadLocal: 线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。这里线程自己的本地存储区域存放是线程自己的Looper。具体看下ThreadLocal.java 的源码!

    public final class Looper {
        // sThreadLocal 是static的变量,可以先简单理解它相当于map,key是线程,value是Looper,
        //那么你只要用当前的线程就能通过sThreadLocal获取当前线程所属的Looper。
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
        //主线程(UI线程)的Looper 单独处理,是static类型的,通过下面的方法getMainLooper() 
        //可以方便的获取主线程的Looper。
        private static Looper sMainLooper; 
    
        //Looper 所属的线程的消息队列
        final MessageQueue mQueue;
        //Looper 所属的线程
        final Thread mThread;
    
        public static void prepare() {
            prepare(true);
        }
    
        private static void prepare(boolean quitAllowed) {
             //如果线程的TLS已有数据,则会抛出异常,一个线程只能有一个Looper,prepare不能重复调用。
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            //往线程的TLS插入数据,简单理解相当于map.put(Thread.currentThread(),new Looper(quitAllowed));
            sThreadLocal.set(new Looper(quitAllowed));
        }
    
        //实际上是调用  prepare(false),并然后给sMainLooper赋值。
        public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
        //static 方法,方便获取主线程的Looper.
        public static Looper getMainLooper() {
            synchronized (Looper.class) {
                return sMainLooper;
            }
        }
        
        public static @Nullable Looper myLooper() {
            //具体看ThreadLocal类的源码的get方法,
            //简单理解相当于map.get(Thread.currentThread()) 获取当前线程的Looper
            return sThreadLocal.get();
        }
    }
    

    看了上面的代码(仔细看下注释),我们发现 Looper.prepareMainLooper()做的事件就是new了一个Looper实例并放入Looper类下面一个static的ThreadLocal<Looper> sThreadLocal静态变量中,同时给sMainLooper赋值,给sMainLooper赋值是为了方便通过Looper.getMainLooper()快速获取主线程的Looper,sMainLooper是主线程的Looper可能获取会比较频繁,避免每次都到 sThreadLocal 去查找获取。

    接下来重点是看下Looper的构造函数,看看在new Looper的时候做了什么事?

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

    看到没有,这时候给当前线程创建了消息队列MessageQueue,并且让Looper持有MessageQueue的引用。执行完Looper.prepareMainLooper() 之后,主线程从普通线程转成一个Looper线程。目前的主线程线程已经有一个Looper对象和一个消息队列mQueue,引用关系如下图:(主线程可以轻松获取它的Looper,主线程的Looper持有主线程消息队列的引用)


    具体如何获取主线程的Looper对象和消息列表呢?

    //在主线程中执行
    mLooper = Looper.myLooper();
    mQueue = mLooper.mQueue
    //或者
    mLooper=Looper.getMainLooper()
    

    接下来回到ActivityThread 的main函数,执行完Looper.prepareMainLooper() 之后下一句代码是ActivityThread thread = new ActivityThread();这句话就是创建一下ActivityThread对象,这边需要注意的时候ActivityThread并不是一个线程,它并没有继承Thread,而只是一个普通的类public final class ActivityThread{...}ActivityThread的构造函数并没有做什么事只是初始化了资源管理器。

     ActivityThread() {
         mResourcesManager = ResourcesManager.getInstance();
     }
    

    接着往下看下一行代码

    ActivityThread thread = new ActivityThread();
    //建立Binder通道 (创建新线程)
    thread.attach(false);
    

    thread.attach(false);便会创建一个Binder线程(具体是指ApplicationThread,该Binder线程会通过想 HandlerMessage发送给主线程,之后讲)。我们之前提到主线程最后会进入无限循环当中,如果没有在进入死循环之前创建其他线程,那么待会谁会给主线程发消息呢?,没错就是在这里创建了这个线程,这个线程会接收来自系统服务发送来的一些事件封装了Message并发送给主线程,主线程在无限循环中从队列里拿到这些消息并处理这些消息。(Binder线程发生的消息包括LAUNCH_ACTIVITYPAUSE_ACTIVITY 等等)

    继续回到mian 函数的下一句代码Looper.loop() 那么重点来了,我们来看下Looper.loop()的源码:

    public static void loop() {
        final Looper me = myLooper();  //获取TLS存储的Looper对象,获取当前线程的Looper 
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
     
        final MessageQueue queue = me.mQueue;  //获取Looper对象中的消息队列
        ....
    
        for (;;) { //主线程开启无限循环模式
            Message msg = queue.next(); //获取队列中的下一条消息,可能会线程阻塞
            if (msg == null) { //没有消息,则退出循环,退出消息循环,那么你的程序也就可以退出了
                return;
            }
            ....
            //分发Message,msg.target 是一个Handler对象,哪个Handler把这个Message发到队列里,
            //这个Message会持有这个Handler的引用,并放到自己的target变量中,这样就可以回调我们重写
            //的handler的handleMessage方法。
            msg.target.dispatchMessage(msg);
            ....
            ....
            msg.recycleUnchecked();  //将Message回收到消息池,下次要用的时候不需要重新创建,obtain()就可以了。
        }
    }
    

    上面的代码,大家具体看下注释,这时候主线程(UI线程)执行到这一步就进入了死循环,不断地去拿消息队列里面的消息出来处理?那么问题来了
    1、UI线程一直在这个循环里跳不出来,主线程不会因为Looper.loop()里的死循环卡死吗,那还怎么执行其他的操作呢?

    • 在looper启动后,主线程上执行的任何代码都是被looper从消息队列里取出来执行的。也就是说主线程之后都是通过其他线程给它发消息来实现执行其他操作的。生命周期的回调也是如此的,系统服务ActivityManagerService通过Binder发送IPC调用给APP进程,App进程接到到调用后,通过App进程的Binder线程给主线程的消息队列插入一条消息来实现的。

    2、主线程是UI线程和用户交互的线程,优先级应该很高,主线程的死循环一直运行是不是会特别消耗CPU资源吗?App进程的其他线程怎么办?

    • 这基本是一个类似生产者消费者的模型,简单说如果在主线程的MessageQueue没有消息时,就会阻塞在loop的queue.next()方法里,这时候主线程会释放CPU资源进入休眠状态,直到有下个消息进来时候就会唤醒主线程,在2.2 版本以前,这套机制是用我们熟悉的线程的wait和notify 来实现的,之后的版本涉及到Linux pipe/epoll机制,通过往pipe管道写端写入数据来唤醒主线程工作。原理类似于I/O,读写是堵塞的,不占用CPU资源。

    所以上面代码的重点是queue.next() 的函数,其他的我们就不多说了,我们来看下queue.next()的源码(主要还是看注释):

    Message next() 
           
            final long ptr = mPtr;
            if (ptr == 0) {
                return null;
            }
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
    
            //nextPollTimeoutMillis 表示nativePollOnce方法需要等待nextPollTimeoutMillis 
            //才会返回
            int nextPollTimeoutMillis = 0;
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
                //读取消息,队里里没有消息有可能会堵塞,两种情况该方法才会返回(代码才能往下执行)
                //一种是等到有消息产生就会返回,
                //另一种是当等了nextPollTimeoutMillis时长后,nativePollOnce也会返回
                nativePollOnce(ptr, nextPollTimeoutMillis);
                //nativePollOnce 返回之后才能往下执行
                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) {
                        // 循环找到一条不是异步而且msg.target不为空的message
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                    if (msg != null) {
                        if (now < msg.when) {
                           // 虽然有消息,但是还没有到运行的时候,像我们经常用的postDelay,
                           //计算出离执行时间还有多久赋值给nextPollTimeoutMillis,
                           //表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            // 获取到消息
                            mBlocked = false;
                           //链表一些操作,获取msg并且删除该节点 
                            if (prevMsg != null) 
                                prevMsg.next = msg.next;
                            } else {
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            msg.markInUse();
                            //返回拿到的消息
                            return msg;
                        }
                    } else {
                        //没有消息,nextPollTimeoutMillis复位
                        nextPollTimeoutMillis = -1;
                    }
                    .....
                    .....
                  
        }
    

    nativePollOnce()很重要,是一个native的函数,在native做了大量的工作,主要涉及到epoll机制的处理(在没有消息处理时阻塞在管道的读端),具体关于native相关的源码本篇文章不涉及,感兴趣的同学可以网上找找,有不少分析得比较深。

    分析到这里,从应用启动创建Looper,创建消息队列,到进入loop方法执行无限循环中,那么这一块就告一段落了,主线程已经在死循环里轮询等待消息了,接下来我们就要再看看,系统是怎么发消息给主线程的,主线程是怎么处理这些个消息的?

    在准备启动一个Activity的时候,系统服务进程下的ActivityManagerService(简称AMS)线程会通过Binder发送IPC调用给APP进程,App进程接到到调用后,通过App进程下的Binder线程最终调用ActivityThread类下面的scheduleLaunchActivity方法来准备启动Activity,看下scheduleLaunchActivity方法:

    注:Binder线程:具体是指ApplicationThread,在App进程中接受系统进程传递过来的信息的线程(在主线程进入死循环之前创建了这个线程)。

      //这个方法不是在主线程调用,是Binder线程下调用的
      public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                    ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                    CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                    int procState, Bundle state, PersistableBundle persistentState,
                    List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                    boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
    
                updateProcessState(procState, false);
    
                ActivityClientRecord r = new ActivityClientRecord();
    
                r.token = token;
                r.ident = ident;
                r.intent = intent;
                r.referrer = referrer;
                r.voiceInteractor = voiceInteractor;
                r.activityInfo = info;
                r.compatInfo = compatInfo;
                r.state = state;
                r.persistentState = persistentState;
    
                r.pendingResults = pendingResults;
                r.pendingIntents = pendingNewIntents;
    
                r.startsNotResumed = notResumed;
                r.isForward = isForward;
    
                r.profilerInfo = profilerInfo;
    
                r.overrideConfig = overrideConfig;
                updatePendingConfiguration(curConfig);
    
                sendMessage(H.LAUNCH_ACTIVITY, r);
      }
    

    把启动一些信息封装成ActivityClientRecord之后,最后一句调用sendMessage(H.LAUNCH_ACTIVITY, r);我们接着往下看:

    private void sendMessage(int what, Object obj) {
            sendMessage(what, obj, 0, 0, false);
        }
    private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
             Message msg = Message.obtain();
            msg.what = what;
            msg.obj = obj;
            msg.arg1 = arg1;
            msg.arg2 = arg2;
            if (async) {
                msg.setAsynchronous(true);
            }
            mH.sendMessage(msg);
        }
    

    看到没有,最后启动Activity的信息都封装一个Message,但是这里有个问题了,之前在分析main函数的时候,完全没给出往主线程消息队列插入消息的方式,这里有了消息,但是怎么发到主线程的消息队列呢?最后一句又是重点mH.sendMessage(msg); mH 是什么呢?难道是Handler,我们来看下它是什么东西?
    我们看了下ActivityThread 的成员变量,发现一句初始化的代码

    final H mH = new H();
    

    继续往下看H是什么?

    public final class ActivityThread{
         ....
         final H mH = new H();
         ....
         private class H extends Handler {
         ....
         ....
         public void handleMessage(Message msg) {
                if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
                switch (msg.what) {
                    case LAUNCH_ACTIVITY: {
                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                        final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
    
                        r.packageInfo = getPackageInfoNoCheck(
                                r.activityInfo.applicationInfo, r.compatInfo);
                        handleLaunchActivity(r, null);
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    } break;
                    case RELAUNCH_ACTIVITY: {
                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
                        ActivityClientRecord r = (ActivityClientRecord)msg.obj;
                        handleRelaunchActivity(r);
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    } break;
                    case PAUSE_ACTIVITY:
                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                        handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
                                (msg.arg1&2) != 0);
                        maybeSnapshot();
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                        break;
                       .....
             }
             .....
             .....
         }
    }
    

    H 果不出其然是Handler,而且是ActivityThread的内部类,看了一下它的handleMessage 方法,LAUNCH_ACTIVITYPAUSE_ACTIVITYRESUME_ACTIVITY...好多好多,H 类帮我们处理了好多声明周期的事情。那么再回到mH.sendMessage(msg)这句代码上,在Binder线程执行mH.sendMessage(msg);,由主线程创建的Handler mH实例发送消息到主线程的消息队列里,消息队列从无到有,主线程堵塞被唤醒,主线程loop拿到消息,并回调mHhandleMessage 方法处理LAUNCH_ACTIVITY 等消息。从而实现Activity的启动。

    讲到这里,基本一个启动流程分析完了,大家可能比较想知道的是 mH.sendMessage(msg); 关于Hanlder是怎么把消息发到主线程的消息队列的?我们接下来就讲解下Handler,首先看下Handler的源码!我们先来看看我们经常用的Handler的无参构造函数,实际调用的是Handler(Callback callback, boolean async)构造函数(看注释)

     public Handler() {
            this(null, false);
     }
     public Handler(Callback callback, boolean async) {
            //不是static 发出可能内存泄露的警告!
            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());
                }
            }
            //获取当前线程的Looper,还记得前面讲过 Looper.myLooper()方法了吗?
            //Looper.myLooper()内部实现可以先简单理解成:map.get(Thread.currentThread()) 
            //获取当前线程的Looper
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                //当前线程不是Looper 线程,没有调用Looper.prepare()给线程创建Looper对象
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }
            //让Handler 持有当前线程消息队列的引用
            mQueue = mLooper.mQueue;
            //这些callback先不管,主要用于handler的消息发送的回调,优先级是比handlerMessage高,但是不常用
            mCallback = callback;
            mAsynchronous = async;
        }
    

    上面的代码说明了下面几个问题:
    1、Handler 对象在哪个线程下构建(Handler的构造函数在哪个线程下调用),那么Handler 就会持有这个线程的Looper引用和这个线程的消息队列的引用。因为持有这个线程的消息队列的引用,意味着这个Handler对象可以在任意其他线程给该线程的消息队列添加消息,也意味着Handler的handlerMessage 肯定也是在该线程执行的。
    2、如果该线程不是Looper线程,在这个线程new Handler 就会报错!
    3、上面两点综合说明了下面一段很常见的代码:把普通线程转成Looper线程的代码,为什么在Looper.prepare()Looper.loop()中间要创建Handler:

     class LooperThread extends Thread {
           //其他线程可以通过mHandler这个引用给该线程的消息队列添加消息
           public Handler mHandler;
           public void run() {
                Looper.prepare();
                //需要在线程进入死循环之前,创建一个Handler实例供外界线程给自己发消息
                mHandler = new Handler() {
                    public void handleMessage(Message msg) {
                        //Handler 对象在这个线程构建,那么handleMessage的方法就在这个线程执行
                    }
                };
                Looper.loop();
            }
        }
    

    那么接下来,我们接着往下看Handler的sendMessage(msg)方法,这个方法也是比较重要的,也比较常用,Handler 有很多sendXXXX开头的方法sendMessageAtTimesendEmptyMessageDelayedsendEmptyMessage等等,都是用来给消息队列添加消息的,那么这些方法最终都会调用enqueueMessage来实现消息进入队列:

     private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            //这句话很重要,让消息持有当前Handler的引用,在消息被Looper线程轮询到的时候
            //回调handler的handleMessage方法
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            //调用MessageQueue 的enqueueMessage 方法把消息放入队列
            return queue.MessageQueue(msg, uptimeMillis);
        }
    

    我们再来看下MessageQueue 的enqueueMessage(msg, uptimeMillis)方法:

        boolean enqueueMessage(Message msg, long when) {
            // msg 必须有target也就是必须有handler
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
            //插入消息队列的时候需要做同步,因为会有多个线程同时做往这个队列插入消息
            synchronized (this) {
                if (mQuitting) {
                    IllegalStateException e = new IllegalStateException(
                            msg.target + " sending message to a Handler on a dead thread");
                    Log.w(TAG, e.getMessage(), e);
                    msg.recycle();
                    return false;
                }
    
                msg.markInUse();
                //when 表示这个消息执行的时间,队列是按照消息执行时间排序的
                //如果handler 调用的是postDelay 那么when=SystemClock.uptimeMillis()+delayMillis
                msg.when = when;
                Message p = mMessages;
                boolean needWake;
                if (p == null || when == 0 || when < p.when) {
                    // p==null 表示当前消息队列没有消息
                    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;
                    //将消息放到队列的确切位置,队列是按照msg的when 排序的,链表操作自己看咯
                    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;
                }
    
                // 如果需要唤醒Looper线程,这里调用native的方法实现epoll机制唤醒线程,我们就不在深入探讨了
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    

    最后我们再看下Handler 的dispatchMessage方法,这个方法在Looper线程从消息队列拿出来的时候,通过msg.target.dispatchMessage(msg)调用的。

     /**
         * Handle system messages here.
         */
        public void dispatchMessage(Message msg) {
            //优先调用callback方法
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                //最后会回调我们重写的handleMessage 方法
                handleMessage(msg);
            }
        }
    

    到这里,整个Android的消息处理机制Java层内容基本讲解完毕了(欢迎关注我的简书:Kelin)

    注:【转载请注明,问题可留言,错误望指出,喜欢可打赏,博客持续更新,欢迎关注】

    相关文章

      网友评论

      • 左手的掌纹_f77e:很清晰,学习了
      • eirunye:思路非常清晰:+1:
      • 小鱼宠ZZ:MQ:消息生产者,消息消费者,消息中介
      • 6766c8f52d7d:收获巨大
      • 53955b5f064b:条理清晰,通俗易懂
      • 慢行的骑兵:昨天一遍,今天一遍,思路清晰很多,写的很用心的博客.
      • burro630:写的很好。受益匪浅。
        越发觉得平时写的代码都是在水面上打转,从未深入。等哪天想跳槽了,才发现自己真的很LOW。
      • hyc_f0ee:嗯,不错,csdn上这篇关于Handler我觉得写的也不错
        http://blog.csdn.net/hyc9200/article/details/78877979
      • Thomas__Yang:在介绍 enqueueMessage() 方法的时候有一个笔误:
        return queue.MessageQueue(msg, uptimeMillis);
        应该是:
        return queue.enqueueMessage(msg, uptimeMillis);
      • 李天迪装修美缝经历:学习了,希望可以与你互动~
      • 欧阳鹏:居然没有人评论这么赞的文章
      • 未子涵:1、Handler 对象在哪个线程下构建(Handler的构造函数在哪个线程下调用),那么Handler 就会持有这个线程的Looper引用和这个线程的消息队列的引用。因为持有这个线程的消息队列的引用,意味着这个Handler对象可以在任意其他线程给该线程的消息队列添加消息,也意味着Handler的handlerMessage 肯定也是在该线程执行的。
        对于楼主的上述说法,也理解了,但是楼主没有提到Handler的另一个构造方法:public Handler(Looper looper, Callback callback, boolean async),当我用这个构造方法构造Handler时,就产生了疑问,请看下面的代码:

        public abstract class MyHandlerThread implements Callback {
        protected final Handler handler;

        public MyHandlerThread() {
        android.os.HandlerThread t2 = new android.os.HandlerThread();
        t2.start();
        this.handler = new Handler(t2.getLooper(), this); // handler持有的是t2的Looper和MessageQueue
        }

        public final boolean handleMessage(Message msg) {
        // 处理this.handler发过来的消息
        switch(msg.what) {
        case MSG_TEST:
        ...
        break;
        default:
        break;
        }
        }
        }

        public class Sub extends MyHandlerThread {
        // 对外提供一个方法,用于发送消息
        public void sendMessage() {
        Message msg = new Message();
        msg.what = MSG_TEST;
        msg.obj = "test data";
        super.handler.sendMessage(msg);
        }
        }

        // 我在程序的某处, 如此调用
        Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
        Sub sub = new Sub();
        sub.sendMessage();
        }
        });
        t1.start;

        问题:
        看起来handler对象是在t1上创建的,但是handler应该是与t2绑定的,也就是handler发送的消息会被插入t2的消息队列,那么handlerMessage究竟是在t1上面处理消息还是在t2上处理消息?
        未子涵:@realxz 我也是这么认为的,就像楼主的结论,其实也是因为通过不带Looper参数的构造方法创建Handler时默认绑定了当前线程的Looper,所以才会有在哪个线程创建Handler,就在哪个线程上执行HandleMessage的说法,之前没怎么去关心Handler机制,现在才算是理解了这个只是点,谢谢
        realxz:@未子涵 Handler 的构建是需要 Looper 参与的,所以你可以理解为,handlerMessage 方法会在该 Looper 对象所在的线程执行。
        未子涵:我索性用这个代码测试了一下,发现handlerMessage是在t2线程下执行的,可是handler这个对象明明就是在t1这个线程里被创建的,所以我的理解是楼主的说法【Handler 对象在哪个线程下构建(Handler的构造函数在哪个线程下调用),那么Handler 就会持有这个线程的Looper引用和这个线程的消息队列的引用。因为持有这个线程的消息队列的引用,意味着这个Handler对象可以在任意其他线程给该线程的消息队列添加消息,也意味着Handler的handlerMessage 肯定也是在该线程执行的。】应该不适用于【Handler(Looper looper, Callback callback)】这个构造方法,一旦调用了这个构造方法,handleMessage就会在looper所在的线程执行,而非在创建handler的线程执行了。是不是?
      • 十旋转45度:“PM 的主要工作是设计产品,写需求文档,改需求,中途改需求,提测前改需求...” 戳中笑点了:stuck_out_tongue_winking_eye:
        Kelin:@十旋转45度 看着你就是被PM坑过的孩子:stuck_out_tongue_winking_eye:
      • trry_ing:子线程创建Looper后,进去无限循环,那子线程的其他代码怎么执行?
        冉桓彬:@trry_ing 需要在其他线程里面调用该子线程中创建的handler.post或者handler.send,你想想看,你在子线程里面创建handler,创建looper,如果你在该子线程里面调用handler.send,那么他的意义何在呢?
      • markRao:主线程会进入while(true){...}的代码段跳不出来?这个循环条件是for(;;){}吧
      • 看不清的未来怎么走:写的真棒!!!:+1:
      • 可乐_JS:好文章
      • 1e8c9072899d:handel的延时执行是怎么实现的呢
      • 68768b474bfc:懂了之后也要在面试时流畅地表达出来:joy:
      • 弓行佬: 期待更新!!!!:+1: :+1:
      • liangyifan:写得非常的好,你是让我真正弄懂这套机制。
      • jtsky: Message msg = queue.next(); //获取队列中的下一条消息,可能会线程阻塞
        if (msg == null) { //没有消息,则退出循环,退出消息循环,那么你的程序也就可以退出了
        return;
        }
        请问什么情况下msg 会为null呢,是不是可以这样理解:当进程主动或被动结束的时候,系统会发送一条null的Message进messageQueue?
      • Vander丶:看博主文章之前,对Handler机制有一点清晰的认知,读完之后,对于主线程Looper无限循环阻塞的问题,还是有点不解.
        通过看博主的文章,了解到Activity的生命周期都是通过H来发送消息来触发Actvitiy的生命周期,这一点还好理解,那么问题 类似于View 的绘制.测量的方法 也是通过 H来通知的么 .
        Vander丶:@Kelin 因为setContentView 在onCreate()方法归根结底 还是通过H来发送通知来调用的.所以也可以理解成通过消息来让主线程更新.
        Vander丶:谢谢博主,我昨天查了一下当时就是想知道那些绘制相关的方法,是如何通过消息来更新让主线程的.但是遗憾的是 我查了一下,也是没查到,但是我了解到setContentView这个方法传入布局ID之后才进行绘制,然后就自然而然的理解这个问题.最后还是谢谢博主.
        Kelin:@困难模式丶 主线程在死循环中,UI相关的都是通过消息来让主线程更新的
      • 被风扬起的沙: //调用MessageQueue 的enqueueMessage 方法把消息放入队列
        return queue.MessageQueue(msg, uptimeMillis);

        代码里面的方法错了,应该是:
        return queue.enqueueMessage(msg, uptimeMillis);
        kirito0424:我也发现了,就感觉这函数名怎么看着怪怪的
      • 被风扬起的沙: if (msg == null) { //没有消息,则退出循环,退出消息循环,那么你的程序也就可以退出了
        return;
        }
        这个地方只是阻塞吧,程序不会退出:smile:
        Kelin:@被风扬起的沙 looper循环只要退出,意味着程序要退出,更新UI那些操作也都是发消息给主线程,如果 looper循环都退出了就更新不了UI,msg == null 应该是个标志,用来退出(for;;)循环体
        被风扬起的沙:@Kelin 没消息的时候不是会用那个 epoll 机制,CPU 去执行别的了吗?程序应该没退出吧
        Kelin:if (msg == null) { /
        return;
        }
        这句话执行了,意味着UI主线程退出了loop无限循环(for (;;) ),也就意味着App要退出了
      • 被风扬起的沙:普通线程转为 loop线程的那个图,中间箭头的 Looper.prepare( )应该改为Looper.prepareMainLooper( )吧:smile:
        被风扬起的沙:两个方法的区别应该是 prepared( )方法里面为 true 或 false 的区别,跟着源码找了找最终找到MessageQueue 里面的
        void quit(boolean safe) {
        if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
        }
        ......
        }
        这里的意思是不是主线程的 MessageQueue 不能删除或者离开,我们自定义的线程就有这种功能啊? (请无视我的英语翻译:scream:
        Kelin:@被风扬起的沙 是用 Looper.prepare( ),不是Looper.prepareMainLooper( ),prepareMainLooper是专门给UI主线程用的,我们自己自定义的线程 用 Looper.prepare( )就行了
      • CappuccinoBx:在你第一张大图下的Handler的介绍中里面有两句代码是重复的。

        原文:
        但是Google 为了统一添加消息和消息的回调处理,又专门构建了Handler类,你只要在主线程构建Handler类,那么这个Handler实例就获取主线程MessageQueue实例的引用(获取方式mLooper = Looper.myLooper();mQueue = mLooper.mQueue;)
        Kelin:两句代码是重复的,是我怕别人看不懂,故意又多写了一遍获取mLooper的,啰嗦了一些。:smile:
      • OceanJS:看了好长时间,不是太懂,不过还是挺有收获的,以后得读几遍
      • 有梦想便可飞翔:博主的每篇都非常用心,写的非常有条理,而且剖析透彻,主要是不讲废话,感谢博主。
      • 4f6b638e3d98:每次执行 nativePollOnce(ptr, nextPollTimeoutMillis);时,nextPollTimeoutMillis始终为0,而nextPollTimeoutMillis却是在nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE)赋值的,我想请问,它是怎么做到消息延迟的,逻辑不通呀。
        4f6b638e3d98:@Kelin 感谢作者:+1: 明白了 多谢!
        Kelin:nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE) 执行到这句话的时候不会拿到这个消息,因为在循环体里面,所以又会重新去执行nativePollOnce(ptr, nextPollTimeoutMillis),当nextPollTimeoutMillis>0 的时候 nativePollOnce经过nextPollTimeoutMillis后就返回了
      • 4f6b638e3d98:首先感谢楼主无私的奉献,只不过有个问题仍然想请教您:在子线程中创建消息循环的时候,为什么在Looper.prepare()和Looper.loop()中间要创建Handler,而为什么不能把创建handler放在Loop.loop之后呢。
        冉桓彬:@xiaoqingxu0502 你问问题之前其实可以稍微看一下源码的,构造handler时通过threadlocal拿到looper。
        4f6b638e3d98:@Kelin :smile: 感谢,非常感谢,loop.loop之后的代码将永远不被执行。
        Kelin:@android_coding Loop.loop就进入死循环了,你可以看Loop.loop方法的源码:smiley:
      • Maat红飞:我经常看博客,毫无疑问,你的博客是最棒的,你要是出书肯定大卖
        被风扬起的沙:@Kelin 老司机感觉好久没过来逛逛了啊:smile:
        Kelin:@Maat红飞 之前有人找过出书,不过感觉自己积累还不够,现在出书压力应该会很大,多积累些再考虑下呢:smile:
        Maat红飞:@Maat红飞 不过现在出书错过了最佳时机
      • CappuccinoBx:Handler介绍那边的代码有误,两段代码是重复的
      • 被风扬起的沙:ActivityThread thread = new ActivityThread() 这句代码执行的时候,final H mH = new H(); 是在这里面初始化的吧
      • 被风扬起的沙:我们再来看下MessageQueue 的MessageQueue(msg, uptimeMillis)方法: 方法名字错了 :smiley:
        Kelin:@被风扬起的沙 3Q 已修复 :smiley:
      • 被风扬起的沙:便会创建一个Binder线程(具体是指ApplicationThread,该Binder线程会通过想 Handler将Message发送给主线程,之后讲)。

        这个ApplicationThread不是主线程吗?Binder线程是系统给创建的? :stuck_out_tongue:
        永正:深夜看完了,真的是受益匪浅,按着文字思路,整个理清了之前的疑惑了,给力啊:+1:
        被风扬起的沙:@Kelin 便会创建一个Binder线程(具体是指ApplicationThread ,这句话是不是表述有错误啊
        Kelin:@被风扬起的沙 ApplicationThread 不是线程,你看ApplicationThread源码就知道了!Binder线程是系统给创建的 。主线程是指UI线程。
      • f3e46c935ab9:请教一下动图是怎么做的?
        Kelin:@4Living Keynote 动画,播放的时候录制 :smile:
      • 32135776c20b:分析的非常全面透彻,特别是 Message msg = queue.next() 为什么不会 引起主线程的ANR
        欢金子:为什么啊,ANR时间怎么算的
        Kelin:@晓游 3Q :smile:
      • 仰恩:我想问下,这个机制为什么要了解的这么透,普通会用不就可以了吗,为什么面试时常会问
      • houliang:写的棒棒的,不错,赞一个
      • 被风扬起的沙:我们之前提到主线程最后会进入无限循环当中,如果没有在进入死循环之前创建其他线程,那么待会谁会给主线程发消息呢?

        这个地方先把主线程循环起来,然后在新建其他的线程给他发消息不行吗? :stuck_out_tongue_winking_eye:
        被风扬起的沙:@Kelin 看分析应该是在looper循环之前,建了一个handler用来接收跟处理消息。系统的Binder线程生成在主线程死循环之前
        被风扬起的沙:@Kelin :scream_cat: :scream_cat: :scream_cat: 对这个线程还有点小迷糊
        Kelin:@被风扬起的沙 App 进程启动后只有UI主线程。 主线程进入死循环了,你在哪个线程新建线程呢? :stuck_out_tongue_closed_eyes:
      • 被风扬起的沙:你只要在主线程构建Handler类,那么这个Handler实例就获取主线程MessageQueue实例的引用(获取方式mLooper = Looper.myLooper();mQueue = mLooper.mQueue;)

        这个地方handler的获取MessageQueue的方法写错了 :stuck_out_tongue_winking_eye:
        被风扬起的沙:@Kelin 是可以拿到的(在主线程下mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),但是Google 为了统一添加消息和消息的回调处理,又专门构建了Handler类,你只要在主线程构建Handler类,那么这个Handler实例就获取主线程MessageQueue实例的引用(获取方式mLooper = Looper.myLooper();mQueue = mLooper.mQueue;)

        感觉有点小重复,以为你会写handler的那个方法呢,方法里面应该也是这么执行的吧
        Kelin:@被风扬起的沙 应该没有错吧,Handler 的构造函数就是里面就有mLooper = Looper.myLooper();mQueue = mLooper.mQueue 的代码,这样Handler 的成员变量mQueue就指向了Looper线程的消息队列了,你再看看源码哈 :stuck_out_tongue_closed_eyes:
      • 13itch:十分感谢,细细的看了一遍,未得要领,只知大概,应该是我的基础比较薄弱,抽空静下心来再仔细看一遍,应该会有很多的收获,再次感谢博主的分享
      • ae12:只是从上到下,一知半解地撸了一遍,用时2个番茄钟,博主写了好几个小时吧?辛苦了。
        Kelin:@Liqing_1938 恩恩 写得比较久,写得文章都比较长,细心点看,会有收获的 :blush:
      • 石器时代小古董:总结的非常好 :+1: :+1:
        石器时代小古董:每次忘了都来温习一下:+1: 。这篇文章写的真的太棒了。今天总结了一下自己的观点:android的消息机制就是开启一个循环不断的处理接收到的消息,之所以能让其他线程向主线程发送消息是因为通过handler拿到主线程的ThreadLocal维护的MessageQueue,使其他线程可以操作到主线程的消息队列。
        石器时代小古董:@Kelin 学习了 :pray: 感谢感谢
        Kelin:@疯狂橘子侠 喜欢就好 :smile:
      • 3601522abbe8:感谢博主

      本文标题:Android 消息处理机制(Looper、Handler、Me

      本文链接:https://www.haomeiwen.com/subject/qwipsttx.html