Android消息循环机制浅析

作者: niknowzcd | 来源:发表于2018-10-10 23:01 被阅读81次

    Android消息机制概略

    从根本上来说Android系统同windows系统一样,也属于消息驱动型系统 消息驱动型系统会有以下4大要素

    • 接收消息的“消息队列”
    • 阻塞式地从消息队列中接收消息并进行处理的“线程”
    • 可发送的“消息的格式”
    • 消息发送函数

    Android系统与之对应的实现就是 (MessageQueue,Looper,Handler)

    • 接收消息的“消息队列” ——【MessageQueue】主要功能是投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);
    • 阻塞式地从消息队列中接收消息并进行处理的“线程” ——【Thread+Looper】
    • 可发送的“消息的格式” ——【Message】
    • “消息发送函数”——【Handler的post和sendMessage】

    一个Looper类似一个消息泵。它本身是一个死循环,不断地从MessageQueue中提取Message或者Runnable。而Handler可以看做是一个Looper的暴露接口,向外部暴露一些事件,并暴露sendMessage()post()函数

    Android消息机制的大致流程

    因为Android的消息模型是贯穿整个App的生命周期的,所以主线程的消息模型在App启动时就被创建。

    Android app的入口类是ActivityThread,这个类中有个main方法,这个方法是整个app的入口,

    其中有下面一段代码

    //创建一个消息循环
    public static void main(String[] args) {
        ......
        Looper.prepareMainLooper();  //主线程的Looper(),也是app应用主主要的消息机制的消息管理者
        ......
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    

    这段代码最后是一个异常,如果代码运行到哪里,就会导致app崩溃,既然我们的app没有崩溃,说明代码中以 hi没有执行到那里,怎么才能使那段代码不被执行呢?通过死循环

    其中Looper.loop()
    public static void loop() {
        ......
        for(;;){
          ......
        }
        ......
    }
    

    looper不停的去轮询消息队列,当有消息时更新,没有消息时休眠.这是我们理想中的状态,

    那么如果来控制这个循环状态呢?

    因为Android系统是基于Linux系统的,自然会汲取Linux成熟的一些设计。其中消息循环就是借助Linux的epoll机制。

    消息循环中有两个重要的native方法 nativePollOnce消息挂起和nativeWake消息唤醒。

    Android消息循环机制的主流程简略概括如下

    主线程
    1. App进程创建
    2. 初始化一个消息队列
    3. 主线程死循环读取消息队列
    4. 消息处理完之后调用nativePollOnce挂起线程
    其他线程
    1. 引用主线程的消息队列,添加消息到队列里。(由于做了线程同步这时候主线程的队列数据已经和子线程一致了)
    2. 调用nativeWake唤醒主线程
    主线程
    1. 主线程nativePollOnce挂起取消
    2. 处理消息
    3. 消息处理完消息,nativePollOnce继续挂起等待新消息

    ....

    细说Android消息机制的流程

    上面对Android的消息机制做了一个简单的介绍,接下来跟着源码再来仔细的分析一下消息机制

    先展示一个典型的关于Handler/Looper的线程

    class LooperThread extends Thread {
        public Handler mHandler;
    
        public void run() {
            Looper.prepare();  
    
            mHandler = new Handler() {  
                public void handleMessage(Message msg) {
                    //TODO 定义消息处理逻辑. 
                }
            };
    
            Looper.loop();  
        }
    }
    //只有在子线程需要  Looper.prepare()和 Looper.loop();  
    //因为主线程在初始化的时候已经创建了一个Looper对象了
    

    Looper

    prepare()

    对于无参的情况,默认调用prepare(true),表示的是这个Looper允许退出,而对于false的情况则表示当前Looper不允许退出,主线程的Looper就是不允许退出的。

    private static void prepare(boolean quitAllowed) {
        //每个线程只允许执行一次该方法,第二次执行时线程的TLS已有数据,则会抛出异常。
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //创建Looper对象,并保存到当前线程的TLS区域
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    //这个方法是主线程Looper初始化的代码,入口在ActivityThread中,即App初始化时就会被调用
    public static void prepareMainLooper() {
        prepare(false); //设置不允许退出的Looper
        synchronized (Looper.class) {
            //将当前的Looper保存为主Looper,每个线程只允许执行一次。
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    

    每个线程只能有一个Looper对象,并且与线程(Thread)绑定,在prepare方法中Looper会新建了Looper对象和MessageQueue对象

    Looper 中有一个 ThreadLocal 类型的 sThreadLocal静态字段,Looper通过它的 getset 方法来赋值和取值。

    由于 ThreadLocal是与线程绑定的,所以我们只要把 LooperThreadLocal 绑定了,那 LooperThread 也就关联上了.ThreadLocal的分析可以看文章下面的知识补充

    loop()

    public static void loop() {
        final Looper me = myLooper();  //获取TLS存储的Looper对象 
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;  //获取Looper对象中的消息队列
    
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
    
        for (;;) { //死循环
            Message msg = queue.next(); //可能会阻塞 
            if (msg == null) { //没有消息,则退出循环
                return;
            }
            //默认为null,可通过setMessageLogging()方法来指定输出,用于debug功能
            Printer logging = me.mLogging;  
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            //msg.target是一个Handler对象,回调Handler.dispatchMessage(msg)
            msg.target.dispatchMessage(msg); 
            ...
            msg.recycleUnchecked();  //将Message放入消息池,用以复用
        }
    }
    
    1. 读取MessageQueue的下一条Message;
    2. 把Message分发给相应的Handler
    3. 再把分发后的Message回收到消息池,以便重复利用

    quit()

    public void quit() {
        mQueue.quit(false); //消息移除
    }
    
    public void quitSafely() {
        mQueue.quit(true); //安全地消息移除
    }
    
    ----------------------------------------MessageQueue#quit();-----------------------------
    
    void quit(boolean safe) {
            // 主线程的MessageQueue.quit()行为会抛出异常,
            if (!mQuitAllowed) {
                throw new IllegalStateException("Main thread not allowed to quit.");
            }
            synchronized (this) {
                if (mQuitting) { //防止多次执行退出操作
                    return;
                }
                mQuitting = true;
                if (safe) {
                    removeAllFutureMessagesLocked(); //移除尚未触发的所有消息
                } else {
                    removeAllMessagesLocked(); //移除所有的消息
                }
                 // We can assume mPtr != 0 because mQuitting was previously false.
                nativeWake(mPtr);
            }
        }
    

    其他方法

    Looper.myLooper()  //获取当前线程
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    
    Looper.getMainLooper() //获取主线程
        
    //判断当前线程是不是主线程
    Looper.myLooper() == Looper.getMainLooper()
    Looper.getMainLooper().getThread() == Thread.currentThread()
    Looper.getMainLooper().isCurrentThread()
    

    Handler

    Handler的主要作用是什么呢?

    • 跨进程通信
    • 跨进程更新UI
    • looper messageQueue一起构建Android的消息循环模型

    首先创建Handler

    无参构造,

    public Handler() {
        this(null, false);
    }
    
    public Handler(Callback callback, boolean async) {
        //匿名类、内部类或本地类都必须申明为static,否则会警告可能出现内存泄露,
        //这个警告Android studio会提示给开发者
        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());
            }
        }
        //Handler与Looper的绑定过程,如果在绑定之前没有调用Looper.prepare()方法的话就会报错
        mLooper = Looper.myLooper();  //从当前线程的TLS中获取Looper对象
        if (mLooper == null) {
            throw new RuntimeException("");
        }
        mQueue = mLooper.mQueue; //消息队列,来自Looper对象
        mCallback = callback;  //回调方法
        mAsynchronous = async; //设置消息是否为异步处理方式
    }
    

    对于Handler的无参构造方法,默认采用当前线程TLS中的Looper对象,并且callback回调方法为null,且消息为同步处理方式。只要执行的Looper.prepare()方法,那么便可以获取有效的Looper对象。

    有参构造

    //Handler与传入的Looper进行绑定
    public Handler(Looper looper) {
        this(looper, null, false);
    }
    
    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    

    Handler类在构造方法中,可指定Looper,Callback回调方法以及消息的处理方式(同步或异步)

    消息分发

    在Looper.loop()中,当发现有消息时,调用消息的目标handler,执行dispatchMessage()方法来分发消息。

    public void dispatchMessage(Message msg) {
        //调用Handler.post系列方法时,会给Message的callback赋值,
        //所以在分发消息的时候会调用handleCallback(msg)
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                //当Handler设置了回调对象Callback变量时,回调方法handleMessage();
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //Handler自身的回调方法handleMessage() 需子类实现相应逻辑
            handleMessage(msg);
        }
    }
    

    消息发送

    查看Handler的所有的消息发送入口,包括post()系列方法和sendMessage()系列方法,你会发送最终都会到MessageQueue.enqueueMessage();

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //设置Message的消费Handler为当前Handler
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis); 
    }
    //uptimeMillis = SystemClock.uptimeMillis() + delayMillis
    //SystemClock.uptimeMillis() 代表的是自系统启动开始从0开始的到调用该方法时相差的毫秒数
    //比起System.currentTimeMillis()更加可靠,因为System.currentTimeMillis()是跟系统时间强关联的,
    //修改了系统时间,System.currentTimeMillis()就会发生变化.
    

    消息回收

    removeMessages

    public final void removeMessages(int what) {
        mQueue.removeMessages(this, what, null); 
    }
    

    removeCallbacksAndMessages

     public final void removeCallbacksAndMessages(Object token) {
         mQueue.removeCallbacksAndMessages(this, token);
     }
    

    实质上都是调用的MessageQueue的方法来达到目的的

    MessageQueue

    MessageQueue是消息机制的Java层和C++层的连接纽带,大部分核心方法都交给native层来处理,其中比较重要两个方法

    private native void nativePollOnce(long ptr, int timeoutMillis);  //阻塞loop循环
    private native static void nativeWake(long ptr);                  //唤醒loop循环
    

    next()

    提取下一条message,如果有

    enqueueMessage

    按照参数when来添加消息刀队列中

    removeMessages

    移除消息

    篇幅有限,MessageQueue的各个方法就不详细说明

    消息池

    在代码中,可能经常看到recycle()方法,咋一看,可能是在做虚拟机的gc()相关的工作,其实不然,这是用于把消息加入到消息池的作用。这样的好处是,当消息池不为空时,可以直接从消息池中获取Message对象,而不是直接创建,提高效率。

    静态变量sPool的数据类型为Message,通过next成员变量,维护一个消息池;静态变量MAX_POOL_SIZE代表消息池的可用大小;消息池的默认大小为50。

    其他补充

    一个线程中可以有多个Handler,但是只能有一个Looper

    MessageQueue是有序的

    子线程可以创建Handler对象

    不可以在子线程中直接调用 Handler 的无参构造方法,因为 Handler 在创建时必须要绑定一个 Looper 对象,有两种方法绑定

    • 先调用 Looper.prepare() 在当前线程初始化一个 Looper
    Looper.prepare();
    Handler handler = new Handler();
    // ....
    // 这一步必须
    Looper.loop();
    
    • 通过构造方法传入一个 Looper
    Looper looper = .....;  //这里可以传入主线程的looper
    Handler handler = new Handler(looper);
    

    知识补充

    ThreadLocal 浅析

    ThreadLocal是一个线程内部的数据存储类Thread Local Storage,简称为TLS),作用域限制在线程之内.即A线程存储的数据,不能被B线程使用。

    ThreadLocal在Android源码中的表现主要在LooperActivityThread以及AMS.

    使用示例
    public class ThreadLocalTest extends AppCompatActivity {
        private static final String TAG = "ThreadLocalTest";
        private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<Boolean>();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            
            mBooleanThreadLocal.set(true);
            Log.d(TAG, "[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
     
            new Thread("Thread#1") {
                @Override
                public void run() {
                    mBooleanThreadLocal.set(false);
                    Log.d(TAG, "[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
                };
            }.start();
     
            new Thread("Thread#2") {
                @Override
                public void run() {
                    Log.d(TAG, "[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
                };
            }.start();
        }
    }
    
    //输出结果
    //[Thread#main]mBooleanThreadLocal=true
    //[Thread#1]mBooleanThreadLocal=false
    //[Thread#2]mBooleanThreadLocal=null
    

    从结果来看,都是调用的mBooleanThreadLocal.get()方法,得到的值却不一样,这时因为ThreadLocal会在各自的线程分别创建一个数据存储空间(ThreadLocalMap),分别保存各个线程中的数据.

    接下里跟着源码看下ThreadLocal的实现

    ThreadLocal的public方法,只有三个 set, get ,remove

    ThreadLocal#set

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    1. 获取当前线程
    2. 获取当前线程对应的ThreadLocalMap,这个map是跟线程绑定的,所以存在这个map中的数据也是跟线程绑定的
    3. 往map中添加数据,或者重新创建一个ThreadLocalMap对象

    至于ThreadLocalMap如何存储数据,这里就不展开说了。

    ThreadLocal#get

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
    

    这段代码也十分简单,无外乎将之前set进去的数据拿出来。

    总之,ThreadLocal根据不同的线程分别创建数据容器,从而达到数据独立的目的

    参考文章

    Android消息机制1-Handler(Java层)

    Android 消息机制——你真的了解Handler?

    你真的懂Handler吗?Handler问答

    Android消息循环机制

    Android中的Thread, Looper和Handler机制

    ThreadLocal 原理

    相关文章

      网友评论

        本文标题:Android消息循环机制浅析

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