Android的消息机制

总的来说,Android应用程序是通过消息来驱动的,Android也可以说是一个以消息驱动的系统,UI、生命周期等各种事件和消息处理机制息息相关,所以消息处理机制在整个Android知识体系中是非常重要的,但是网上关于Android消息机制的文章,大多都长篇大论,如果没有足够的耐心,很难阅读并理解下去,所以,我觉得在汲取别人文章知识的同时,也需要自己去花功夫去总结出自己的结论,才能记忆深刻。

组成成员

  • Looper 消息轮询器
  • MessageQueue 消息队列
  • Message 消息载体
  • Handler 发送与处理消息者

原理简述

App启动时,在ActivityThread的main方法里,调用了Looper.prepareMainLooper(),创建一个全局主线程的Looper(通过ThreadLocal将Looper与主线程绑定,并确保一个线程只有一个Looper),这个Looper在实例化的同时,会实例化一个消息队列MessageQueue,在Looper.prepareMainLooper()完毕后,main方法继续调用Looper.loop(),主线程的Looper进入一个无限的for循坏,在这个for循环里,looper会不断从自己的消息队列MessageQueue中查询消息,当队列中没有消息时,CPU会腾出资源给其他线程,当队列中有消息Message时,由于Message会持有一个handler对象,那么此时会迫使handler调用dispatchMessage方法,而在dispatchMessage方法里面,则会继续调用我们最熟悉的handleMessage方法了。
由于handler自身持有主线程的Looper(该handler是在主线程实例化的前提下),当我们利用handler发送消息时,会通过Looper中的消息队列MessageQueue插入消息,那么在loop方法里的无限for循坏就会取出这条消息,handler执行这条消息,因此其他线程可以通过这个handler,与主线程进行消息传递,从而形成这个完整的消息机制。

值得注意的是,在其他线程创建Handler时,必须首先调用Looper.prepare(),在该线程才会持有对应的Looper,否则会抛出异常

例子

我们通过一个例子来概括:
某公司有一个24小时运行的请假系统(即APP的主线程),系统会有一个请假列表(即MessageQueue),请假者(Handler)发送休假申请到系统时,会自动插一条请假记录(Message)到列表中,系统的工作就是不断查询列表是否有休假申请,如果有,就一条条进行审核,并将审核结果发送到请假者的手机上,请假者根据审核结果行动;如果列表中没有记录,系统就将进入休眠状态(即线程堵塞),但是当系统收到一条休假申请时,系统会自动恢复工作状态,继续进行审核。

源码分析

首先,从App的入口,即ActivityThread

public final class ActivityThread {
    ...

        ActivityThread() {
            mResourcesManager = ResourcesManager.getInstance();
        }
    ...
    public static void main(String[] args) {
    ...

     //创建一个主线程的Looper
    Looper.prepareMainLooper();

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

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

    ...

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    //进入一个无限的消息读取操作
    Looper.loop();

   ...
   
   }
 ...
}

由以上源码可见ActivityThread并不是一个线程类,只是一个普通类,在其主方法中调用了

 Looper.prepareMainLooper() 
 Looper.loop() 

来大致看看Looper的源码

public final class Looper {
...
    //ThreadLocal,线程本地存储区,这里用于存放线程对应的Looper
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    //主线程的Looper
    private static Looper sMainLooper;  // guarded by Looper.class
    //Looper持有的消息队列
    final MessageQueue mQueue;
    //Looper的所属线程
    final Thread mThread;
    //实际上调用了prepare方法,并且利用synchronized锁,确保主线程只有一个Looper
    public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been                   prepared.");
        }
        sMainLooper = myLooper();
    }
    }

    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变量
     mThread = Thread.currentThread();
    }

    //获取线程的Looper
    public static @Nullable Looper myLooper() {
     return sThreadLocal.get();
    }

    //方便获取主线程的Looper
    public static Looper getMainLooper() {
       synchronized (Looper.class) {
          return sMainLooper;
     }
    }
}

//进入无限循环,不断读取消息,当队列中有消息时,通过消息中的handler对象的dispatchMessage方法分发消息
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;

    // 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
        //没有消息时,自身堵塞
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        ...
        try {
                //Message对象持有一个target即Handler对象,利用它的dispatchMessage方法进行消息分发
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
       ...
            //将Message回收到消息池,下次要用的时候不需要重新创建,obtain()就可以了。
           msg.recycleUnchecked();
    }
}

那么,APP启动时,并创建了一个属于主线程的Looper,而这个Looper也拥有自己的消息队列,主线程已经开始在无限循环中不断查询消息了,那么系统是怎样发送消息到主线程中去,而主线程又如何处理这些消息?

注:Binder线程:具体是指ApplicationThread,在App进程中接受系统进程传递过来的信息的线程(这个线程的创建时间先于主线程)。

接下来,我们来探讨一下Activity的启动流程:当APP准备启动一个Activity的时候,系统服务进程下的ActivityManagerService(AMS)线程会通过Binder线程发送IPC调用给APP进程,App进程接到到调用后,通过App进程下的Binder线程最终调用ActivityThread类下面的scheduleLaunchActivity方法来准备启动Activity,看下scheduleLaunchActivity方法:


//这个方法不是在主线程调用,是Binder线程下调用的
 public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state, PersistableBundle persistentState, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
 
  updateProcessState(procState, false);
  ActivityClientRecord r = new ActivityClientRecord();
  r.token = token;
  r.ident = ident;
  ...
  
   updatePendingConfiguration(curConfig);
   
   //将信息内容封装成ActivityClientRecord,发送启动activity的指令
   sendMessage(H.LAUNCH_ACTIVITY, r);
  
  
  }
  
  private void sendMessage(int what, Object obj) {
    sendMessage(what, obj, 0, 0, false);
    }


    private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) 
    {
     if (DEBUG_MESSAGES) Slog.v(
        TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
        + ": " + arg1 + " / " + obj);
    Message msg = Message.obtain();
    msg.what = what;
    msg.obj = obj;
    msg.arg1 = arg1;
    msg.arg2 = arg2;
    if (async) {
        msg.setAsynchronous(true);
    }
    mH.sendMessage(msg);
}
 

由上面的代码可以知道,最后通过变量mH来发送消息,那么重新查阅ActivityThread的源码,可以得知它就是一个继承Handler的H类,它帮我们处理许多activity生命周期的事情

public final class ActivityThread {

    ...

    final H mH = new H();

    ...

    private class H extends Handler {
    ...

        public void handleMessage(Message msg) {
            switch (msg.what) {
         //进行一系列指令操作
            }
        }
...
    }
}

到此为止,我们对启动流程有了较为清晰的了解,那么Handler又是如何将消息发送到线程的消息队列中去呢?再来简单看看Handler的源码

public class Handler {


...
//可见,内部有Looper和MessageQueue成员
final Looper mLooper;
final MessageQueue mQueue;
...

//我们常用的无参构造
public Handler() {
        this(null, false);
 }

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());
        }
    }
     //得到当前线程的Looper
    mLooper = Looper.myLooper();
    
    /* 
     * 如果在一个新线程中,实例化Handler前,必须先调用Looper.prepare()
     */
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    //得到Looper的消息队列
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

/**
 * 
 *  所有发送消息的方法,最终都会调用到此方法
 */
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) {        
    //将消息中的target赋值为当前handler
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    //将消息插入队列中
    return queue.enqueueMessage(msg, uptimeMillis);
}


}

由Handler的源码可知,handler在发送消息时,会将消息插入其持有的Looper所对应的消息队列中,即以上代码的queue.enqueueMessage


public final class MessageQueue {

    ...
    private final boolean mQuitAllowed;
    ...


    //将消息按时间插入队列中
    boolean enqueueMessage(Message msg, long when) {
          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();
        //when 表示这个消息执行的时间,队列是按照消息执行时间排序的
        //如果handler 调用的是postDelay 那么when=SystemClock.uptimeMillis()+delayMillis
        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.
            // p==null 表示当前消息队列没有消息
            msg.next = p;
            mMessages = msg;
            //需要唤醒主线程,如果队列没有元素,主线程会堵塞在管道的读端,这时
            //候队列突然有消息了,就会往管道写入字符,唤醒主线程
            needWake = mBlocked;
        } else { 
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            //将消息放到队列的确切位置,按照msg的when 排序的
            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;
}

}

回顾一下Looper的源码,在loop方法中无限的for循环中看到,当队列中有消息时,消息对象持有一个target即Handler对象,利用它的dispatchMessage方法进行消息分发

  msg.target.dispatchMessage(msg);

再补充一下Handler的分发消息的部分代码

public class Handler {

...
//这里方法内容为空,因为后续由开发人员操作
public void handleMessage(Message msg) {
}

/**
 * 可以看到,最后会调用handleMessage(当回调接口为空时)
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}


...

}


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

推荐阅读更多精彩内容

  • 1. ANR异常 Application No Response:应用程序无响应。在主线程中,是不允许执行耗时的操...
    JackChen1024阅读 1,357评论 0 3
  • Android平台上,主要用到的通信机制有两种:Handler和Binder,前者用于进程内部的通信,后者主要用于...
    帝都De雾霾阅读 1,484评论 1 7
  • 本文出自 “阿敏其人” 简书博客,转载或引用请注明出处。 能简单说得我们尽量不复杂: 为了避免ANR,我们会通常把...
    阿敏其人阅读 33,836评论 5 110
  • 引言 由于Android对消息机制的封装,开发者在平常的开发过程中,直接使用Handler对象就能满足大部分的应用...
    红灰李阅读 769评论 1 2
  • 致小东西——疼 写首小诗,鼓励自己: 此时此刻,我正在休息。 无心看书,更无心工作, 有个小东西扰着我的安宁, 躲...
    我是Linny阅读 151评论 0 4