第一部分:Android消息机制讲解。


Android的消息机制主要指的是Handler的运行机制。Handler的运行主要靠底层的MessageQueue和Looper支持。

一、MessageQueue

消息队列,其内部存储了一组消息,以队列的形式对外提供插入、删除操作。但其内部数据结构并不是队列,而是单链表。

队列中存储的消息是什么呢?假设我们在UI界面上单击了某个按钮,而此时程序又恰好收到了某个广播事件,那我们如何处理这两件事呢? 因为一个线程在某一时刻只能处理一件事情,不能同时处理多件事情,所以我们不能同时处理按钮的单击事件和广播事件,我们只能挨个对其进行处理,只要挨个处理就要有处理的先后顺序。 为此Android把UI界面上单击按钮的事件封装成了一个Message,将其放入到MessageQueue里面去,即将单击按钮事件的Message入栈到消息队列中,然后再将广播事件的封装成以Message,也将其入栈到消息队列中。也就是说一个Message对象表示的是线程需要处理的一件事情,消息队列就是一堆需要处理的Message的池。线程Thread会依次取出消息队列中的消息,依次对其进行处理。MessageQueue中有两个比较重要的方法,一个是enqueueMessage方法,一个是next方法。enqueueMessage方法用于将一个Message放入到消息队列MessageQueue中,next方法是从消息队列MessageQueue中阻塞式地取出一个Message。在Android中,消息队列负责管理着顶级程序对象(Activity、BroadcastReceiver等)以及由其创建的所有窗口。需要注意的是,消息队列不是Android平台特有的,其他的平台框架也会用到消息队列,比如微软的MFC框架等。

MessageQueue插入一条消息就是对单链表进行插入操作,主要通过 enqueueMessage方法实现,而这个方法内部实现主要就是对单链表的插入操作。

MessageQueue取出一条消息是通过 next方法实现的,取出一条消息时,还会将这条消息删除。next方法是一个无限循环方法,当有消息来时,next方法会返回这条消息并将其删除。如果队列里没有消息,那么next方法会一直阻塞。


二、Looper

循环,MessageQueue只是一个消息存储单元,真正让消息队列循环起来的是Looper。

默认情况下当我们创建一个新的线程的时候,这个线程里面是没有消息队列MessageQueue和Looper的。为了能够让线程能够绑定一个消息队列,我们需要借助于Looper:首先我们要调用Looper的prepare方法,然后调用Looper的Loop方法。

Looper的构造函数,我们看一下其代码:

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

Looper的构造函数是private的,也就是在该类的外部不能用new Looper()的形式得到一个Looper对象。线程Thread和Looper是一对一绑定的,也就是一个线程中最多只有一个Looper对象,这也就能解释Looper的构造函数为什么是private的了,我们只能通过工厂方法Looper.myLooper()这个静态方法获取当前线程所绑定的Looper

在其构造函数中实例化一个消息队列MessageQueue,并将其赋值给其成员字段mQueue,这样Looper也就与MessageQueue通过成员字段mQueue进行了关联。

myLooper的代码如下所示:

public static Looper myLooper() {        return sThreadLocal.get();}

sThreadLocal是什么?

ThreadLocal是Thread内部的数据存储类,通过它可以在指定的线程中存储数据,并且只有在指定的线程才能获取该数据。

它存储了该线程的looper。通过get、set方法存取looper。


现在看一下Looper.prepare(),该方法是让Looper做好准备,只有Looper准备好了之后才能调用Looper.loop()方法,Looper.prepare()的代码如下:

private static void prepare(boolean quitAllowed) {        if (sThreadLocal.get() != null) {            throw new RuntimeException("Only one Looper may be created per thread");        }        sThreadLocal.set(new Looper(quitAllowed));}

上面的代码首先通过sThreadLocal.get()拿到线程sThreadLocal所绑定的Looper对象,由于初始情况下sThreadLocal并没有绑定Looper,所以第一次调用prepare方法时,sThreadLocal.get()返回null,不会抛出异常。重点是下面的代码sThreadLocal.set(new Looper(quitAllowed)),首先通过私有的构造函数创建了一个Looper对象的实例,然后通过sThreadLocal的set方法将该Looper绑定到sThreadLocal中。 
这样就完成了线程sThreadLocal与Looper的双向绑定: 

a. 在Looper内通过sThreadLocal可以获取Looper所绑定的线程; 

b.线程sThreadLocal通过sThreadLocal.get()方法可以获取该线程所绑定的Looper对象。


在执行完了Looper.prepare()之后,我们就可以在外部通过调用Looper.myLooper()获取当前线程绑定的Looper对象。 这时候我们就可以调用Looper.loop()方法让消息队列循环起来了。 

Looper.loop()是个静态方法,代码我就不贴出来了,我总结了里面的关键点:

1. final MessageQueue queue = me.mQueue; 

变量me是通过静态方法myLooper()获得的当前线程所绑定的Looper,me.mQueue是当前线程所关联的消息队列。 

2. for (;;) 

我们发现for循环没有设置循环终止的条件,所以这个for循环是个死循环。 唯一跳出循环的标志是:MessageQueue的next方法返回null。当Looper的quit方法(退出方法)被调用时,Looper会调用MessageQueue的quit方法通知其退出,当MessageQueue要退出时,它的next方法返回null。

3. Message msg = queue.next(); // might block 

我们通过消息队列MessageQueue的next方法从消息队列中取出一条消息,如果此时消息队列中有Message,那么next方法会立即返回该Message,如果此时消息队列中没有Message,那么next方法就会阻塞式地等待获取Message。 

4. msg.target.dispatchMessage(msg); 

当收到新的消息时,Looper会处理它:msg.target.dispatchMessage(msg) 。msg.target是发送这条消息的Handler对象。dispatchMessage方法是在创建Handler时所使用的Looper中执行的。


三、Handler

Handler的工作主要就是发送、处理消息。

先来说说他的构造方法:

Handler具有多个构造函数,分别如下所示: 
1. public Handler() 
2. public Handler(Callbackcallback) 
3. public Handler(Looperlooper) 
4. public Handler(Looperlooper, Callbackcallback) 
第1个和第2个构造函数都没有传递Looper,这两个构造函数都将通过调用Looper.myLooper()获取当前线程绑定的Looper对象,然后将该Looper对象保存到名为mLooper的成员字段中。 

第3个和第4个构造函数传递了Looper对象,这两个构造函数会将该Looper保存到名为mLooper的成员字段中。 

也就是说,在Handler的构造方法中就完成了和当前线程的Looper的绑定操作。

第2个和第4个构造函数还传递了Callback对象,Callback是Handler中的内部接口,需要实现其内部的handleMessage方法,Callback代码如下:
public interface Callback {        public boolean handleMessage(Message msg);}
Handler.Callback是用来处理Message的一种手段,如果没有传递该参数,那么就应该重写Handler的handleMessage方法,也就是说为了使得Handler能够处理Message,我们有两种办法: 
1. 向Hanlder的构造函数传入一个Handler.Callback对象,并实现Handler.Callback的handleMessage方法 
2. 无需向Hanlder的构造函数传入Handler.Callback对象,但是需要重写Handler本身的handleMessage方法 


Handler的发送过程:

主要是一系列的postXXX方法和sendMessageXXX方法发送消息,其最终都调用了sendMessageAtTime方法发送消息。如果使用postXXX方法发送消息,那么Handler会将post内的Runable对象赋值给Message的callback对象,然后转换为message对象,进行传递。

于是来看看sendMessageAtTime方法:

public boolean sendMessageAtTime(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);}

该方法内部调用了enqueueMessage方法,该方法的源码如下:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {        //注意下面这行代码        msg.target = this;        if (mAsynchronous) {            msg.setAsynchronous(true);        }        //注意下面这行代码        return queue.enqueueMessage(msg, uptimeMillis);}

在该方法中有两件事需要注意: 
1. msg.target = this 
该代码将Message的target绑定为当前的Handler 。 msg.target有木有很熟悉?在Looper.loop方法中,Looper通过MessageQueue的next方法获取到Message后,就通过msg.target.dispatchMessage(msg)处理消息。到这就很简单理解这个方法了:让该msg所绑定的Handler(msg.target)去执行dispatchMessage方法处理消息。
2. queue.enqueueMessage 
变量queue表示的是Handler所绑定的消息队列MessageQueue,通过调用queue.enqueueMessage(msg, uptimeMillis)我们将Message放入到消息队列中。

Handler的处理过程:

通过dispatchMessage处理消息:

public void dispatchMessage(Message msg) {        //注意下面这行代码        if (msg.callback != null) {            handleCallback(msg);        } else {             //注意下面这行代码            if (mCallback != null) {                if (mCallback.handleMessage(msg)) {                    return;                }            }             //注意下面这行代码            handleMessage(msg);        }}

我们来分析下这段代码:

1.首先会判断msg.callback存不存在,msg.callback是Runnable类型,如果msg.callback存在,那么说明该Message是通过执行Handler的postXXX系列方法将Message放入到消息队列中的,这种情况下会执行handleCallback(msg), handleCallback源码如下:

private static void handleCallback(Message message) {        message.callback.run();}

这样我们我们就清楚地看到我们执行了msg.callback的run方法,也就是执行了postXXX所传递的Runnable对象的run方法。

2.如果我们不是通过postXXX系列方法将Message放入到消息队列中的,那么msg.callback就是null,代码继续往下执行,接着我们会判断Handler的成员字段mCallback存不存在。mCallback是Hanlder.Callback类型的,我们在上面提到过,在Handler的构造函数中我们可以传递Hanlder.Callback类型的对象,该对象需要实现handleMessage方法,如果我们在构造函数中传递了该Callback对象,那么我们就会让Callback的handleMessage方法来处理Message。

3.如果我们在构造函数中没有传入Callback类型的对象,那么mCallback就为null,那么我们会调用Handler自身的hanldeMessage方法,该方法默认是个空方法,我们需要自己是重写实现该方法。

综上,我们可以看到Handler提供了三种途径处理Message,而且处理有前后优先级之分:首先尝试让postXXX中传递的Runnable执行,其次尝试让Handler构造函数中传入的Callback的handleMessage方法处理,最后才是让Handler自身的handleMessage方法处理Message。



第二部分:HandlerThread、Handler相关的内存泄漏问题。


一、HandlerThread

HandlerThread继承自Thread,它的实现也很简单:在run()方法内通过Looper.prepare来创建消息队列,并通过Looper.loop来开启消息循环,这样就允许在HandlerThread中创建Handler了。创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。

我们先来看看HandlerThread使用步骤。 
1.创建实例对象

    1.  HandlerThread handlerThread = new HandlerThread("downloadImage");

传入参数的作用主要是标记当前线程的名字,可以任意字符串。 
2.启动HandlerThread线程

    1.  //必须先开启线程    2.  handlerThread.start();

  到此,我们创建完HandlerThread并启动了线程。那么我们怎么将一个耗时的异步任务投放到HandlerThread线程中去执行呢?接下来看下面步骤: 
3.构建循环消息处理机制

  /**     * 该callback运行于子线程     */    class ChildCallback implements Handler.Callback {        @Override        public boolean handleMessage(Message msg) {            //在子线程中进行相应的网络请求            //通知主线程去更新UI            mUIHandler.sendMessage(msg1);            return false;        }    }

4.构建异步handler

//子线程HandlerHandler childHandler = new Handler(handlerThread.getLooper(),new ChildCallback());

  第3步和第4步是构建一个可以用于异步操作的handler,并将前面创建的HandlerThread的Looper对象以及Callback接口类作为参数传递给当前的handler,这样当前的异步handler就拥有了HandlerThread的Looper对象,由于HandlerThread本身是异步线程,因此Looper也与异步线程绑定,从而handlerMessage方法也就可以异步处理耗时任务了,这样我们的Looper+Handler+MessageQueue+Thread异步循环机制构建完成


再看看HandlerThread的源码(70行,非常简单!):

public class HandlerThread extends Thread {    int mPriority;    int mTid = -1;    Looper mLooper;    public HandlerThread(String name) {        super(name);        mPriority = Process.THREAD_PRIORITY_DEFAULT;    }    //也可以指定线程的优先级,注意使用的是 android.os.Process 而不是 java.lang.Thread 的优先级!    public HandlerThread(String name, int priority) {        super(name);        mPriority = priority;    }    // 子类需要重写的方法,在这里做一些执行前的初始化工作    protected void onLooperPrepared() {    }    //获取当前线程的 Looper    //如果线程不是正常运行的就返回 null    //如果线程启动后,Looper 还没创建,就 wait() 等待 创建 Looper 后 notify    public Looper getLooper() {        if (!isAlive()) {            return null;        }        synchronized (this) {            while (isAlive() && mLooper == null) {    //循环等待                try {                    wait();                } catch (InterruptedException e) {                }            }        }        return mLooper;    }    //调用 start() 后就会执行的 run()    @Override    public void run() {        mTid = Process.myTid();        Looper.prepare();            //帮我们创建了 Looepr        synchronized (this) {            mLooper = Looper.myLooper();            notifyAll();    //Looper 已经创建,唤醒阻塞在获取 Looper 的线程        }        Process.setThreadPriority(mPriority);        onLooperPrepared();            Looper.loop();        //开始循环        mTid = -1;    }    public boolean quit() {        Looper looper = getLooper();        if (looper != null) {            looper.quit();            return true;        }        return false;    }    public boolean quitSafely() {        Looper looper = getLooper();        if (looper != null) {            looper.quitSafely();            return true;        }        return false;    }    public int getThreadId() {        return mTid;    }}

是不是很简单?

主要需要注意两个方法:run 和 getLooper

run:

前面我们在HandlerThread的常规使用中分析过,在创建HandlerThread对象后必须调用其start()方法才能进行其他操作,而调用start()方法后相当于启动了线程,也就是run方法将会被调用,而我们从run源码中可以看出其执行了Looper.prepare()代码,这时Looper对象将被创建,当Looper对象被创建后将绑定在当前线程(也就是当前异步线程),这样我们才可以把Looper对象赋值给Handler对象,进而确保Handler对象中的handleMessage方法是在异步线程执行的。

getLooper:

在哪用到了这个方法呢?   在上面的使用步骤的第四步。用于创建子线程Handler时传入当前线程Looper。run方法内只是准备好了Looper,允许在子线程创建Handler,最后我们还是要自己去创建Handler的。可以看出外部在通过getLooper方法获取looper对象时会先先判断当前线程是否启动了,如果线程已经启动,那么将会进入同步语句并判断Looper是否为null,为null则代表Looper对象还没有被赋值,也就是还没被创建,此时当前调用线程进入等待阶段,直到Looper对象被创建并通过 notifyAll()方法唤醒等待线程,最后才返回Looper对象,之所以需要等待唤醒机制,是因为Looper的创建是在子线程中执行的,而调用getLooper方法则是在主线程进行的,这样我们就无法保障我们在调用getLooper方法时Looper已经被创建,到这里我们也就明白了在获取mLooper对象时会存在一个同步的问题,只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值,HandlerThread内部则通过等待唤醒机制解决了同步问题。

HandlerThread的主要用途在实现IntentService,后面会专门写一篇文章介绍IntentService。



二、使用Handler时的内存泄漏问题:

在进行异步操作时,我们经常会使用到Handler类。最常见的写法如下。


但是,这段代码有可能会引起内存泄漏。我们来分析一下内存泄漏是如何发生的:

当使用内部类或匿名内部类的方式创建Handler时,Handler对象会隐式地持有一个外部类对象的引用(这里的外部类是Activity)。一般在一个耗时任务中会开启一个子线程,如网络请求或文件读写操作,我们会使用到Handler对象。但是,如果在任务未执行完时,Activity被关闭了,Activity已不再使用,此时由GC来回收掉Activity对象。由于子线程未执行完毕,子线程持有Handler的引用,而Handler又持有Activity的引用,这样直接导致Activity对象无法被GC回收,即出现内存泄漏。

解决方法:

方法一:通过程序逻辑来进行保护。
1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。 
2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。

方法二:将Handler声明为静态类。
静态类不持有外部类的对象,所以你的Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference)。

ps:关于方法一,我似乎有听过有人说最好不要通过这种方式来处理Handler的内存泄漏问题,可是我忘了具体原因是什么又或是我记错了?如果大家有知道的请在评论区大方赐教一番...



参考了以下文献:

深入源码解析Android中的Handler,Message,MessageQueue,Looper

Android 多线程之HandlerThread 完全详解》

Handler内存泄漏详解及其解决方案》

《Android开发艺术探索》


更多相关文章

  1. Android(安卓)双缓冲技术
  2. 换一种方式理解 Android协程
  3. android之Handler整理
  4. Android事件传递机制(更加深入的了解事件的触发过程)
  5. Handler原理
  6. android HTTP 通信, XML 解析, 通过 Hander 实现异步消息处理 (1)
  7. Android开发之Android开发规范(初)
  8. android消息推送方案
  9. 面试题目及其答案

随机推荐

  1. 《Android》Lesson19-广播1
  2. Android之常用开发框架
  3. Android项目中使用MVP模式
  4. Android studio 自定义打包APK名称
  5. Android中ActivityManager学习笔记(3)
  6. Android学习笔记-1.Android工程结构
  7. android暂停或停止其他音乐播放器的播放
  8. Android的四种启动模式(launchModel)
  9. 【搜集】Android permission 访问权限(附
  10. 【Android Studio问题】创建模拟器时没有