前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Android] Handler消息传递机制

[Android] Handler消息传递机制

作者头像
wOw
发布2018-09-18 14:55:21
2.3K0
发布2018-09-18 14:55:21
举报
文章被收录于专栏:wOw的Android小站wOw的Android小站

今天看文章的时候看到这么一句话:

UI线程是从ActivityThread运行的,在该类的main()方法中已经使用了Looper.prepareMainLooper()为该线程添加了Looper对象,已经为该线程创建了消息队列,是自带秘书光环的。因此,我们才可以在Activity中去定义Handler对象,因为创建Handler对象时其线程必须已经创建了消息队列,装卸工得配运输带要不然没法干活。而普通的Thread则没有默认创建消息队列,所以不能直接在Thread中直接定义Handler,这个就是我们不懂程序运行原理导致的困惑。

其实这块知识我都看过,但是读完这段话有些地方还是让我回想了一小会儿。想完就觉着既然回想了一遍,不如整理一篇博客出来好了。

经验之谈

在Android中经常会创建线程做一些耗时的事情,结束后会更新UI线程。一般代码这样写:

代码语言:javascript
复制
private Handler uiHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                break;
        }
    }
};

    new Thread(new Runnable() {
        @Override
        public void run() {
            Message msg = uiHandler.obtainMessage();
            msg.what = 1;
            uiHandler.sendMessage(msg);
        }
    }).start();

即在主线程中创建Handler接收处理消息,在子线程用handler发送消息。

上面的Handler是在主线程中创建的,当我们在子线程创建一个Handler时,运行程序会报错:

代码语言:javascript
复制
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
    at android.os.Handler.<init>(Handler.java:203)
	at android.os.Handler.<init>(Handler.java:117)

这是因为主线程是自带Looper的,而子线程需要我们自己添加Looper。

代码语言:javascript
复制
class LooperThread extends Thread {
	public Handler mHandler;

	public void run() {
  		Looper.prepare();

		mHandler = new Handler() {
			public void handleMessage(Message msg) {
			// process incoming messages here
			}
		};

		Looper.loop();
      	// loop就进入死循环,loop之后的代码不会执行,除非loop结束。
	}
}

看到这里,虽然说代码会写,功能会实现,但是很多问题却无法准确回答。

所以后面就跟着源码,把这些关系理清楚。

android.os.Message

一个包含描述信息和任意数据的可以发送给Handler的消息对象。它包含两个int域和一个object域供直接使用(省去alloc操作)。

它虽然有构造函数,但还是推荐使用Message.obtain()获得Message实例,或者使用Handler.obtainMessage()方法从消息回收池获取消息对象。

代码语言:javascript
复制
public final Message obtainMessage()
{
        return Message.obtain(this);
}

Handler调用的方法就是Message自己的obtain。Message源码如下:

代码语言:javascript
复制
public final class Message implements Parcelable {
    // 标识一个message。因为每个Handler都有自己的命名空间,故不必担心这个值和其他handler冲突
    public int what;

   // 如果只要存储int数据,用arg1和arg2即可,不需要构建Bundle对象做setData
    public int arg1;
    public int arg2;

    // 任意的一个对象。当用Messenger在进程间发消息时,如果它包含一个framework类的Parcelable对象,则它是非空的。对于其他数据使用Bundle.SetData即可。
    public Object obj;

    // 负责回复消息的Messenger,可选。使用取决于发送者和接收者
    public Messenger replyTo;

    // 消息uid标识,只在被Messenger传递消息时使用,平时是-1
    public int sendingUid = -1;

    // 表示message正被使用。
    // 该标志在消息入队完成后设置,并在传送后包括到回收之后都保持设置状态。 只有在创建或获取新消息时才会清除该标志,因为这是允许应用程序修改消息内容的唯一时间。所以在in use状态下不可以对一个message进行enqueue和recycle操作
    /*package*/ static final int FLAG_IN_USE = 1 << 0;

    // 标识消息是否是异步的
    /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;

    // 需要在CopyFrom方法清除的标识,默认是FLAG_IN_USE
    /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;

    /*package*/ int flags;
    /*package*/ long when;
  
	// 关键数据
    /*package*/ Bundle data;

  	// 发送和处理消息关联的Handler
    /*package*/ Handler target;

    /*package*/ Runnable callback;

    // 有时会用链表关联下一个message
    /*package*/ Message next;

  	// 同步锁使用的对象
    private static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0; //obtain时-1,recycleUnchecked后+1

    private static final int MAX_POOL_SIZE = 50;
	private static boolean gCheckRecycle = true; // 只在recycle使用
    

    // 从消息回收池返回一个新的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();
    }

    // 先obtain一个message,然后从参数message拷贝一份数据
    public static Message obtain(Message orig) {
        Message m = obtain(); 
        m.what = orig.what;
        m.arg1 = orig.arg1;
        m.arg2 = orig.arg2;
        m.obj = orig.obj;
        m.replyTo = orig.replyTo;
        m.sendingUid = orig.sendingUid;
        if (orig.data != null) {
            m.data = new Bundle(orig.data);
        }
        m.target = orig.target;
        m.callback = orig.callback;
        return m;
    }

    // 先obtain一个message,然后设置要传递的handler
    public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;
        return m;
    }
    // 下面的不用介绍了,和上面类似。
    public static Message obtain(Handler h, Runnable callback) {}
    public static Message obtain(Handler h, int what) {}
    public static Message obtain(Handler h, int what, Object obj) {}
    public static Message obtain(Handler h, int what, int arg1, int arg2) {}
    public static Message obtain(Handler h, int what,
            int arg1, int arg2, Object obj) {}

    /** @hide */
    public static void updateCheckRecycle(int targetSdkVersion) {
        if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
            gCheckRecycle = false; // 只有版本低于lollipop的在recycle时不会抛出异常信息
        }
    }

    // 返回一个message实例到global pool
    // 调用这个方法后就不能再访问该消息了,因为它已经被有效释放。
  	// 对于正在enqueued或者被delivered到Handler的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();
    }

    // 回收可能正在in-use状态的消息。 在处理队列消息时由MessageQueue和Looper在内部使用。
    void recycleUnchecked() {
        // 当其保持在recycled object pool时将message标记为in use,并清除其他信息
        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++;
            }
        }
    }

    // 浅拷贝
    public void copyFrom(Message o) {
        this.flags = o.flags & ~FLAGS_TO_CLEAR_ON_COPY_FROM;
        // ...省略...
    }

    // 返回消息目标delivery时间  毫秒
    public long getWhen() { return when; }
    public void setTarget(Handler target) { this.target = target; }
    public Handler getTarget() { return target; }
    public Runnable getCallback() { return callback; }

    // data为空会创建新的Bundle
    public Bundle getData() {
        if (data == null) {
            data = new Bundle();
        }

        return data;
    }
    // data为空就返回空
    public Bundle peekData() { return data; }
    public void setData(Bundle data) { this.data = data; }

    // 把自己发送给目标Handler
    public void sendToTarget() { target.sendMessage(this); }

    // true则为异步的。意味着他不是Looper synchronization barriers
    public boolean isAsynchronous() {
        return (flags & FLAG_ASYNCHRONOUS) != 0;
    }

    // 某些操作(如视图无效)可能会在Looper的消息队列中引入同步障碍,以防止后续消息被传递,直到满足某些条件。
    // 在视图无效的情况下,在调用View.invalidate之后发布的消息将通过同步屏障挂起,直到下一帧准备好绘制为止。
    // 同步屏障确保在恢复之前完全处理失效请求。
    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }

    /*package*/ boolean isInUse() { return ((flags & FLAG_IN_USE) == FLAG_IN_USE); }
    /*package*/ void markInUse() { flags |= FLAG_IN_USE; }
    
  // 剩下的省略

代码中提到的同步障碍(synchronization barriers)可以参考 [Java] CountDownLatch 与 CyclicBarrier 了解一下Barrier。

源码的注释应该很详细了,关键点再整理如下:

  • FLAG_IN_USE 标志在消息入队完成后设置,并在消息传送后包括到回收之后都保持设置状态。
  • 只有在创建或获取新消息时才会清除FLAG_IN_USE标志,因为这是允许应用程序修改消息内容的唯一时间。
  • in use状态下不可以对一个message进行enqueue和recycle操作。
  • 调用recycle后就不能再访问该消息了,因为它已经被有效释放。
  • 对于正在enqueued或者被delivered到Handler的message是不能调用recycle的。

剩下我认为比较重要的是

代码语言:javascript
复制
private static int sPoolSize = 0; //obtain时-1,recycleUnchecked后+1

如同介绍,调用obtain时计数-1,调用recycleUnchecked后计数+1。除此之外,没有其他地方为其赋值。

所以这两个方法的调用在整个消息机制里起很大的作用。obtain 方法就不说了,获取Message对象用的,recycleUnchecked 方法的调用都在什么地方呢?

首先在Message自身的recycle 方法内会执行一次。

然后在源码搜索,找到两个调用的类:MessageQueue和Looper。

Looper的调用很简单:

代码语言:javascript
复制
public static void loop() {
    final Looper me = myLooper();
    ...
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
      ...
        msg.recycleUnchecked();
    }
}

就是在loop循环的时候,从消息队列一个个取出Message,处理完的最后调用 msg.recycleUnchecked()。

所以一个消息在被 Looper 处理时或者移出队列时会被标识为 FLAG_IN_USE,然后会被加入回收的消息链表,这样我们调用 Message.obtain() 方法时就可以从回收的消息池中获取一个旧的消息,从而节约成本。

MessageQueue的调用可以找到9处,分别在:

代码语言:javascript
复制
public void removeSyncBarrier(int token){ ... }
void removeMessages(Handler h, int what, Object object) { ... }
void removeMessages(Handler h, Runnable r, Object object) { ... }
void removeCallbacksAndMessages(Handler h, Object object) { ... }
private void removeAllMessagesLocked() { ... }
private void removeAllFutureMessagesLocked() { ... }

通过函数名就知道,所有remove消息的操作都会把remove掉的消息recycle。

android.os.MessageQueue

MessageQueue是一个低级别的类,它持有一个将由Looper派发的Message列表。Message不是直接添加到MessageQueue的,而是通过与Looper关联的Handler对象的。

整理下语言就是,MessageQueue存放Message列表,Handler往队列里塞Message,Looper从队列取出Message往外发送。

代码语言:javascript
复制
public final class MessageQueue {

    // 队列是否可以退出
    private final boolean mQuitAllowed;

    @SuppressWarnings("unused")
    private long mPtr; // Native层使用

    Message mMessages;
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
    private IdleHandler[] mPendingIdleHandlers;
    private boolean mQuitting;

    // next()阻塞提示
    private boolean mBlocked;

    // 下一个屏障的token.
    // 屏障由具有空目标的消息指定,其arg1字段携带该token。
    private int mNextBarrierToken;

当前线程的MessageQueue对象是通过Looper.myQueue()获取的(这个线程必须启动Looper,才有MessageQueue):

代码语言:javascript
复制
 public static @NonNull MessageQueue myQueue() {
     return myLooper().mQueue;
 }

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

public static void prepare() {
     prepare(true);
 }

 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));
 }
代码语言:javascript
复制
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

以上是MessageQueue的构造过程。在Looper一开始的prepare阶段创建new Looper(true),然后拿到new MessageQueue(true)。

队列的功能是按顺序排列消息,实行FIFO原则。所以看一下这个In和Out。

入队方法:

代码语言:javascript
复制
boolean enqueueMessage(Message msg, long when) {
  		// 如开头介绍,消息是通过Handler塞进消息队列的,所以先判断消息的Handler是否为空
        if (msg.target == null) { 
            throw new IllegalArgumentException("Message must have a target.");
        }
  		// 回去再看Message的FLAG_IN_USE注释,明确说明inUse状态下不可以入队
        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(); // 前面异常判断完,可以入队了,标记消息为in use
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // 原来空的链表阻塞消息读取,新消息进入唤醒
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // 消息插入到队列。通常只有在队列头有屏障且这个消息是异步消息时需要唤醒队列
                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;
    }

可以看到,MessageQueue 虽然叫“消息队列”,持有的其实是一个消息链表的节点。插入消息也是以链表插入。

下面是出队

代码语言:javascript
复制
Message next() {
    // 如果Message loop退出,直接return
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        // 如果有需要过段时间再处理的消息,先调用 Binder 的方法
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // 找下一个消息  找到就return 消息
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages; // 链表头
            if (msg != null && msg.target == null) {
                // 如果消息没有 target,那它就是一个屏障,需要一直往后遍历找到第一个异步的消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // 如果这个消息还没到处理时间,就设置个时间过段时间再处理
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 拿到消息
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse(); // 标记使用
                    return msg;
                }
            } else {
                // 队列没有消息了
                nextPollTimeoutMillis = -1;
            }

            // 所有等待的消息都处理完了,处理退出消息
            if (mQuitting) {
                dispose();
                return null;
            }

            // 如果第一次idle, 获取要运行idlers的数量.
            // Idle handles仅在队列为空时或队列中的第一条消息(可能是屏障)将在未来处理时才运行。
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        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 {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        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;
    }
}

代码中出现很多IdleHandler,其定义如下:

代码语言:javascript
复制
   // 当线程block等待更多消息时,获得新消息的回调接口
public static interface IdleHandler {
       // 在消息队列没有消息并且等待更多消息时调用。
       // 返回true表示保持idle handler活跃,false表示将其移除
     	// 如果队列有消息等待在未来某时间点执行也会调用这个方法。
       boolean queueIdle();
   }

当消息队列阻塞时,就会回调到这里。

这里大概知道MessageQueue是什么就够了。

前面介绍Message时,提到Looper在Loop时处理完消息会将消息recycle掉,在prepare的时候会创建MessageQueue。而且必须是在线程中加入Looper这个线程才可以拥有MessageQueue。那么下面就看一下Looper。

android.os.Looper

Looper是对一个线程运行消息循环的类。一个线程默认是没有消息循环的,如果要创建一个,需要在线程先调用Looper.prepare,然后调用Looper.loop使其处理消息,知道循环结束。

基本上是和Handler交互处理消息循环。

代码语言:javascript
复制
public final class Looper {

    // 必须调用先prepare(),否则sThreadLocal.get()返回空
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;
    final Thread mThread;

    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;

Looper声明了其管理的消息队列,以及与之绑定的线程。

在线程里使用Looper的第一步是Looper.prepare(),看一下源码:

代码语言:javascript
复制
public static void prepare() {
    prepare(true);
}

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));
}

这里就解释了必须调用先prepare(),否则sThreadLocal.get()返回空的原因。在prepare的时候会从sThreadLocal判断这个线程是否有Looper,如果没有则创建。

ThreadLocal用于提供线程内部的局部变量,这些变量与它们的正常对象不同,每个线程访问一个单独属于自己的,独立的变量的初始副本。

通过ThreadLocal控制一个线程只有一个Looper。

Looper里还有一个和prepare相关的方法:

代码语言:javascript
复制
// 初始化当前线程为一个Looper,并标记它为一个应用程序的主Looper。应用程序的主Looper是在Android环境中构建的,所以不要自己去调用这个方法
   public static void prepareMainLooper() {
       prepare(false);
       synchronized (Looper.class) {
           if (sMainLooper != null) {
               throw new IllegalStateException("The main Looper has already been prepared.");
           }
           sMainLooper = myLooper();
       }
   }

   // 返回应用程序主线程中的主Looper
   public static Looper getMainLooper() {
       synchronized (Looper.class) {
           return sMainLooper;
       }
   }

这个就是主线程创建的Looper,一开始的例子说过,在UI线程创建Handler不需要自己加Looper,因为UI线程已经做过这个工作了。具体的创建在后面介绍。

Looper在prepare之后,就只剩下一个重要功能,loop:

代码语言:javascript
复制
// 在当前线程中运行message queue。确保在结束时调用quit结束loop
   public static void loop() {
       final Looper me = myLooper();
       if (me == null) { // 确保Looper.prepare调用过
           throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
       }
       final MessageQueue queue = me.mQueue;
	// 底层对IPC标识处理
       Binder.clearCallingIdentity();
       final long ident = Binder.clearCallingIdentity();

       for (;;) { // 无限循环
           Message msg = queue.next(); // 读Message,可能会阻塞
           if (msg == null) {
               // 没有消息意味着消息队列正在退出
               return;
           }

           // 打印一些Log Trace
           final Printer logging = me.mLogging;
           if (logging != null) {
               logging.println(">>>>> Dispatching to " + msg.target + " " +
                       msg.callback + ": " + msg.what);
           }

           final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

           final long traceTag = me.mTraceTag;
           if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
               Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
           }
           final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
           final long end;
           try {
               msg.target.dispatchMessage(msg); // 调用Handler派发消息
               end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
           } finally {
               if (traceTag != 0) {
                   Trace.traceEnd(traceTag);
               }
           }
           if (slowDispatchThresholdMs > 0) {
               final long time = end - start;
               if (time > slowDispatchThresholdMs) {
                   Slog.w(TAG, "Dispatch took " + time + "ms on "
                           + Thread.currentThread().getName() + ", h=" +
                           msg.target + " cb=" + msg.callback + " msg=" + msg.what);
               }
           }

           if (logging != null) {
               logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
           }

           // 确保在调度过程中线程的identity没有被破坏。
           final long newIdent = Binder.clearCallingIdentity();
           if (ident != newIdent) {
               Log.wtf(TAG, "Thread identity changed from 0x"
                       + Long.toHexString(ident) + " to 0x"
                       + Long.toHexString(newIdent) + " while dispatching to "
                       + msg.target.getClass().getName() + " "
                       + msg.callback + " what=" + msg.what);
           }

           msg.recycleUnchecked(); // 在Message提到过
       }
   }

Looper.loop就是从MessageQueue取出消息,如果没有消息就阻塞,知道来消息或者MessageQueue退出。拿到消息后,有消息内部绑定的Handler进行处理。

回想一下,MessageQueue的消息是Handler塞进去的,Looper循环拿消息出来最后还是由Handler处理。

最后确保在结束时调用quit结束loop即可。

再往回看一遍,MessageQueue,Looper都有自己明确的辅助工作。而真正完成消息传递的,全靠Handler。

android.os.Handler

Handler允许你发送和处理Message和与线程的MessageQueue关联的Runnable对象。

每个Handler实例都与一个线程和这个线程的MessageQueue相关联。当你创建一个新的Handler,它直接与创建它的所在的线程和MessageQueue绑定。这一点上,它会传递消息和runnable到那个MessageQueue,并且在消息从队列取出时执行消息。

Handler的两个主要用途:

  1. 规划(Scheduling)message和runnable在未来某个时间点执行
  2. 将一个要在其他线程执行的操作入队

Scheduling Message是由以下方法完成:

代码语言:javascript
复制
public final boolean post(Runnable r)
public final boolean postAtTime(Runnable r, long uptimeMillis)
public final boolean postDelayed(Runnable r, long delayMillis)
public final boolean sendEmptyMessage(int what)
public final boolean sendMessage(Message msg)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
public final boolean sendMessageDelayed(Message msg, long delayMillis)

post方法传递runnable参数,由message queue调用。send方法都是Message参数,由Handler的handleMessage处理。

回到最开始的例子,Handler的使用步骤如下:

  1. 在线程A内创建Handler,重载handleMessage方法处理消息
  2. 在线程B使用Handler.sendMessage等发送消息
  3. 消息从线程B发到线程A,handleMessage接收到消息并处理

下面以这三步来跟一下源码。

Handler最重要的就是线程间消息传递,下面跟源码了解中间发生了什么

代码语言:javascript
复制
public class Handler {
    // 将此标志设置为true以检测扩展此Handler类并且不是静态的匿名,本地或成员类。 这些类可能会造成泄漏。
  	// 关于Handler内存泄露的隐患,以后在研究。
    private static final boolean FIND_POTENTIAL_LEAKS = false;
    private static final String TAG = "Handler";
    private static Handler MAIN_THREAD_HANDLER = null;
  
    final Looper mLooper;
    final MessageQueue mQueue;
    final Callback mCallback;
    final boolean mAsynchronous;
    IMessenger mMessenger;

Handler的属性并不多,关于Callback:

代码语言:javascript
复制
public interface Callback {
    public boolean handleMessage(Message msg);
}

这个接口增加一个handleMessage的回调,根据注释:

Callback interface you can use when instantiating a Handler to avoid having to implement your own subclass of Handler.

说明可以在构造Handler的时候实现这个接口方法,这样就不用重载handleMessage方法了。实现如下:

代码语言:javascript
复制
Handler mHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        return false;
    }
});

需要注意,这里创建了匿名内部类,还是会持有外部引用,导致内存泄漏

通过源码,可以了解到这么做的原理:

代码语言:javascript
复制
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();
}

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

源码在调用dispatchMessage的时候(记不记得从MessageQueue读取到Message后就是调用这个方法?),如果有callback,就调用callback的接口并返回,否则就调用自己的handleMessage,进而调用到我们重载的方法。

前面提到,Handler发送消息有postXXX和sendXXX。

其实看完postXXX方法后,发现post也是调用sendXXX方法进行下一步处理,就是把runnable存入message的callback中,然后send message。所以选择一个send方法了解过程。

代码语言:javascript
复制
public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}

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) {
    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 发送消息最后调用到了消息队列的 enqueueMessage() 方法。然后从队列取出message也讲过了,最终是调用Handler.dispatchMessage()派发消息出去。这段代码在上面callback处已经贴出,然后send到调用便结束了。

另外提一点,如果移除消息,调用的也是消息队列的remove方法。

几个问题

这几个概念间的关系

  • 一个Thread只有一个Looper
  • Looper内有一个MessageQueue
  • 一个Thread可以有多个Handler
  • Handler内持有一个Looper和MessageQueue的引用
  • Handler内的Looper就是当前Thread(sThreadLocal)的Looper,MQ就是这个Looper的MQ

Handler构造方法:

代码语言:javascript
复制
public Handler(Callback callback, boolean async) {
    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());
        }
    }

    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

跨线程通信

核心就是,Handler可以在任意线程发送消息。

回到开头那段代码:

代码语言:javascript
复制
private Handler uiHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                break;
        }
    }
};

    new Thread(new Runnable() {
        @Override
        public void run() {
            Message msg = uiHandler.obtainMessage();
            msg.what = 1;
            uiHandler.sendMessage(msg);
        }
    }).start();

在UI线程创建uiHandler,此时这个Handler属于UIThread,其内部Looper也是UIThread创建的,MQ也在UIThread(Looper内持有)。

做到跨线程通信,就是在新的Thread内使用uiHandler的引用,用它给MQ发送一个message,这样消息就从Thread发送到了UIThread。

getMainLooper&prepareMainLooper

前面的例子,在UIThread创建Handler是不用指定Looper的,因为相关Looper已经创建。

如果在子线程中想更新UI线程,除了在UI线程创建Handler外,也可以在子线程创建Handler,不过需要给这个Handler指定主线程的Looper:

代码语言:javascript
复制
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(new Runnable() {
    @Override
    public void run() {
        //已在主线程中,可以更新UI
    }
});

通过上面的信息,了解到这两个方法和主线程息息相关,所以先看看这两个方法的源码:

代码语言:javascript
复制
// 将当前线程初始化为一个Looper,并标记其为应用的主looper。这个Looper是由Android环境创建的,所以你不应该主动调用这个方法。
   public static void prepareMainLooper() {
       prepare(false);
       synchronized (Looper.class) {
           if (sMainLooper != null) {
               throw new IllegalStateException("The main Looper has already been prepared.");
           }
           sMainLooper = myLooper();
       }
   }

   // 返回应用程序的主looper,其存在于应用的主线程
   public static Looper getMainLooper() {
       synchronized (Looper.class) {
           return sMainLooper;
       }
   }

所以要想知道系统的主线程是何时创建Main Looper的,就要跟踪prepareMainLooper 方法。

搜索AOSP,找到两处调用该方法的地方:

  1. frameworks/base/services/java/com/android/server/SystemServer.java 的 run 方法
  2. frameworks/base/core/java/android/app/ActivityThread.java 的 main 方法

这边直接说结论,ActivityThread就是所谓的主线程,其中的main方法就是主线程的入口:

代码语言:javascript
复制
public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

  		// 这里sMainLooper赋值
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

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

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

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();	// Looper进入循环

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

主线程的Handler从thread.getHandler();拿到的:

代码语言:javascript
复制
final H mH = new H();    
final Handler getHandler() {
    return mH;
}

H这个Handler基本处理了所有与应用有关的操作。具体的自己可以看看源码:H源码

至此,Android的Handler消息传递机制大概总结完了。不过又引出一个新的问题,ActivityThread的启动流程以及Application的启动过程和Activity的启动过程。这几天会抽时间整理一下。

相关阅读

也是搜了一些资料,但重复的很多。

http://www.cnblogs.com/codingmyworld/archive/2011/09/14/2174255.html

https://blog.csdn.net/u011240877/article/details/72892321

https://blog.csdn.net/u011240877/article/details/72905631

https://juejin.im/post/5a756b27f265da4e7d6018e6

https://hit-alibaba.github.io/interview/Android/basic/Android-handler-thread-looper.html

https://blog.csdn.net/rain_butterfly/article/details/48756797

https://www.cnblogs.com/angrycode/p/6576905.html

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-05-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 经验之谈
  • android.os.Message
  • android.os.MessageQueue
  • android.os.Looper
  • android.os.Handler
  • 几个问题
    • 这几个概念间的关系
      • 跨线程通信
        • getMainLooper&prepareMainLooper
        • 相关阅读
        相关产品与服务
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档