Android Handler/Looper/MessageQueue异步消息处理机制源码剖析

Android的View模型采用的是单线程模型,所有的视图相关的操作都必须在主线程中进行,否则会抛出异常。某些耗时的操作需要放入工作线程中,执行完后通过异步消息处理机制把结果传入主线程中进行刷新UI等操作。异步消息处理机制主要由Handler\Looper\MessageQueue\Message几个类,它工作的原理网上有很多资料,这里不再赘述。接下来深入剖析下相关源码(源码基于android8.0)。

1. Message

Message顾名思义,是一个消息,是多线程间通信的实体,是Handler发送和处理的对象。Message对象实现了Parcelable接口,说明Message对象支持序列化/反序列化操作。

成员变量
//标识消息的code
public int what;
//存储int类型的数据域
public int arg1;
//存储int类型的数据域
public int arg2;
//存储对象的数据域
public Object obj;
//消息标识,当消息被回收放入到消息池时会被打上FLAG_IN_USE标识
/*package*/
int flags;
//记录消息的时间戳
/*package*/
long when;
//Bundle数据域
/*package*/
Bundle data;
//发送和处理消息的Handler
/*package*/
Handler target;
//回调接口,消息可以被Handler处理,也可以被自身的Runnable对象处理
/*package*/
Runnable callback;

// 链式结构,指向下一个Massgage对象,用于维护链表结构的消息池
/*package*/
Message next;
//静态域,信号量,可理解成这个对象是为了同步加锁创建的,所有需要对消息池进行的操作,会对该对象进行加锁同步
private static final Object sPoolSync = new Object();
//静态域,链表结构消息池的表头,由它维护了一个链式空闲消息池,当消息被回收的时候,会加入到这个消息池中
private static Message sPool;
//静态域,消息池大小
private static int sPoolSize = 0;
//消息池最大容量是50
private static final int MAX_POOL_SIZE = 50;
//在回收消息之前是否需要检查消息是否是在被使用当中
private static boolean gCheckRecycle = true;

从Message定义的这些变量可知以下几点:

  • Message中分别定义了几个数据域,可以用来存储数据(arg1,arg2,obj,data)。
  • Message中维护了一个全局的消息池,消息池的最大容量是50
obtain()方法

通过Meaasge.obtain()可以获得一个Message对象:

public static Message obtain() {
    synchronized(sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

获取一个Message对象的源码很简单,先加锁,如果消息池是空的,直接new一个新的Message对象出来,否则从链表头取出一个Message对象,把它暂时从消息池中删除,并清除它的flag。需要注意的是,obtain静态方法有很多重载的实现,但都是先调用Meaasge.obtain()方法获取到一个Message对象,再对相关变量进行赋值。

recycle()方法

在一个Message被使用完以后,调用recycle()方法会被回收:

public void recycle() {
    if (isInUse()) {
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it " + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}

/**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized(sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

recycle()方法其实调用的是recycleUnchecked()方法,在这个方法中,会将flag标记为FLAG_IN_USE,并把所有的域清空,并在消息池没有达到最大限定值的情况下,把这个对象插入消息池的表头。同样,在操作消息池的时候需要先对sPoolSync信号量加锁。
Message除了上述关键的方法以外,还有一些setter/getter、toString()方法、设置消息flag、判断消息flag和实现Parceable接口的方法,具体不再分析,感兴趣的同学可以自行去查看。


2.MessageQueue

MessageQueue是消息处理队列,在异步消息处理中,需要调用enqueueMessage()方法将消息入队到MessageQueue中,然后通过next()方法取出,交给Handler去处理消息。MessageQueue的类由final修饰,禁止对其进行继承、修改。

成员变量

下面来看MessageQueue中定义的成员变量变量:

// True if the message queue can be quit.
private final boolean mQuitAllowed;

@SuppressWarnings("unused") private long mPtr; // used by native code
Message mMessages;
private final ArrayList < IdleHandler > mIdleHandlers = new ArrayList < IdleHandler > ();
private SparseArray < FileDescriptorRecord > mFileDescriptorRecords;
private IdleHandler[] mPendingIdleHandlers;
private boolean mQuitting;
// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
private boolean mBlocked;

// The next barrier token.
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
private int mNextBarrierToken;
  • mQuitAllowed: 是否允许退出,主线程的MessageQueue在初始化的时候赋值为true,在退出的时候会对其进行判断,如果不允许退出,则抛出异常。
  • mPtr:native层使用的code,暂不做分析;
  • mMessage: 消息队列链表的表头,存储入队的消息,取消息的时候也是从它里边取消息;
  • mIdleHandlers :存放IdleHandler的AarrayList,在消息队列中没有消息或者要处理的消息是在未来才被执行时,会去遍历执行mIdleHandlers中的queueIdle()方法;
  • mPendingIdleHandlers:mIdleHandlers中的IdleHandler要执行时,会先存放在mPendingIdleHandler中,真正得到执行的其实是这个数组中的IdleHandler;
  • mQuitting:标识MessageQueue是否在执行退出,消息队列在退出状态时不可以再入队消息,否则会报出异常
IdleHandler
public static interface IdleHandler {
    boolean queueIdle();
}

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized(this) {
        mIdleHandlers.add(handler);
    }
}

public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized(this) {
        mIdleHandlers.remove(handler);
    }
}
  

从上述部分源码知道,IdleHandler是一个接口,可以通过addIdleHandler和removeHandler往mIdleHandlers中添加和移除IdleHandler的实现类的实例。

构造方法
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

MessageQueue的构造函数需要传入quitAllowed参数,代表是否允许MessageQueue退出。

next()方法
//取出下一个入队的消息
Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    //需要处理的IdleHandler的数量
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    //死循环去拿Message,直到返回一个Message,或者MessageQueue退出
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized(this) {
            // Try to retrieve the next message.  Return if found.
            //去拿一个消息,拿到后return
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            //拿到队头,如果拿到的是一个屏障
            //(或者叫拦截器,target一定为null,arg1里保存着屏障的token),
            //则往后遍历队列,直到拿到一个异步消息
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while ( msg != null && ! msg . isAsynchronous ());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    //如果拿到的消息的执行时间未到,更新nextPollTimeoutMills
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        //preMsg不为空,说明此时队列头是一个barrier,msg此时是异步消息。
                        //preMsg是msg的前驱节点,把msg从队列中删除
                        prevMsg.next = msg.next;
                    } else {
                        //此时Msg依旧为队头,所以只需要将头结点删掉即可
                        mMessages = msg.next;
                    }
                    //将取出的消息的next域置为null
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    //把消息msg打上FLAG_IN_USE操作,使用的是位或运算
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            //如果队列为空或者队头的消息没有到执行时间(在未来执行),
            //则执行idleHandlers的queueIdle()
            if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                //没有IdleHandlers需要被执行,则继续for循环,直到拿到消息
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            //把mIdleHanders的内容复制到mPendingIdleHandlers的数组中,等待被执行
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        //遍历pendingIdleHandler数组,依次执行IdleHandler.queueIdle()方法
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler
            boolean keep = false;
            try {
                //执行queueIdle()
                keep = idler.queueIdle();
            } catch(Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            //如果queueIdle()的执行结果为false,
            //则把对应的IdleHandler从mIdleHandlers中移除,
            //否则,下次队列空闲的时候,还会把mIdleHandlers的
            //内容复制到mPendingIdleHandlers中再次执行;
            if (!keep) {
                synchronized(this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        //执行完以后把变量置0
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

针对next()方法需要说明以下几点:

  • next方法是从消息队列中取出一个可以被执行的消息,并将其从队列中删除;如果队列为空,或者队头的消息还未到执行时间(需要在未来才执行),则会执行mIdleHandlers中的空闲队列IdleHandler.queueIdle()方法;
  • next()方法在执行时,如果遇到当前队列确实为空时,会去执行空闲队列,但是不会退出循环,也不会返回null,直到返回一个Messsage对象,或者MessageQueue退出,所以可以根据next()返回的null来判断MessageQueue是否退出了;
  • IdleHandler.queueIdle()方法的返回值代表是否要保留这个IdleHandler,true为保留,再下次执行空闲队列时会再次被执行,除非手动remove掉这个IdleHandler;false为不保留,执行一次则会被移除。可以利用这个来优化页面启动,把页面启动时比较复杂的逻辑利用IdleHandler去做,这样可以让主线程先处理完UI相关的事件后再去处理负责的逻辑,可以减少页面启动白屏时间。
  • Android消息处理机制中增加了Barrier机制,称作屏障或者拦截器、监视器,设置它的目的在于区分同步消息和异步消息。为了让View能够快速的布局和绘制,当View在绘制和布局时会向Looper中添加了Barrier,这样后续的消息队列中的同步的消息将不会被执行,以免会影响到UI绘制,但是只有异步消息才能被执行。当不设置Barrier时,消息的同步和异步没有任何区别,都是依次按照when的时间先后被执行,但是假如Barrier时,队列中就只执行异步消息,直到我们调用removeBarrier移除了这个Barrier。
  • 消息默认是同步消息(flag默认是0,1<<1代表消息是异步消息),调用Message的setAsynchronous可以把消息变为异步消息,但是这个方法是Hide的,只能由Handler去调用,在构造Handler时,需要传入是否为异步消息的变量,具体源码下边分析;
  • 针对队列的操作,进行了同步操作,在操作队列之前,先进行了加锁,所以MessageQueue是线程安全的消息队列;
enqueueMessage()方法
boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
           //入队的消息的target,必须不为空,否则会抛异常
            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) {
                 //如果消息队列在退出状态 ,则直接回收消息,返回false
                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;
            }
           //把消息标记为在使用状态,设置when
            msg.markInUse();
            msg.when = when;
           //此时p指向链表头
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                //如果队列为空或者when等于0,或者when小于
                //队头Message的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的前驱节点,依次遍历
                    prev = p;
                    p = p.next;
                    //当p已经到队尾或者找到一个节点msg.when < p.when时退出循环
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
               //链表插入操作,把msg插入到p节点前边,并把p的前驱节点的next改为msg
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        //插入成功,返回true
        return true;
    }

针对消息入队函数enqueueMessage()方法,需要说明以下几点:

  • 该方法对链表队列操作时,依然是进行了加锁同步,所以是线程安全的;
  • 返回值true代表入队成功,false代表入队失败,在队列已经退出状态时再次入队消息会返回false;
  • 队列是根据when值(也就是所谓的消息执行时间)大小组成的一个升序链表,插入也必须找到合适的节点进行插入,所以next遍历消息的时候,只需要看队头消息的when是否到执行时间就可以判断是否需要执行mIdleHandlers里的空闲队列;
quit()方法
void quit(boolean safe) {
    if (!mQuitAllowed) {
        //如果不允许退出,调用该方法,会抛出异常
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    //对自身进行加锁同步
    synchronized(this) {
        //如果已经是退出状态了,再次退出,直接返回
        if (mQuitting) {
            return;
        }
        //把mQuitting置为true,标识队列已经退出。一旦队列
        //是退出状态,则无法再次使用,除非再次new一个MessageQueue
        mQuitting = true;
        //参数safe标识是否为安全退出,ture会将还未到执行时间的Message
        //(在未来时刻执行)remove掉并清除Message的内容;false会移除
        //全部Message并清除Message的内容
        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }

        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}

private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {
        //如果队头的执行时间已经是在未来时刻了,则直接调用
        //removeAllMessagesLocked()方法清除所有的消息
        if (p.when > now) {
            removeAllMessagesLocked();
        } else {
            Message n;
            //for循环找出Message的执行时间大于now的节点,
            //退出循环
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    break;
                }
                p = n;
            }
            p.next = null;
            //while循环遍历,调用Message.recycleUncheked()方法
            //清除消息内容,并从队列中删除
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();
            } while ( n != null );
        }
    }
}
//循环遍历,清除消息队列中所有Message的内容并从队列中删除
private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}

  • 参数safe表示是否需要安全退出,true则会把未执行(Message.when > now)的消息全部清除并删除,待执行的Message会继续执行完;false会直接把队列中所有的Message给清除并删除;
  • 退出操作依然进行了同步操作,属于线程安全的;
  • 如果队列quitAllowed为false,表示不允许退出,如果强行退出,会抛出异常;一旦退出,队列无法再次被使用;

MessageQueue里还有判断队列中是否有特定消息和移除特定消息的一系列重载方法、一些native方法等,有兴趣的同学可以去看下,这里不再分析。


3.Handler

Handler是消息分发对象,可以发送Message和处理Message。

成员变量
//j是否发现潜在的内存泄漏,默认为false
private static final boolean FIND_POTENTIAL_LEAKS = false;
private static final String TAG = "Handler";
//静态全局变量,主线程的Handler
private static Handler MAIN_THREAD_HANDLER = null;

//绑定的Looper对象
final Looper mLooper;
//绑定的MessageQueue消息队列
final MessageQueue mQueue;
//回调接口的实现
final Callback mCallback;
//是否是异步的,如果是异步的,在发送消息的时候,
//会调用Message.setAsynchronous(true)把消息设为异步消息
final boolean mAsynchronous;
IMessenger mMessenger;
  • 一个Handler与一个Looper和MessageQueue相关联
Callback、dispatchMessage()方法
public interface Callback {
    /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
    public boolean handleMessage(Message msg);
}

/**
     * Subclasses must implement this to receive messages.
     */
public void handleMessage(Message msg) {}

/**
     * Handle system messages here.
     */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}
  • Callback是一个接口,可以在Handler对象初始化的时候把一个Callback对象传进去;
  • 可以看出dispatchMessage处理消息的优先级顺序依次是
    • 如果Message本身设置了Callback(是一个Runnable对象),则把Message交给Message自身的Callback来处理;
    • 否则,如果Handler的设置了Callback回调(是Handler.Callback的实现类),则由Handler的Callback来处理,并返回一个返回值,表示是否消耗掉这个消息,不进行后续处理(有点类似于消息传递机制的返回值);
    • 如果上一步的返回值是false,则表示不消耗这个消息,交给Handler本身的handleMessage方法来处理,这个方法默认实现是空方法,需要自行覆写去处理消息;
构造函数
public Handler() {
    this(null, false);
}

public Handler(Callback callback) {
    this(callback, false);
}

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

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

public Handler(boolean async) {
    this(null, async);
}

public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        //如果FIND_POTENTIAL_LEAKS设为true,如果Handler自身对象是匿名类、内部成员
        //类和本地类而且不是static的时候,会给出警告,可能有潜在的内存泄漏风险
        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());
        }
    }
    //调用Looper.myLooper()方法获得一个Looper对象,源码下文分析
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
    }
    //MessageQueue直接拿Looper对象里的mQueue对象
    mQueue = mLooper.mQueue;
    mCallback = callback;
    //设置是否异步
    mAsynchronous = async;
}

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
  • 第6个重载方法,调用Looper.myLooper()方法获得一个Looper对象,并从Looper对象里直接拿mQueue赋值给自身对象的mQueue;
  • 构造方法重载中,有可以直接设置Looper对象的,不一定是非得通过Looper.myLooper()来初始化mLooper对象,也有可能是外部获得了直接传进来;
  • 如果FIND_POTENTIAL_LEAKS设为true,如果Handler自身对象是匿名类、内部成员类和本地类而且不是static的时候,会给出警告,可能有潜在的内存泄漏风险,所以建议Handler如果是内部类的时候,建议使用静态内部类;
obtainMessage()方法
public final Message obtainMessage() {
    return Message.obtain(this);
}
  • Handler中有一系列obtanMessage()重载方法,只是传的参数不同,其实最后调用的还是Message.obtain()方法,需要注意的是,在Message中,obtain()方法是静态方法,在Handler中,是非静态的,需要通过具体的Handler实例对象来获得,但是禁止子类进行覆写;
post(Runnable r)相关方法

在Handler中,可以发送一个Runnable对象,也可以发送一个Message对象,现在我们先来分析发送Runnable对象的相关方法:

//立即post一个Runnable到MessageQueue中,此时Runnable被包装正Message后放在MessageQueue队列中(when == 当前系统时间,可能是队头,也可能不是队头,队列中已经有when值小于当前时间的Message)
public final boolean post(Runnable r) {
    return sendMessageDelayed(getPostMessage(r), 0);
}
//Runnable包装成Message后加入到MessageQueue中,但此时
//Message.when=uptimeMillis,uptimeMills是消息的执行时间,
//即指定了具体的执行系统时间
public final boolean postAtTime(Runnable r, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
//同上,只是又传入了token对象,存储在Message.obj中
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
//Runnable包装成Message后加入到MessageQueue中,
//但是Message.when等于当前时间+delayMillis,
//表示延迟delayMills后执行
public final boolean postDelayed(Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}
//Runnable包装成Message后加入到MessageQueue中,此时when=0,
//一定是在MessageQueue的队头
public final boolean postAtFrontOfQueue(Runnable r) {
    return sendMessageAtFrontOfQueue(getPostMessage(r));
}
//把Runnable对象包装成Message对象,可见只是把Runnable对象
//赋值给了Message的callback域
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
//上述方法的重载方法,把token赋值给了Message的obj域,
//可以用这个方法进行传值
private static Message getPostMessage(Runnable r, Object token) {
    Message m = Message.obtain();
    m.obj = token;
    m.callback = r;
    return m;
}
//延迟发送,把当前时间加上延迟时间后调用了sendMessageAtTime()方法
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//发送消息的方法,对queue判空后,调用enqueueMessage进行实际入队
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);
}
//实际对消息入队的方法,在该方法中,会把Message的target域进行赋值,
//如果mAsynchronous是true,则会调用setter方法把消息设置为异步消息,
//调用的入队方法其实是调用的MessageQueue的enqueueMessage方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
  • post相关方法传入的是Runnable对象,但是最终都会被包装成Message对象,通过obtain()获取一个Message对象,即把Runnable对象传入Message的callback域,并可以传入一个Object对象到Message的obj域进行传值;
  • 发送消息其实就是把消息入队,实际的入队操作其实是调用的MessageQueue对象的enqueueMessage操作,一系列的调用方法只是在计算Message的when值;
  • 调用post()后,不一定消息就在MessageQueue的队头,也就意味着post的Runnable不一定会立即得到执行,因为此时消息的when值是当前的系统时间,不能保证它前边没有在等待执行的消息;如果想要立即被执行,可以调用postAtFrontOfQueue(Runnable r)方法;
  • 在实际的入队方法enqueueMessage()中,会将传入的Message对象的target对象赋值为自身的引用,如果mAsynchronous是true,则会调用setter方法把消息设置为异步消息,最后调用的MessageQueue的enqueueMessage方法对消息进行入队操作;上边讲到的MessageQueue中异步消息标志位,就是在这设置位ture的;
sendMessage(Message msg)相关方法
public final boolean sendMessage(Message msg) {
    return sendMessageDelayed(msg, 0);
}

public final boolean sendEmptyMessage(int what) {
    return sendEmptyMessageDelayed(what, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}
//...
//省略其他方法,基本上跟post系列方法是一一对应的
  • sendMessage方法的参数是Message对象,跟post系列方法的区别就是不用把Runnable封装成Message,其实调用链最后都是 sendMessageAtTime() -> enqueueMessage();
  • sendMessage方法和post方法的区别在于对消息的处理不同:
    • post一个Runnable的时候,由于把Runnable对象赋值给了callback域,所以在Looper调用dispatchMessage()方法的时候,这个Runnable对象直接得到了执行;而sendMessage系列的方法把消息发送后,在Looper调用dispatchMessage()方法的时候,由于Message的callback域可能为空,需要给Handler设置callback或者覆写handlerMessage(Message msg)来处理消息;
    • 我的理解是,post系列方法侧重是想把某些操作给post到特定线程去执行,而sendMessage系列的方法是想把值传到特定线程,然后针对特定值去做某些处理;所以,post方法弱化传值的操作,sendMessage方法强调的是传值并对传过去的值进行操作;
runWithScissors(final Runnable r, long timeout)

除了post(Runnable r)系列方法和sendMessage(Message msg)系列方法可以发送消息外,还有runWithScissors可以发送消息(Runnable对象)。

/**
     * Runs the specified task synchronously.
     * <p>
     * If the current thread is the same as the handler thread, then the runnable
     * runs immediately without being enqueued.  Otherwise, posts the runnable
     * to the handler and waits for it to complete before returning.
     * </p><p>
     * This method is dangerous!  Improper use can result in deadlocks.
     * Never call this method while any locks are held or use it in a
     * possibly re-entrant manner.
     * </p><p>
     * This method is occasionally useful in situations where a background thread
     * must synchronously await completion of a task that must run on the
     * handler's thread.  However, this problem is often a symptom of bad design.
     * Consider improving the design (if possible) before resorting to this method.
     * </p><p>
     * One example of where you might want to use this method is when you just
     * set up a Handler thread and need to perform some initialization steps on
     * it before continuing execution.
     * </p><p>
     * If timeout occurs then this method returns <code>false</code> but the runnable
     * will remain posted on the handler and may already be in progress or
     * complete at a later time.
     * </p><p>
     * When using this method, be sure to use {@link Looper#quitSafely} when
     * quitting the looper.  Otherwise {@link #runWithScissors} may hang indefinitely.
     * (TODO: We should fix this by making MessageQueue aware of blocking runnables.)
     * </p>
     *
     * @param r The Runnable that will be executed synchronously.
     * @param timeout The timeout in milliseconds, or 0 to wait indefinitely.
     *
     * @return Returns true if the Runnable was successfully executed.
     *         Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     *
     * @hide This method is prone to abuse and should probably not be in the API.
     * If we ever do make it part of the API, we might want to rename it to something
     * less funny like runUnsafe().
     */
public final boolean runWithScissors(final Runnable r, long timeout) {
    if (r == null) {
        throw new IllegalArgumentException("runnable must not be null");
    }
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout must be non-negative");
    }
    //如果当前的线程和Handler所在的线程是同一个,则直接运行Runnable对象,
    //而不需要再进行入队-排队-出队的操作;
    if (Looper.myLooper() == mLooper) {
        r.run();
        return true;
    }
    //如果当前线程和Handler的线程不在同一个线程,
    //则会调用BlockingRunnable的postAndWait()方法
    BlockingRunnable br = new BlockingRunnable(r);
    return br.postAndWait(this, timeout);
}

//静态内部私有常量类,实现Runnable接口
private static final class BlockingRunnable implements Runnable {
    //真正需要执行的Rnnable对象
    private final Runnable mTask;
    //标识是否执行完毕
    private boolean mDone;

    public BlockingRunnable(Runnable task) {
        mTask = task;
    }

    @Override public void run() {
        try {
            mTask.run(); //运行mTask.Runnalbe
        } finally {
            synchronized(this) {
                //执行完以后,会把mDone标志位置为true,并唤醒其他阻塞线程
                mDone = true;
                notifyAll();
            }
        }
    }

    public boolean postAndWait(Handler handler, long timeout) {
        //把自身对象通过传入的handler.post()方法进行入队操作,
        //入队成功返回true,失败返回false;
        if (!handler.post(this)) {
            return false;
        }
        //加锁同步
        synchronized(this) {
            if (timeout > 0) {
                //计算超时时间,如果超时后还没有执行完,返回false,
                //并执行run中finally中的操作,
                //即把标志位置为true,并唤醒其他线程,此时run可能还没有执行完
                final long expirationTime = SystemClock.uptimeMillis() + timeout;
                while (!mDone) {
                    long delay = expirationTime - SystemClock.uptimeMillis();
                    if (delay <= 0) {
                        return false; // timeout
                    }
                    try {
                        wait(delay);
                    } catch(InterruptedException ex) {}
                }
            } else {
                //如果timeout小于等于0,会阻塞handler所在线程,
                //直到run方法内的finallay块被执行(会一直等到run执行完毕)
                while (!mDone) {
                    try {
                        wait();
                    } catch(InterruptedException ex) {}
                }
            }
        }
        return true;
    }
} 
  • 这个方法是消息的同步执行方法,在执行的时候会阻塞handler所在线程。执行时可以设置超时时间,如果超时后,直接唤醒线程,停止执行;如果超时时间小于等于0,则会一直等到run执行完毕,finally块得到执行才能唤醒线程;
  • runWithScissors()方法的返回值,true代表Runnable对象被正确执行,false代表执行失败;
  • 源码注释写的很清楚,这个方法非常的危险,不建议使用,因为不当的使用可能会导致死锁。不要在持有任何锁时调用此方法,也不要以可重入的方式使用它。
  • 在后台线程必须同步等待在处理程序的线程上运行的任务完成的情况下,这种方法有时很有用。然而,这个问题通常是糟糕设计的症状。在使用这种方法之前,考虑改进设计(如果可能的话)。
getMain()方法
/** @hide */
@NonNull public static Handler getMain() {
    if (MAIN_THREAD_HANDLER == null) {
        MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
    }
    return MAIN_THREAD_HANDLER;
}

/** @hide */
@NonNull public static Handler mainIfNull(@Nullable Handler handler) {
    return handler == null ? getMain() : handler;
}
  • 通过getMain()方法可以拿到主线程的Handler;
  • mainIfNull(Handler hander)方法,相当于是对传入的handler进行判空,如果为空,则返回主线程的Handler;

Handler中还有removeCallbacks()、removeMessage()、hasMessage()、toString()方法以及其重载方法,源码比较简单,这里就不做分析了,感兴趣的同学可以自行去查看源码。


4. Looper

Looper内部有一个消息队列MessageQueue,循环从MessageQueue中不断取出Message并对其进行分发。一个线程只能有一个Looper,由于Looper是线程局部变量,所以每个Looper中又有各自的MessageQueue。

成员变量
// sThreadLocal.get() will return null unless you've called prepare().
//静态域,线程局部变量,每个线程中有一个独立Looper对象。
//如果不调用prepare()方法,sThreadLocal.get()返回null
static final ThreadLocal < Looper > sThreadLocal = new ThreadLocal < Looper > ();
//静态域,主线程的Looper,由Looper class来维护
private static Looper sMainLooper; // guarded by Looper.class
//消息队列,每个Looper中有一个MessageQueue
final MessageQueue mQueue;
//Looper对象自身所在线程
final Thread mThread;
//打印对象及Tag
private Printer mLogging;
private long mTraceTag;
/* If set, the looper will show a warning log if a message dispatch takes longer than time. */
private long mSlowDispatchThresholdMs;
  • sThreadLocal是一个线程本地变量,每一个线程独自维护各自Looper的值,保证了每个线程都有一个Looper。关于ThreadLocal线程本地变量的原理这里不多展开讲解了,不了解的同学可以自行查阅资料。这里需要注意,Java的ThreadLocal和Android的ThreadLocal的实现是不一样的;
  • 由于sThreadLocal是线程局部变量,Looper中所有的非静态变量在每个线程都有单独的副本,所以每个线程都有各自的MessageQueue;
  • 初始化的时候,会把Looper所在线程信息记录在mThread变量中;
构造方法
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
  • Looper的构造方法是私有的,不能new出一个Looper,而是应该调用prepare()方法来获得一个Looper对象;
  • 构造方法很简单,new了一个MessageQueue,并把当前线程信息记录下来。传入的参数quitAllowed代表是否允许MessageQueue退出,如果不允许退出,调用MessageQueue的quit()方法时会抛出异常;
prepare()方法
//公有方法,开发者只能调用这个方法进行Looper的初始化,
//可见开发者得到的Looper中的MessageQueue,是允许退出的
public static void prepare() {
    prepare(true);
}
//私有方法,开发者无法调用,而且对同一个线程重复调用
//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));
}
//主线程的Looper初始化,虽然是公有方法,我们无法调用,
//因为系统启动的时候已经调用过了,如果再次调用,会抛异常
public static void prepareMainLooper() {
    prepare(false);
    synchronized(Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        //把得到的主线程Looper赋值给sMainLooper 
        sMainLooper = myLooper();
    }
}
  • 调用Looper.prepare()会为当前线程初始化一个Looper对象,Looper对象又会初始化一个MessageQueue对象;
  • 主线程初始化Looper时创建的MessageQueue对象是禁止退出操作的,而其他线程初始化时Looper创建的MessagQueue是允许退出操作的。
  • 一个线程只能调用一次prepare()方法,否则会抛出异常,所以在prepare()之前,应该先调用myLooper()方法判断是否为空;
  • 主线程的Looper在ActivityThread中的main()方法进行初始化的,
public static@Nullable Looper myLooper() {
    return sThreadLocal.get();
}
loop()方法
 //代码省去打印等其他无关逻辑
public static void loop() {
    //拿到本线程的sThreadLocal变量的值
    final Looper me = myLooper();
    //如果当前线程没有调用过prepare()方法,则me为null,抛出异常
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //从me里获得本线程的MessageQueue对象
    final MessageQueue queue = me.mQueue;
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    //进入死循环,开始从MessageQueue中取出消息进行处理
    for (;;) {
        //取出消息,可能被阻塞
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        try {
            //通过Message对象中的targe域获得发送该消息的Handler,
            //调用它的dispatchMessage()方法来处理对象
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        final long newIdent = Binder.clearCallingIdentity();
        msg.recycleUnchecked();
    }
}
  • 拿到本线程的sThreadLocal变量的值,如果当前线程没有调用过prepare()方法,则me为null,抛出异常;
  • 从me里获得本线程的MessageQueue对象,进入死循环,开始从MessageQueue中取出消息进行处理;
  • 通过Message对象中的targe域获得发送该消息的Handler,调用它的dispatchMessage()方法来处理对象,这也是为什么在入队的时候,会对Message的target域进行判空;
  • 当MessageQueue的next方法返回为null时,意味着MessageQueue已经退出了(next()方法有分析),此时会退出死循环,所以MessageQueue的退出,也会导致Looper的循环结束;

Looper还有quit(),quitSafely(),toString()等其他方法,感兴趣的同学可以自行查看源码;


5. 利用异步消息处理机制,主线程与子线程之间的通信

通过上述源码的详细分析,可以知道:

  • 一个线程只能通过Looper.prepare()(建议不确定子线程是否已经调过了,调用它之前可以先调用Looper.myLooper()判断下,否则会抛异常)来产生一个Looper对象,同时也绑定了一个属于该线程的MessageQueue对象;
  • 一个Handler在构造函数中,会获取当前线程中的Looper对象,并从Looper对象中获取MessageQueue对象,所以在new一个Handler之前,要保证Looper.prepare()已经调用过,否则会抛出异常;
  • 一个消息,最终是在哪个线程被处理的,取决于这个Handler在哪个线程(通过new一个Handler方式得到的Looper和MessageQueue是在同一个线程的),最后分发Message被执行的是在Looper.loop()方法的 msg.target.dispatchMessage(msg)语句来处理的,所以消息被抛给Handler去处理,也就是说消息被哪个线程的Handler发送,就会在哪个线程被执行;
  • 基于上述思路,主线程与子线程之间的通信就简单明了了,子线程向主线程进行通信,只需直接在主线程new一个Handler(在主线程中不可以调用Looper.prepare()方法),然后把消息发送过去就行:
private static Handler handler = new Handler() {
    //主线程处理消息
    @Override public void handleMessage(Message msg) {
        //主线程根据Message进行逻辑处理
    }
};
class WorkThreadRunnable implements Runnable {
    //子线程发送相关消息
    @Override public void run() {
        Message message = Message.obtain();
        //在子线程中用在主线程中创建的Hander发送消息即可
        handler.sendMessage(message);
    }
}
  • 主线程向子线程进行通信,有两种方式,一种是需要在子线程先调用Looper.prepare()方法初始化一个Looper和MessageQueue,然后在子线程创建一个Handler,并在主线程使用这个handler进行发送消息,子线程接收到消息后进行处理即可:
 private Handler handler;

@Override 
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_handler_main_to_child);
    button = (Button) findViewById(R.id.button);
    button.setOnClickListener(new View.OnClickListener() {@Override public void onClick(View view) {
            Message message = Message.obtain();
            handler.sendMessage(message);
        }
    });
    new Thread(new WorkThreadRunnable()).start();

}

class WorkThreadRunnable implements Runnable {@Override public void run() {
        Looper.prepare();
        handler = new Handler() {@Override public void handleMessage(Message msg) {
                //接收到消息后,进行处理
            }
        };
        Looper.loop();
    }
}
  • 主线程向子线程进行通信的时候,Handler也不一定非得是在子线程进行创建,另一种方式是在子线程创建Looper,在主线程new Handler的时候,把子线程的Looper当参数传入,这样新建的Handler与Looper所在线程就进行了绑定,这样,主线程通过这个Handler依然可以向子线程进行通信:
     //创建子线程
class WorkThread extends Thread {
    private Looper looper; //取出该子线程的Looper
    public void run() {
        Looper.prepare(); //创建该子线程的Looper
        looper = Looper.myLooper(); //取出该子线程的Looper
        Looper.loop(); //只要调用了该方法才能不断循环取出消息
    }
}

private Handler mHandler;

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    thread = new WorkThread();
    thread.start();
    //创建Handler时把looper做入参,把Handler与Looper绑定在一起
    mHandler = new Handler(thread.looper) {
        public void handleMessage(android.os.Message msg) {
            //在子线程中处理消息的逻辑
        };
    };
    mHandler.senMessage(Message.obtain());
}
  • 利用HandlerThread也可以实现线程间的通信,它其实是Thread和消息处理机制的一种封装,后续文章再进行分析

6. 异步消息处理机制使用不当可能导致的问题
内存泄漏

通常情况下,我们会在一个Activity/Fragment中使用消息处理机制进行线程间的通信,如果Handler为非静态内部类,则会引用外部类对象。当Activity/Fragment finish时,Handler可能并未执行完,则会引起Activity/Fragment的内存泄漏。故而所有调用Handler的地方,都使用静态内部类。如果Handler需要访问Activity/Fragemnt非静态成员/非静态方法,可以让Handler持有外部对象的弱引用;

private static class HandlerDemo extends Handler{
    private final WeakReference<Activity> mActivity;
    
    public HandlerDemo(Activvity mActivity) {
        this.mActivity = new WeakRefrence<Activity>(mActivity);
    }

   @Override
    public void handleMessage(Message msg) {
          //处理消息,在访问mActivity的时候要先进行判空
    }
}
资源被释放而导致的异常

当Activity/Fragment调用onDestroy()等回调方法后,会释放掉一些资源,而在Handler执行handleMessage()方法时,在访问Activity/Fragment的成员变量/方法时,可能会由于资源被释放而导致空异常。所以除了在对Activity/Fragment进行判空以外,还要对访问到的成员变量等进行判空操作,并且在onDestory()方法里把消息队列中的消息remove。

  @Override
  protected onDestroy() {\
      super.onDestory();
      //移除post的Runnable对象
      handler.removeCallback(postRunnable);
      //移除send的Message对象
      handler.removeMessage(what);
  }

7. 总结

通过上述源码分析,我们理解了Message、MessageQueue、Handler和Looper的实现细节,现在应该对Android异步消息处理机制的实现的细节、通信原理和可能带来问题都清楚了,如果还有什么疑问,大家可以自行查看源码。如果有写的不对的地方,欢迎进行指正和交流。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容