Android-消息机制(Handler机制)浅析

简介

Android消息机制主要就是指Handler的运行机制;
借用网络的图片
在这里插入图片描述
图解:
1.以Handler的sendMessage方法为例,当发送一个消息后,会将此消息加入消息队列MessageQueue中。
2.Looper负责去遍历消息队列并且将队列中的消息分发给对应的Handler进行处理。
3.在Handler的handleMessage方法中处理该消息,这就完成了一个消息的发送和处理过程。
这里从图中可以看到参与消息处理有四个对象,它们分别是 Handler, Message, MessageQueue,Looper。

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

源码探索

App启动

ActivityThread 类

ActivityThread类中mian方法中,注意看Looper.prepareMainLooper();的调用,会为当前这个App创建出全局唯一的Looper对象;

package android.app;
......

public final class ActivityThread extends ClientTransactionHandler {
	......
	......
	 public static void main(String[] args) {
		......
		......
		//创建出App全局唯一的Looper对象
        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

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

}

Looper 类

当ActivityThread类的main方法中调用了Looper.prepareMainLooper的时候,会先创建出当前主线程唯一的Looper对象,并调用ThreadLocal的set方法将这个Looper对象传了进去,在创建Looper对象的时候,构造方法中又创建了当前这个Looper中的唯一的消息队列MessageQueue和获取到当前线程保存到Looper中唯一的mThread线程对象中;

package android.os;
......

public final class Looper {
	......
	  static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
	......
	final MessageQueue mQueue;
    final Thread mThread;
	......
	//构造方法
	//在这个构造方法中,有创建了当前Looper唯一的消息队列MessageQueue和将当前线程复制到了全局的mThread线程对象中
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
	......
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //这里为我们创建了全局唯一的Looper对象
        sThreadLocal.set(new Looper(quitAllowed));
    }
	......
}

ThreadLocal类

接着上面,创建主线程唯一的Looper对象之后调用了ThreadLocal类中的这个set方法,将Looper对象传了进来;
传入进来的时候,因为当前是主线程第一次创建Looper对象,并set进来,所以这类获取ThreadLocalMap的时候是个null,然后走到下面的else当中,去给当前线程Thread创建ThreadLocalMap并保存到threadLocals 这个全局变量中;
ThreadLocalMap是键值对的方式保存,key是当前线程,value是当前线程的Looper对象;

public class ThreadLocal<T> {
	......
    public void set(T value) {
    	//获取到当前线程
        Thread t = Thread.currentThread();
        //通过当前线程去获取ThreadLocalMap对象
        //这里的ThreadLocalMap拿的是当前这个Thread线程中的变量ThreadLocalMap 对象  而一路跟进来,这里的线程肯定是主线程吧
        //这里是第一次进来,所以当前这个主线程肯定是没有ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
        //所以当前线程会走入这个创建ThreadLocalMap的方法中,并且保存到当前这个线程中的threadLocals变量中
            createMap(t, value);
    }
    ......
    void createMap(Thread t, T firstValue) {
    	//创建ThreadLocalMap并赋值保存到当前线程的threadLocals 对象中;
    	//这个时候全局唯一的主线程中的这个threadLocals 对象就有了吧
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
	......
}

流程图

在这里插入图片描述

小结

相信大家看完这里,就能明白ActivityThread类main方法中会给我们创建出全局唯一的主线程的Looper对象,和Looper对象中会给我们创建出当前主线程Looper对象全局唯一的消息队列以及赋值了当前线程,也可以说是当前主线程的消息队列;

主线程中创建Handler

   private Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };

我们跟进这个Handler类里面去看看

创建Handler的时候,通过Looper.myLooper去获取了全局唯一主线程的Looper对象;这里获取到的Looper对象是不为空的,为什么不为空,请往下翻,下面有解释;
然后将获取的主线程Looper的消息队列变量赋值到了当前这个Handler的消息队列变量上;赋值回调Callback;
也就是说当前这个Handler是有个消息队列的,这个消息队列就是主线程唯一的消息队列;

package android.os;
	public class Handler {
	   public Handler() {
        	this(null, false);
    	}
    	public Handler(@Nullable Callback callback, boolean async) {
       ......
       //这类是获取当前线程(因为是主线程创建的Handler,所以这里是获取了全局唯一的主线程Looper对象)的Looper对象
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        //拿到Looper对象的消息队列,赋值到了当前Handler的消息队列变量上面;
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
	}

Looper类中的myLooper方法

这方法中调用了ThreadLocal的get方法

	......
	static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
	......
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

ThreadLocal的get方法

可以看到这里是获取了一遍当前线程;
通过当前线程去获取了ThreadLocalMap 对象;
记得ActivityThread类的main方法调用吧?
主线程的ThreadLocalMap 对象是存在了吧?已经创建并赋值到了当前线程的threadLocals变量上面了吧

	public T get() {
		//获取当前线程,因为是在主线程中创建的Handler对象;
		//所以这类是拿到主线程
        Thread t = Thread.currentThread();
        //通过主线程获取到主线程的ThreadLocalMap对象
        //因为主线程的ThreadLocalMap对象已经在ActivityThread的main方法中创建了,所以这里不为空
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	//进入到这里
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

流程图

在这里插入图片描述

发送消息handler.sendMessage(msg);

我们跟进Handler中的sendMessage方法中去

注意:sendMessage这个方法最终调用的是sendMessageDelayed这个延时的方法,但是默认传了个0秒延时;
然后调入到sendMessageAtTime这个方法中,将全局唯一的消息队列赋值到了局部的消息队列变量中,然后调用enqueueMessage这个方法;
enqueueMessage方法中:
1、将当前Handler本身保存到了传入进来的Message的target变量上面,这时候这个message就持有了当前这个Handler了;
2、最后调用的是消息队列的enqueueMessage方法,将持有当前Handler引用的消息队列,以及更新的时间传入到了这个方法中
接着往下看↓

package android.os;
	public class Handler {
    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }
        public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
      public boolean sendMessageAtTime(@NonNull 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(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        //将当前Handler本身保存到了传入进来的Message的target变量上面,这时候这个message就持有了当前这个Handler了
        msg.target = this;
		
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //最后调用的是消息队列的enqueueMessage方法,将持有当前Handler引用的消息队列,以及更新的时间传入到了这个方法中
        return queue.enqueueMessage(msg, uptimeMillis);
    }
}

我们来看看MessageQueue里面的enqueueMessage方法

进来一条消息消息赋值到全局的Message上;
这方法中会有唤醒操作,继续往后看;
Message中有个对象池的概念:Message类使用了一个链表来实现对象池,最大支持50条消息对象;
Message类中有一个常量:private static final int MAX_POOL_SIZE = 50; 这是对象池中支持的最大消息对象;
里面有一个全局静态的sPoolSize指针,这个对象永远都是指向的是当前的第一个;

public final class MessageQueue {
	......
	Message mMessages;
	......

	boolean enqueueMessage(Message msg, long when) {
			......
			......
	        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();
	            msg.when = when;
	            Message p = mMessages;
	            boolean needWake;
	            if (p == null || when == 0 || when < p.when) {
	                msg.next = p;
	                //将持有Handler引用的消息Message赋值到了全局变量中
	                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;
	    }
}

流程图

在这里插入图片描述

处理消息

我们回到App启动的ActivityThread 类中的main方法中

Looper.loop(): 这类调用了Looper.loop()方法,这个方法是开启当前全局唯一的主线程Looper对象的消息循环;
往下看,我们跟进这个Looper.loop方法中去,往下看↓

package android.app;
......

public final class ActivityThread extends ClientTransactionHandler {
	......
	......
	 public static void main(String[] args) {
		......
		......
		//创建出App全局唯一的Looper对象
        Looper.prepareMainLooper();
		......
        Looper.loop();

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

}

Looper类中的loop方法

这类我们只看主要的调用了,省略掉其他的一些判断和啥的…
1、myLooper方法我们上面已经解释了吧?就是获取当前的Looper对象;
2、拿到这个Looper对象中的消息队列;
3、无限循环,去消息队列中取消息,然后调用msg.target.dispatchMessage(msg);去分发消息;
是否还记得target这个值???
这个是当前这个消息Message对象中存放的Handler引用;
这里就是说,最后通过调用当前handler吧消息通过dispatchMessage方法分发出去了。

 public static void loop() {
 		//获取当前线程的Looper对象
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //获取到Looper对象中的消息队列
        final MessageQueue queue = me.mQueue;
        //开启无限循环
        for (;;) {
        	//从消息队列中取出下一条消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
           ......
		   ......
		   //调用msg对象中的handler的dispatchMessage方法把消息分发出去;
           msg.target.dispatchMessage(msg);
        }
    }

来看看这个Handler的dispatchMessage方法

Loop方法中,无限循环,把消息传入到这个方法中;
判断了下消息中是否有回调,如果没有,调用handleCallback方法;
如果有回调,在判断了下当前Handler中是否有回调接口,如果有的话,直接用Handler的回调接口,把消息分发出去,如果没有的话,调用handleMessage方法,把消息分发出去;
还记得handleMessage这个方法不?我们在创建Handler的时候,会去重写的handleMessage方法,用来处理消息的;

    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //创建handler 重写的handleMessage方法
            handleMessage(msg);
        }
    }
        private static void handleCallback(Message message) {
        message.callback.run();
    }
        public void handleMessage(@NonNull Message msg) {
    }

图解

在这里插入图片描述
在这里插入图片描述

消息阻塞和延时

Looper 的阻塞主要是靠 MessageQueue 来实现的,在next()@MessageQuese 进行阻塞,在 enqueueMessage()@MessageQueue 进行唤醒。主要依赖 native 层的 Looper 依靠 epoll 机制进行的。

消息阻塞

来看看这个消息队列MessageQueue的next方法

nativePollOnce(ptr, nextPollTimeoutMillis) 这是调入到这个方法,注意看这类是一个native层的方法…阻塞在这个方法上面,等待唤醒;
native层中也是一个死循环;
被唤醒之后,才会接着下面的代码继续往下走;
阻塞和延时,主要是next()中nativePollOnce(ptr, nextPollTimeoutMillis)调用naive方法操作管道,由nextPollTimeoutMillis决定是否需要阻塞nextPollTimeoutMillis为0的时候表示不阻塞,为-1的时候表示一直阻塞直到被唤醒,其他时间表示延时。
那为什么主线程这类阻塞了,而没造成app的卡死ANR?
有人的解释是:native层会帮我们做一些资源的释放;

public final class MessageQueue {
 ......
 private native void nativePollOnce(long ptr, int timeoutMillis);
 ......

 Message next() {
		......
		......
		int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
			//消息的阻塞	nextPollTimeoutMillis时间
            nativePollOnce(ptr, nextPollTimeoutMillis);
			......
			......
			......
     
    }
}

唤醒

唤醒也同样是在消息队列MessageQueue中,这是在enqueueMessage方法

同样是调用的native蹭的nativeWake方法做唤醒操作

private native static void nativeWake(long ptr);

boolean enqueueMessage(Message msg, long when) {
		......
		......
        synchronized (this) {
        	......
        	......
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

对Handler中的Native层调用需要了解的请移步
转载-Handler消息机制(native层)

阻塞、唤醒的流程图

在这里插入图片描述

问题

为什么不能在子线程中更新UI,根本原因是什么?

在这里插入图片描述
mThread是UI线程,这里会检查当前线程是不是UI线程。那么为什么onCreate里面没有进行这个检查呢。这个问题原因出现在Activity的生命周期中,在onCreate方法中,UI处于创建过程,对用户来说界面还不可视,直到onStart方法后界面可视了,再到onResume方法后界面可以交互。从某种程度来讲,在onCreate方法中不能算是更新UI,只能说是配置UI,或者是设置UI的属性。这个时候不会调用到ViewRootImpl.checkThread(),因为ViewRootImpl没被创建。而在onResume方法后,ViewRootImpl才被创建。这个时候去交互界面才算是更新UI。
setContentView只是建立了View树,并没有进行渲染工作(其实真正的渲染工作是在
onResume之后)。也正是建立了View树,因此我们可以通过findViewById()来获取到View对象,但是由于并没有进行渲染视图的工作,也就是没有执行ViewRootImpl.performTransversal。同样View中也不会执行onMeasure(),如果在onResume()方法里直接获取View.getHeight()/View.getWidth()得到的结果总是0。

Handler内存泄露产生的原因?

Handler的handleMessage方法中(或者run方法)处理消息,如果这个时候一个来了一条延时消息,会一直保存在主线程的消息队列里面,并且会影响系统对Activity的回收,造成内存泄露

感谢各位童鞋观看到最后,一起共勉加油,大神勿喷,谢谢~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值