Android Handler 消息机制(下)

Looper

上篇介绍的是 MessageMessageQueue ,有了传递的消息和负责存储的队列,接下来看看负责调度的 looper

Class used to run a message loop for a thread

看一下 looper 中的属性:

//只有调用了prepare方法之后,sThreadLocal.get()才不会返回空
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // guarded by Looper.class  主线程中的Looper

final MessageQueue mQueue;//与之管理的消息队列
final Thread mThread;//当前线程

private Printer mLogging;
private long mTraceTag;

private long mSlowDispatchThresholdMs;

looper 中的注释可以知道,线程默认是没有 looper,需要调用 Looper.prepare() 为当前线程创建一个 looper ,接着调用 looper() 方法让它处理消息,只到循环停止。

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

上面提到了两个主要的方法:Looper.prepare();Looper.loop();

首先看一下 Looper.prepare() 方法的实现:

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 中存放一个 looper 对象,由于 ThreadLocal 的特性可以知道,一个线程中只能有一个 looper 对象,因为ThreadLocal是线程中共享的,如果重复调用prepare的话,就会报错。

看到 prepare 方法,不得不说说 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.");
        }
        sMainLooper = myLooper();
    }
}

prepareMainLooper 这个方法主要是在 ActivityThread 中的 main 方法中被调用的,而 ActivityThread 线程就是我们所说的主线程了,通过注释我们可以看出这个方法这个 mainLooper 在Android环境中是为了 application 创建的,这个方法永远都不需要我们手动去调用。

再看构造方法,里面只有两行代码:

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

创建了消息队列,并获取了当前线程 。

到这里 prepareMainLooper() 就执行完了,查看 prepareMainLooper() 调用者可以看到,在 SystemServer.run()ActivityThread.main() 中都在调用 Looper.prepareMainLooper() 后不远就调用了 Looper.loop() 。而这两处可以推断一个是系统应用的主线程,一个是用户应用的主线程。

Looper.loop() 方法:

public static void loop() {
   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;

   // Make sure the identity of this thread is that of the local process,
   // and keep track of what that identity token actually is.
   Binder.clearCallingIdentity();
   final long ident = Binder.clearCallingIdentity();

   for (;;) {
       Message msg = queue.next(); // might block 这个方法是会阻塞的,知道拿到Message,或者退出队列
       if (msg == null) {
           // No message indicates that the message queue is quitting.
           return;
       }

       // This must be in a local variable, in case a UI event sets the logger
       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 {
           //调用了message的target进行分发处理事件,target其实就是跟message绑定的handler
           msg.target.dispatchMessage(msg);
           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);
       }

       // Make sure that during the course of dispatching the
       // identity of the thread wasn't corrupted.
       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();
   }
}

可以看到,Looper.loop() 也很简单,就是调用 MessageQueue.next() 方法取消息,如果没有消息的话会阻塞,直到有新的消息进入或者消息队列退出。

拿到消息后调用消息关联的 Handler 处理消息。

可以看到,Looper 并没有执行消息,真正执行消息的还是添加消息到队列中的那个 Handler,真应了那句:解铃还须系铃人啊!

looper 的构造方法中我们可以看到一个参数 quitAllowed 为是否允许退出,最后是将这个参数给 MessageQueue ,可以猜测到,looper 中的退出方法应该是调用了 MessageQueue 的退出方法,来看一下代码:

public void quit() {
    mQueue.quit(false);
}

public void quitSafely() {
    mQueue.quit(true);
}

两个方法都是结束,那这两个方法之间的区别是什么呢,等我给你慢慢道来:还是先看源码吧

void quit(boolean safe) {
    if (!mQuitAllowed) {
        //只有主线程的mQuitAllowed才会为false,如果代码能够进到这里面,说明退出的是主线程的looper
        //主线程的looper是不需要也是不允许手动停止的
        throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
        if (mQuitting) {
            //如果已经停止  直接返回
            return;
        }
        mQuitting = true;

        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }

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

private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {//挨个遍历,把消息全都回收
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}

private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {//消息链表不为空
        if (p.when > now) {//链表中的消息执行时间都要比当前时间晚,则挨个移除消息
            removeAllMessagesLocked();
        } else {
            Message n;
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    break;
                }
                p = n;
            }
            p.next = null;
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();
            } while (n != null);
        }
    }
}

移除消息的逻辑就是先判断当前的 Looper 是否为主线程的 Looper ,如果是的话就直接报错,主线程的 Looper 是不运行手动退出的。接下来就是判断当前的 Looper 是否已经退出了,如果已经退出了就直接返回,否则的话就进行消息移除逻辑,如果是不安全的移除的话,那就是直接移除队列中所有的消息。如果是安全移除的话,首先会判断当前的队列中是否有消息,然后判断当前的队头消息的时间是不是比当前时间要晚,如果要晚的话就直接移除所有消息。如果不是的话,斗图模式开启,首先看一下执行代码 n = p.next; 以后的效果:

赋值n

如果没有下一个 message 为空的时候,也就是说没有队列中只有一个消息了,那就直接返回并让消息执行。下一个消息不为空的话就会继续判断,消息n的执行时间是否大于当前时间,如果不是的话就执行 p = n;:

并且继续轮询,如果想要继续执行下面代码的话,那只有满足条件 n.when > now ,这个时候就会执行代码 p.next = null; :

然后继续执行代码 p = n;

执行 n = p.next; 后,链表的结构会跟最初的状态一样,如果消息n不为空的话,就会继续执行并回收消息,直到消息为空。轮询结束。

两种停止轮询的方法主要区别在于:一个是不管三七二十一,直接移除所有消息;一个是让立马需要执行的消息先执行完毕,然后在移除队列中需要等待执行的消息。

Handler

上主菜,但是是一个很简单的主菜,为什么呢,因为 Handler 好多的操作都交给了 MessageQueueLooper 去处理了,Handler 只要调用方法就行了。

首先看一下 Handler 的构造方法,来深入了解一下:

public Handler(Callback callback, boolean async) {
    ...
    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 的时候会判断当前线程是否创建过 Looper ,也就是是否先调用了 Looper.prepare(); 方法,如果没有调用的话就会报错。然后就是给成员变量赋值。

接下来看一下发送消息的方法

一般来说分为两种

  • post(Runnable r)
  • sendMessage(Message msg)

先看一下 post(Runnable r) 方法

public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}

public final boolean postAtTime(Runnable r, long uptimeMillis){
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

private static Message getPostMessage(Runnable r) {
        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);
}

原来 post 方法其实是调用的是 sendMessageAtTime 方法,只是将 Runnable 进行封装到了 Message 中。那我们再看看sendMessage(Message msg)

public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}

也是调用 sendMessageDelayed 方法,那我们只需要关注 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);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
}

首先会判断 mQueue 是否为空,如果为空则直接抛出异常并返回,否则的话继续调用 enqueueMessage ,将当前的 Handler 对象设置给 msg ,最后调用了 MessageQueueenqueueMessage 方法加入到队列中。

处理消息

/**
 * 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();
}

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

首先会判断 msg 对象中的 callback 属性是否为空,那么 callback 是在什么时候放到 Message 中的呢?其实就是我们调用 post(Runnable r) 方法的时候,就会将这个 Runnable 赋值给 Message.callbck 的,如果 callback 不为空的话,就会执行在当前线程执行 run 方法。如果为空的话,也就是我们发送消息的时候是采用 sendMessage(Message msg) 方式进行发送的,接下来我们看到了 mCallback ,这个是什么呢,什么时候赋值的呢?

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

咦?是构造方法?我细细想...

Handler handler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        return false;
    }
});

是的,没错了,应该就是这样的,如果我们这样创建一个 Handler 的话,mCallback 属性就不会为空,如果 mCallback.handleMessage(msg) 返回为 ture 的话,就不会继续走,直接就返回了。如果 mCallback 为空或者是 mCallback.handleMessage(msg)false ,就会调用到 handleMessage 方法,handleMessage 方法在 Handler 其实是一个空实现,需要在子类中进行重写。处理消息的逻辑基本到此就结束了。

移除消息

public final void removeMessages(int what, Object object) {
    mQueue.removeMessages(this, what, object);
}

public final void removeCallbacks(Runnable r)
{
    mQueue.removeMessages(this, r, null);
}

可以看见,其实 Handler 什么都没有做,只是单纯的调用了 MessageQueue 中的方法, MessageQueue 的移除方法在之前已经讲解过了,就不进行再次讲解了。

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

推荐阅读更多精彩内容