理解 Android 消息机制

本人只是Android小菜一个,写技术文章只是为了总结本身最近学习到的知识,历来不敢为人师,若是里面有不正确的地方请你们尽情指出,谢谢!java

本文基于原生 Android 9.0 源码来解析 Android 消息机制:

frameworks/base/core/java/android/os/Handler.java
frameworks/base/core/java/android/os/Looper.java
frameworks/base/core/java/android/os/MessageQueue.java
frameworks/base/core/java/android/os/Message.java
frameworks/base/core/java/android/app/ActivityThread.java
复制代码

1. 概述

咱们知道在Android的主线程中不能进行耗时操做,例如网络访问、数据处理等,由于一旦主线程的任务处理时间超过系统规定的限制就会出现应用不响应的状况。但在实际工做中,处理耗时任务是不可避免的,并且常常须要在处理完耗时任务后更新某些UI控件,以显示处理结果。在这种场景下,最经常使用方案就是在新线程中进行耗时操做,处理完成后通知主线程进行相关UI的更新,这时就须要使用到Android消息机制了。android

其实在前面几篇文章中,小菜讲解过IntentServiceAsyncTaskHandlerThread的使用方法和实现原理,它们都是Android消息机制的具体应用。缓存

到底什么是消息机制呢?简单来讲,Android消息机制是一套以“消息”为中介来实现线程之间的任务切换或同一线程中任务的按需执行的机制,其中涉及到消息的发送、存储消息、消息循环以及消息的分发和处理。markdown

本文将先经过一个简单的示例演示如何使用Android消息机制,再经过分析源码来进一步了解消息机制的内部实现方式,最后会讲解一些使用Android消息机制的注意点。网络

2. 初见 Android 消息机制

先用一个简单示例来展现下Android消息机制在实际工做中如何使用,就直接利用前面提到的场景,即子线程处理耗时任务并在任务处理完毕后通知主线程进行UI的更新,示例代码以下:并发

public class MainActivity extends Activity {
    // 定义 Handler 和 Thread 局部变量
    private Handler mHandler;
    private Thread mThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mHandler = new Handler() {
            @Override
            public void handleMessage (Message msg) {
                // 4. 根据不一样的消息类型进行不一样的处理
                switch (msg.what) {
                    // 在这里进行和 UI 相关的操做,例如显示处理结果。
                }
            }
        };

        mThread = new Thread() {
            @Override
            public void run() {
                // 2. 休眠一段时间,模拟子线程在处理耗时任务。
                try {
                    Thread.sleep(30000);
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
                // 3. 发送消息
                mHandler.sendEmptyMessage(0);
            }
        };

        // 1. 开启子线程
        mThread.start();
    }
}
复制代码

小菜在示例代码里经过序号标注了逻辑流程,即先开启子线程并在线程内部处理任务,任务处理完成后经过Handler向主线程发送消息,最后在主线程中处理消息并更新UI。app

看起来Android消息机制很简单嘛,只要利用Handler发送消息并处理其中的消息就能够了嘛。真的这么简单吗?固然不是!前面提到过在消息机制中涉及到几个关键点:发送消息、存储消息、消息循环和分发处理消息,在这个示例中咱们只看到了发送消息和处理消息,并无看到存储消息和消息循环。框架

这是由于这个例子中的Handler使用的消息是发送和存储在主线程中的消息队列中,这个消息队列的建立和循环都是在主线程建立的时候系统自动进行的,对咱们是透明的,不利于理解消息机制的总体流程。less

如今给出一个更为通用的示例,从这个例子中能够清楚地看到消息队列的建立和消息循环的开启:async

class LooperThread extends Thread {
    public Handler mHandler;
    
    public void run() {
        // 初始化 Looper 对象,其内部会建立消息队列。
        Looper.prepare();
  
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
            // 处理消息队列中的消息。
            }
        };
        // 开启消息循环,会从消息队列中取出消息,没有消息时等待新消息的到来。
        Looper.loop();
    }
}
复制代码

综合这两个示例,咱们了解了Android消息机制的使用方法,也看到了发送消息、建立消息队列、开启消息循环以及处理消息的过程,下面给出一个更直观的“消息传递流程图”:

经过流程图能够看到整个消息传递过程,也能够看到在不一样的阶段涉及的类:

  • 消息发送:经过 Handler向关联的MessageQueue发送消息;
  • 消息存储: 把发送的消息以Message的形式存储在MessageQueue中;
  • 消息循环:经过Looper不停地从MessageQueue中获取消息,队列中没有消息时就阻塞等待新消息;
  • 消息分发和处理:Looper获取消息后分发给Handler进行处理。

3. 理解 Android 消息机制

前面提到消息传递流程主要分为“发送消息”、“存储消息”、“消息循环”和“消息分发和处理”几个不一样阶段,小菜本打算按照这个流程来分别讲解每一个阶段,可是在具体行文的时候发现每一个阶段并非彻底分割开来的,好比在讲“发送消息”以前要先了解“消息的存储结构”和“消息循环的开启”,而“消息的分发”又是属于“消息循环”的功能。

正是因为这几个阶段之间的相互关系,致使没有办法严格按照消息传递的顺序讲解Android消息机制。思虑再三,小菜决定经过上面讲解的Android消息机制通用示例来一步步解析其背后的逻辑流程。

再来看下通用示例:

class LooperThread extends Thread {
    public Handler mHandler;
    
    public void run() {
        // 1. 初始化 Looper 对象,其内部会建立消息队列。
        Looper.prepare();
  
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
            // 4. 处理消息队列中的消息。
            }
        };
        // 2. 开启消息循环,会从消息队列中取出消息,没有消息时阻塞等待新消息的到来。
        Looper.loop();
    }
    
    // 3. 发送消息
    mHandler.sendEmptyMessage(0);
}
复制代码

在示例代码中用不一样的序号标注了“消息传递机制”的各个关键点,如下的内容也都是根据这些关键节点进行讲解的。

3.1 消息载体

“消息”是Android消息机制中信息的载体,它包含了在整个消息传递过程当中想要传送的数据,要理解“消息机制”就要先了解这个消息载体类Message:

/** * Defines a message containing a description and arbitrary data object that can be * sent to a {@link Handler}. This object contains two extra int fields and an * extra object field that allow you to not do allocations in many cases. * * <p class="note">While the constructor of Message is public, the best way to get * one of these is to call {@link #obtain Message.obtain()} or one of the * {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull * them from a pool of recycled objects.</p> */
public final class Message implements Parcelable { ... }
复制代码

Android框架中对消息载体Message的声明虽然简短,却传达了最核心最重要的两点信息:

  1. Message的做用:Message是包含了描述信息和数据对象而且在消息机制中发送给Handler的对象,其中的数据对象主要包括两个整型域和一个对象域,经过这些域能够传递信息。整型的代价是最小的,因此尽可能使用整型域传递信息。
/** * User-defined message code so that the recipient can identify * what this message is about. Each {@link Handler} has its own name-space * for message codes, so you do not need to worry about yours conflicting * with other handlers. */
public int what;

/** * arg1 and arg2 are lower-cost alternatives to using * {@link #setData(Bundle) setData()} if you only need to store a * few integer values. */
public int arg1;
public int arg2;

/** * An arbitrary object to send to the recipient. When using * {@link Messenger} to send the message across processes this can only * be non-null if it contains a Parcelable of a framework class (not one * implemented by the application). For other data transfer use * {@link #setData}. * * <p>Note that Parcelable objects here are not supported prior to * the {@link android.os.Build.VERSION_CODES#FROYO} release. */
public Object obj;
复制代码
  1. Message的建立方式:虽然Message有公有构造函数,可是建议使用其提供的obtain系列函数来获取Message对象,这种建立方式会重复利用缓存池中的对象而不是直接建立新的对象,从而避免在内存中建立太多对象,避免可能的性能问题。
/** * Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases. */
public static Message obtain() {
    synchronized (sPoolSync) {
        // 缓存池中存在可用对象时去缓存池获取 Message 对象。
        if (sPool != null) {
            // 获取缓存中的对象,并把缓存池指针后移。 
            Message m = sPool;
            sPool = m.next;
            
            m.next = null;
            // 清除标志位
            m.flags = 0; // clear in-use flag
            // 更新当前缓存池大小
            sPoolSize--;
            return m;
        }
    }
    // 缓存池中没有可用对象时直接建立一个新的 Message 对象。
    return new Message();
}
复制代码

Message中有一系列obtain函数用以在不一样场景中获取对象,但这个是最核心的,其余函数都会在其内部调用它,有兴趣的同窗能够自行查看源码,考虑到篇幅问题,这里就再也不一一列举说明了。

看到这里,相信你们都会有一个疑问:obtain函数是从缓存池中获取Message对象,那缓存池中的对象是何时被添加进去的呢?既然缓存池中的对象都是一些能够被重复使用的对象,很明显是在Message对象再也不被须要的时候,即从MessageQueue中取出并分发给Handler的时候,被添加到缓存中的,使用的是recycleUnchecked函数:

/** * Recycles a Message that may be in-use. * Used internally by the MessageQueue and Looper when disposing of queued Messages. */
void recycleUnchecked() {
    // 设置标志位为“使用中”,在从缓存中取出时会清除这个标志位。
    flags = FLAG_IN_USE;
    // Message 对象中的信息都再也不有意义,在放入缓存池前直接清空。
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        // 缓存池中只缓存必定数量的 Message 对象,默认是 50 个。
        if (sPoolSize < MAX_POOL_SIZE) {
            // 把对象放在缓存池的链表首部。 
            next = sPool;
            sPool = this;
            // 及时更新缓存池大小。
            sPoolSize++;
        }
    }
}
复制代码

3.2 建立消息队列

消息队列的建立对消息传递相当重要,它决定了消息在传递过程当中的存取方式。可是线程在默认状况下是没有消息队列的,也没法在其内部进行消息循环。若是想为线程开启消息循环就须要使用到Looper类,它能够为关联的线程建立消息队列并开启消息循环,建立消息队列的方式是调用prepare()接口:

/** * Class used to run a message loop for a thread. Threads by default do * not have a message loop associated with them; to create one, call * {@link #prepare} in the thread that is to run the loop, and then * {@link #loop} to have it process messages until the loop is stopped. */
public final class Looper {
    // 省略无关代码
    
    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    // 内部的消息队列和关联的线程
    final MessageQueue mQueue;
    final Thread mThread;
    
    // 省略无关代码
    
    /** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, and end it by calling * {@link #quit()}. */
    public static void prepare() {
        // 建立可退出的消息循环,主线程的消息循环是不可退出的。
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        // 若是当前线程已经有了 Looper 对象就直接抛出异常,
        // 由于一个线程只能有一个消息队列。
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 建立 Looper 对象并和线程关联。
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    // 私有构造函数,建立消息队列并获取当前线程对象。
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
复制代码

能够看到Looper.prepare()只是在内部建立了一个MessageQueue对象并和当前线程关联起来,同时还保证了每一个线程只能有一个消息队列。

很显然MessageQueue就是用来存储消息对象的结构了,看下它的声明:

/** * Low-level class holding the list of messages to be dispatched by a * {@link Looper}. Messages are not added directly to a MessageQueue, * but rather through {@link Handler} objects associated with the Looper. * * <p>You can retrieve the MessageQueue for the current thread with * {@link Looper#myQueue() Looper.myQueue()}. */
public final class MessageQueue { ... }
复制代码

MessageQueue是一个持有消息对象列表的类,而这些消息对象经过和Looper关联的Handler添加并最终由Looper进行分发,其中有个关键信息须要引发咱们的格外关注:list of messages,这是否是告诉咱们虽然这个类的名字是queue可是其内部并非队列而是列表呢?确实如此,MessageQueue的内部是使用单向链表的方法进行存取的,这点在后面解析Message的存取过程当中会看到,在这里就不详细讲述了。

3.3 开启消息循环

“消息队列”建立完成了,是否是就能够直接向其中添加消息对象了呢?还不到时候,还须要先开启消息循环,来监听消息队列的状况,这时须要使用Looper.loop()接口:

/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */
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.");
    }
    // 获取当前线程的消息队列。
    final MessageQueue queue = me.mQueue;

    // 省略无关代码

    // 开启一个无限循环来监听消息队列的状况
    for (;;) {
        // 获取消息队列中的消息对象,若是没有消息对象就阻塞等待。
        Message msg = queue.next(); // might block
        if (msg == null) {
            // 消息队列正在退出时就终止监听并退出循环
            return;
        }

        // 省略无关代码
        
        try {
            // 分发消息,把消息发送合适的处理对象。
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        
        // 省略无关代码
        
        // 回收消息对象,放入消息缓存池中以待后续复用。
        msg.recycleUnchecked();
    }
}
复制代码

这段代码自己比较复杂,小菜省略了其中和核心逻辑无关的部分代码,以方便你们阅读和理解,其核心逻辑就是利用一个“无限循环”来监听消息队列,当发现有可用消息就取出并分发处理,若是没有就一直等待。

3.4 发送和存储消息

“消息队列”已经建立完成,“消息循环”也已经开启,终于可用发送消息了。

要发送消息,就要使用到Handler类了,其中的sendpost系列方法均可以进行“消息的发送”,核心方法都是同样的,在这里就以post方法来说解下发送消息的过程:

/** * Causes the Runnable r to be added to the message queue. * The runnable will be run on the thread to which this handler is * attached. * * @param r The Runnable that will be executed. * * @return Returns true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */
public final boolean post(Runnable r) {
    return  sendMessageDelayed(getPostMessage(r), 0);
}
    
private static Message getPostMessage(Runnable r) {
    // 把 Runnable 对象封装成 Message 并设置 callback,
    // 这个 callback 会在后面消息的分发处理中起到做用。
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
    
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    // 把延迟时间转换为绝对时间,方便后续执行。
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
    
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    // 消息队列,即经过 Looper.prepare() 建立的消息队列。
    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(MessageQueue queue, Message msg, long uptimeMillis) {
    // 设置消息队列的目标,用于后续的消息分发过程。
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // 消息对象入队
    return queue.enqueueMessage(msg, uptimeMillis);
}
复制代码

经过一系列的调用过程,Handler最终会经过 MessageQueue.enqueueMessage()把消息存储到消息队列中,MessageQueue内部又是如何存储这个发送过来的消息对象的呢?

boolean enqueueMessage(Message msg, long when) {
    // 消息对象的目标是 null 时直接抛出异常,由于这意味这个消息没法进行分发处理,
    // 是不合法的消息对象。
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    // 消息正在使用时抛出异常,消息不能并发使用。
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    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) {
            // New head, wake up the event queue if blocked.
            // 消息队列为空或者当前消息对象的时间最近,直接放在链表首部。
            msg.next = p;
            // 更新链表指针
            mMessages = msg;
            // 若是原来消息循环处于阻塞状态就从新唤醒
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue. Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            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;
}
复制代码

3.5 消息分发处理

当消息队列中有新的消息而且消息循环被唤醒后,消息队列中的消息就能够被取出并分发给合适的处理者了,这点能够在“开启消息循环”一节中看到,利用的是msg.target.dispatchMessage(msg),而target就是Handler对象,直接看具体的分发过程:

public void dispatchMessage(Message msg) {
    // Message 对象是从 Runnable 封装造成的时候,callback 不为空。
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        // mCallback 是在 Handler 的构造函数中设置的,也能够不设置。
        if (mCallback != null) {
            // 调用 Handler 的 callback 处理消息
            if (mCallback.handleMessage(msg)) {
                // 能够拦截消息,以后 Handler.handleMessage 将没法继续处理这个消息。
                return;
            }
        }
        // 调用 Handler 的 handleMessage 处理消息,子类会实现这个方法。
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    // Message 中的 callback 是 Runnable,直接执行 Runnable.run()。
    message.callback.run();
}

/** * Callback interface you can use when instantiating a Handler to avoid * having to implement your own subclass of Handler. */
 public interface Callback {
    /** * @param msg A {@link android.os.Message Message} object * @return True if no further handling is desired */
    // Handler 的回调方法,经过返回值能够进行消息拦截。
    public boolean handleMessage(Message msg);
}
    
/** * Subclasses must implement this to receive messages. */
// Handler 的处理消息回调,子类须要实现。
public void handleMessage(Message msg) {
}
复制代码

消息的分发是有必定优先顺序的:

  1. 首先会考虑交给Message.callback来处理,若是是经过post系列函数发送的消息会走到这里进行处理,而经过send系列函数发送的消息默认是没有这个回调接口的;
  2. 若是Message.callback不存在就考虑交给Handler.callback来处理,在处理过程当中能够经过返回值拦截消息;
  3. 若是Handler.callback不存在或者存在可是在处理消息过程当中没有进行拦截,就会交给Handler.handleMessage来处理,这个接口须要子类实现,也是在实际工做中最经常使用的处理消息的地方。

到这里,消息的传递过程就基本讲完了,你们能够结合以前的流程图仔细揣摩,相信能够对Android消息机制有更深入的理解。

4. 延伸知识点

4.1 主线程消息循环的建立

前面讲到一个线程默认是没有消息队列的,也没法在其内部开启消息循环,可是咱们在实际工做中常常会直接在主线程中使用Handler来进行消息的发送和处理,而且运行正常,这是由于主线程在启动的时候就已经建立了消息队列并开启了消息循环,只是这个过程是透明的,咱们没有感知到。

了解Activity启动过程的同窗应该已经想到了这个建立过程是在哪里了,没错,就是在ActivityThread,不了解启动过程的同窗也不要担忧,后续我会讲解具体的启动过程。在这里,你们只要简单地把ActivityThread当作Activity的启动入口便可,直接来看入口函数:

/** * This manages the execution of the main thread in an * application process, scheduling and executing activities, * broadcasts, and other operations on it as the activity * manager requests. * * {@hide} */
public final class ActivityThread extends ClientTransactionHandler {
    public static void main(String[] args) {
        // 记录开始,用于后续经过 systrace 检查和调试性能问题。
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        // 省略无关代码

        // 为主线程建立消息队列
        Looper.prepareMainLooper();

        // 省略无关代码
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

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

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

        // 记录结束,后续能够经过 systrace 观察这段代码的执行状况。
        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        
        // 开启消息循环
        Looper.loop();

        // 主线程消息循环不会退出,若是走到这意味着发生意外,抛出异常。
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}
复制代码

代码结构和Android消息机制的通用示例很像,在里面看到了消息队列的建立和消息循环的开启,不一样之处在于主线程中建立消息队列使用的是Looper.prepareMainLooper

/** * Initialize the current thread as a looper, marking it as an * application's main looper. The main looper for your application * is created by the Android environment, so you should never need * to call this function yourself. See also: {@link #prepare()} */
public static void prepareMainLooper() {
    // 启动一个没法退出的消息循环
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        // 返回主线程 looper 对象
        sMainLooper = myLooper();
    }
}
复制代码

为主线程建立的消息循环是没法退出的,由于这个消息循环要处理不少重要事务,好比Activity生命周期的回调等,若是退出将致使异常,这点在后续讲解Activity启动过程的时候再详细解析。

4.2 内存泄露

Java垃圾回收机制对于每一个从事Java的开发者应该都不陌生,咱们也清楚并非全部对象占用的内存均可以被及时回收,若是垃圾回收器准备回收某些对象,可是因为它们还被其余对象引用,那么这些对象就没法被回收,这也是内存泄漏的主要缘由。

使用Android消息机制时会不会致使内存泄漏呢?首先来看一种常见的使用方法:

public class MainActivity extends Activity {
    private TextView mTextView = null;
    private Handler mMyHandler = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化控件
        mTextView = (TextView) findViewById(R.id.sample_text);
        // 初始化 Handler 对象
        mMyHandler = new MyHandler();
        // 启动一个延迟消息,在 3000ms 后有 mMyHandler 执行。
        mMyHandler.sendEmptyMessageDelayed(0, 3000);
    }

    private class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            // 执行消息,更新主线程中的控件。
            if (mTextView != null) {
                mTextView.setText("execute message");
            }
        }
    };

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}
复制代码

在这个示例中,MyHandler是以Activity内部类的形式存在的,因此mMyHandler是须要持有外部类对象引用的,而mMyHandler又被其发送的Message对象以target的方式引用,最终的结果就是Activity间接被Message引用。因为这个Message须要在必定的延迟后被执行,若是在这以前Activity退出,可是因为其引用被Message持有,致使没法被系统回收,进而致使内存泄露。

既然ActivityMessage引用致使内存泄露,那有没有办法不让其持有引用呢?固然能够,使用“静态内部类”就能够避免这种状况,由于“静态内部类”不须要持有外部类对象的引用,来看示例代码:

public class MainActivity extends Activity {
    private TextView mTextView = null;
    private Handler mMyHandler = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.sample_text);

        // 初始化 Handler 对象,并把主线程控件做为参数传入。
        mMyHandler = new MyHandler(mTextView);
        // 启动一个延迟消息,在 3000ms 后有 mMyHandler 执行。
        mMyHandler.sendEmptyMessageDelayed(0, 3000);
    }

    private static class MyHandler extends Handler {
        // 经过弱引用的方式持有外部对象的变量。
        private WeakReference<TextView> mTextViewRef = null;

        // 初始化弱引用对象,此后就持有了正确的对象引用。
        public MyHandler(TextView textView) {
            mTextViewRef = new WeakReference<>(textView);
        }
        @Override
        public void handleMessage(Message msg) {
            // 执行消息,更新主线程中的控件。
            if (mTextViewRef != null && mTextViewRef.get() != null) {
                mTextViewRef.get().setText("execute message");
            }
        }
    };

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 退出时状况消息队列中的消息
        mMyHandler.removeCallbacksAndMessages(null);
    }
}
复制代码

经过“静态内部类”和“弱引用”的结合,既能够不持有外部类对象引用又能够访问外部类对象的变量,并在Activity退出时又移除消息队列中的消息,进一步避免了内存泄露的风险。

这只是其中一中避免内存泄露的方法,确定还有其余方法也能够达到目的,有兴趣的同窗能够自行研究。

5. 总结

本文讲解了Android消息机制的使用方法、总体流程和每一个阶段的实现原理,在最后还提到主线程消息循环的建立以及错误使用致使的内存泄漏及避免方法,但愿能对你们学习消息机制有所帮忙。

相关文章
相关标签/搜索