Android消息循环机制浅析

阅读 507
收藏 20
2018-10-10
原文链接:www.jianshu.com

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 原理

评论