Android进阶之第10章 消息机制

作者: sinyu 分类: Android,学习笔记,总结与反思 发布时间: 2018-05-05 14:27

Android的消息机制指的是Handler的运行机制,本篇将总结Handler机制的相关知识点:

  • 消息机制概述
  • 消息机制分析

 

10.1 Android消息机制概述

  1. Handler的主要作用是将某个任务切换到Handler所在的线程中去执行。为什么Android要提供这个功能呢?这是因为Android规定访问UI只能通过主线程,如果子线程访问UI,程序会抛出异常;ViewRootImpl在checkThread方法中做了判断。
  2. 由于Android不建议在主线程进行耗时操作,否则可能会导致ANR。那我们耗时操作在子线程执行完毕后,我们需要将一些更新UI的操作切换到主线程当中去。所以系统就提供了Handler。
  3. 系统为什么不允许在子线程中去访问UI呢?
    因为Android的UI控件不是线程安全的,多线程并发访问可能会导致UI控件处于不可预期的状态,为什么不加锁?因为加锁机制会让UI访问逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。所以Android采用了高效的单线程模型来处理UI操作。
  4. Handler创建时会采用当前线程的Looper来构建内部的消息循环系统,如果当前线程没有Looper就会报错。Handler可以通过post方法发送一个Runnable到消息队列中,也可以通过send方法发送一个消息到消息队列中,其实post方法最终也是通过send方法来完成。
  5. MessageQueue的enqueueMessage方法最终将这个消息放到消息队列中,当Looper发现有新消息到来时,处理这个消息,最终消息中的Runnable或者Handler的handleMessage方法就会被调用,注意Looper是运行Handler所在的线程中的,这样一来业务逻辑就切换到了Handler所在的线程中去执行了。

10.2 Android的消息机制分析

10.2.1 ThreadLocal的工作原理

  1. ThreadLocal是一个线程内部的数据存储类,通过它可以在指定线程存储数据,数据存储后,只能在指定的线程可以获取到存储的数据,对于其他线程则无法获取到数据。一般来说,当数据是以线程作为作用域并且不同线程有不同副本的时候,就可以考虑使用ThreadLocal。对于Handler来说,它需要获取当前线程的Looper,而Looper的作用于就是线程并且不同的线程具有不同的Looper,通过ThreadLocal可以轻松实现线程中的存取。
  1. ThreadLocal的另一个使用场景是复杂逻辑下的对象传递。
  1. ThreadLocal原理:不同线程访问同一个ThreadLoacl的get方法,ThreadLocal的get方法会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找对应的Value值。
    (1) ThreadLocal set方法
    //PS:JDK中与Android SDK的ThreadLocal有些许区别,我们以SDK中的为例

 

   public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

获取到当前线程,并从当前线程中取出对应的ThreadLocalMap(内部由Entry[]
table来存储ThreadLoacl的值),再把相应数据存入其中。如果ThreadLocalMap为空,则创建该线程的ThreadLocalMap对象,再将ThreadLocal的值进行存储。

(2) ThreadLocal get方法

 

   public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }
        return (T) values.getAfterMiss(this);
    }

获取当前线程,并从当前线程中获取对应的ThreadLocalMap,通过key获取对应的value;如果未获取到对应的ThreadLocalMap,则创建并将该对象返回。

(3)ThreadLocal的值在table数值中的位置总是ThreadLocal的索引+1。

 

10.2.2 消息队列的工作原理

  1. MessageQueue主要有两个操作,插入和读取,读取操作伴随着删除操作;MessageQueue是通过单链表的数据结构来维护消息列表的。
  2. enqueueMessage方法的作用是往消息队列插入一条消息。next方法是一个无线循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里。当有新消息到来时,next方法会返回这条消息并将其从单链表中移除。

 

10.2.3 Looper的工作原理

  1. prepareMainLooper方法主要给主线程也就是ActivityThread创建Looper使用的,本质也是通过prepare方法实现的。
  1. Looper提供quit和quitSafely来退出一个Looper,区别在于quit会直接退出Looper,而quitSafely会把消息队列中已有的消息处理完毕后才安全地退出。
    Looper退出后,这时候通过Handler发送的消息会失败,Handler的send方法会返回false。
    在子线程中,如果手动为其创建了Looper,在所有事情做完后,应该调用Looper的quit方法来终止消息循环,否则这个子线程就会一直处于等待状态;而如果退出了Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。
  1. loop方法会调用MessageQueue的next方法来获取新消息,而next是是一个阻塞操作,但没有信息时,next方法会一直阻塞在那里,这也导致loop方法一直阻塞在那里。如果MessageQueue的next方法返回了新消息,Looper就会处理这条消息:mas.target.dispatchMessage(msg),这里的msg.target是发送这条消息的Handler对象,这样Handler发送的消息最终又交给Handler来处理了。

 

10.2.4 Handler的工作原理

  1. Handler的工作主要包含消息的发送和接受过程。发送过程通过post的一系列方法和send的一系列方法来实现。Handler发送过程仅仅是向消息队列中插入了一条消息。MessageQueue的next方法就会返回这条消息给Looper,Looper拿到这条消息就开始处理,最终消息会交给Handler来处理。
  2. Handler处理消息

   

 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            //Message的callback是一个Runnable,       
           //也就是Handler的 post方法所传递的Runnable参数
            handleCallback(msg);
        } else {
            //如果给Handler设置了Callback的实现,
            //则调用Callback的handleMessage(msg)
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //调用Handler的handleMessage方法来处理消息,
            //该Handler子类需重写handlerMessage(msg)方法
            handleMessage(msg);
        }
    }
    private static void handleCallback(Message message) {
        message.callback.run();
    }
    public interface Callback {
        public boolean handleMessage(Message msg);
    }
    //默认空实现
    public void handleMessage(Message msg) {
    }

  1. Handler还有一个特殊的构造方法,可以指定一个特殊的Looper来构造Handler。

 

   public Handler(Looper looper) {
        this(looper, null, false);
    }

  1. Handler创建需要Looper,否则会抛出异常,默认获取当前线程的Looper。主线程也就是ActivityThread会自动创建Looper,其他线程如果需要Looper均需要手动创建。

 

10.3 主线程消息循环

  1. Android的主线程就是ActivityThread,主线程的入口方法为main,在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。

public static void main(String[] args) {
        ... 
        Process.setArgV0("<pre-initialized>");
 
        Looper.prepareMainLooper();
 
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
 
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

AsyncTask.init();

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

Looper.loop();

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

  1. Looper.loop(),这里是一个死循环,如果主线程的Looper终止,则应用程序会抛出异常。那么问题来了,既然主线程卡在这里了,(1)那Activity为什么还能启动;(2)点击一个按钮仍然可以响应?
    问题1:startActivity的时候,会向AMS(ActivityManagerService)发一个跨进程请求(AMS运行在系统进程中),之后AMS启动对应的Activity;AMS也需要调用App中Activity的生命周期方法(不同进程不可直接调用),AMS会发送跨进程请求,然后由App的ActivityThread中的ApplicationThread会来处理,ApplicationThread会通过主线程线程的Handler将执行逻辑切换到主线程。重点来了,主线程的Handler把消息添加到了MessageQueue,Looper.loop会拿到该消息,并在主线程中执行。这就解释了为什么主线程的Looper是个死循环,而Activity还能启动,因为四大组件的生命周期都是以消息的形式通过UI线程的Handler发送,由UI线程的Looper执行的。
    问题2:和问题1原理一样,最终都是由系统发消息来处理的,都经过了Looper.loop()。
    问题2详细分析请看原书作者的Android中MotionEvent的来源和ViewRootImpl

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注