Handler机制(Android消息机制)

自我理解链接

handler机制大致流程图.png

1. 首先有一个Looper对象已经存在,并处于轮询的状态;
相对应的代码,Looper.prepare()-->Looper.loop()

2. Handler发送Message,加入到MQ中
各种发送消息的方法-->equeueMessage()-->MessageQueue.equeueMessage()

3. 轮询取出MQ的队头Message,通过Handler进行回调
MessageQueue.next()-->(handler)msg.target.dispatchMessage()-->handleMessage()[自定义TODO]

Message 【继承Parcelable】

封装了任务携带的参数和处理该任务的Handler

/**
 * User-defined message code so that the recipient can identify
 * what this message is about. Each {@link Handler} has its own name-space
 * for message codes, so you do not need to worry about yours conflicting
 * with other handlers.
 */
public int what; //标识位,用来区别消息

/**
 * arg1 and arg2 are lower-cost alternatives to using
 * {@link #setData(Bundle) setData()} if you only need to store a
 * few integer values.
 */
public int arg1; //用来存储一些整数值,替代setData()存储

/**
 * arg1 and arg2 are lower-cost alternatives to using
 * {@link #setData(Bundle) setData()} if you only need to store a
 * few integer values.
 */
public int arg2; //用来存储一些整数值,替代setData()存储

/**
 * An arbitrary object to send to the recipient.  When using
 * {@link Messenger} to send the message across processes this can only
 * be non-null if it contains a Parcelable of a framework class (not one
 * implemented by the application).   For other data transfer use
 * {@link #setData}.
 *
 * <p>Note that Parcelable objects here are not supported prior to
 * the {@link android.os.Build.VERSION_CODES#FROYO} release.
 */
//发送给收件人的任意对象。当使用{@link Messenger}跨进程发送消息时,
//如果它包含框架类的Parcelable(不是应用程序实现的框架类),则它只能是非null。
//对于其他数据传输,请使用{@link #setData}
//2.2版本之前不支持实现Parceable的实体类
public Object obj;
  • Message的创建(2种方法)

    1. 直接实例化一个Message,然后设置其参数
      Message msg = new Message;
      msg.what = 0;
      msg.arg0 = 1; 
      ...
      
    2. Message.obtain() --【推荐】
      在许多情况下可以避免分配新的对象;避免重复创建Message
  • Message注意点:

    1. 尽量通过Message.obtain()的方式构建Message对象,防止Message的多次创建;
    2. 仅有int型参数时 最好使用arg1和arg2,减少内存的使用;

Looper:消息通道(使普通线程变成Looper线程)

在Activity中,系统会自动启动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()系统源码:

/** Initialize the current thread as a looper.
  * This gives you a chance to create handlers that then reference
  * this looper, before actually starting the loop. Be sure to call
  * {@link #loop()} after calling this method, and end it by calling
  * {@link #quit()}.
  */
"//将当前线程初始化为looper;确保在调用prepare方法之后,调用loop方法;然后结束时调用quit方法;"
public static void prepare() {
    prepare(true);
}

'//一个Thread只能有一个Looper对象'
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));
}
//初始化时会创建一个MessageQueue对象和线程
private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}

关于Looper.loop()系统源码:

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
//在此线程中运行消息队列,确保在结束loop后调用quit
public static void loop() {
    //获取当前looper线程
    final Looper me = myLooper();
    //判断是否调用了prepare
    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(;;)和while(true)  for (;;)指令少,不占用寄存器,而且没有判断跳转'
    for (;;) {
        // 获取消息队列中的message
        Message msg = queue.next(); // might block 
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ... log日志[省略]...
        try {
            '//交给Message对应的Handler处理消息'
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
         ... [省略]...
        msg.recycleUnchecked();
    }
}

总结:
1)一个Thread只能有一个Looper对象;
2)Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行;
3)Looper使一个线程变成Looper线程。

MessageQueue:消息队列

根据源码中的注释翻译如下:
保存由{@link Looper}分派的消息列表的低级类。消息不会直接添加到MessageQueue,而是通过与Looper关联的{@link Handler}对象添加。
您可以使用{@link Looper#myQueue()Looper.myQueue()}检索当前线程的MessageQueue。

/**
 Low-level class holding the list of messages to be dispatched by a
 {@link Looper}.  Messages are not added directly to a MessageQueue,
 but rather through {@link Handler} objects associated with the Looper.

 <p>You can retrieve the MessageQueue for the current thread with
 {@link Looper#myQueue() Looper.myQueue()}.
 */
public final class MessageQueue {
    ...
    //获取下一个message方法
    Message next(){
        ...
    }
    //message添加到消息队列的方法
    boolean enqueueMessage(Message msg, long when) {
        ...
    }
}

Handler:消息操作类【重点】

官方注释(抽象的翻译):
Handler允许您发送和处理与线程{@link MessageQueue}关联的{@link Message}和Runnable对象。
每个Handler实例都与一个线程和该线程的消息队列相关联。
当您创建一个新的Handler时,它被绑定到正在创建它的线程的线程/消息队列 - 从那时起,它将消息和runnables传递给该消息队列并在消息出来时执行它们队列。

Handler有两个主要用途:
(1)将消息和runnable安排在将来的某个点上执行;
(2)将要在不同于自己的线程上执行的动作排入队列。

源码分析:
/**
  *默认的构造方法会 关联一个looper
  */
public Handler(Callback callback, boolean async) {
    ...
    //关联looper,不能为null
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    //直接获取looper的消息队列,
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
handler发送消息:

可传递参数包括Message和Runnable,但即使传递Runnable对象,最终也被处理为Message对象,然后执行sendMessageAtTime()方法

/**
 * 在所有待处理消息之后将消息排入消息队列。
 *  如果消息成功的加入到MQ中,就返回true
 */
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);
}
handler消息的处理:

dispatchMessage(msg);此方法在Looper.loop()中调用msg.target.dispatchMessage();这里的msg.traget就是指向当前的handler;
然后Handler中调用handleMessage(msg)方法,来触发我们需要实现的具体逻辑。

延伸:

1. Handler内部如何获取到当前线程的Looper?

答:是通过 ThreadLocal

Handler内部.png
myLooper方法.png
2. 系统为什么不允许子线程中访问UI?

答:Android中的UI控件不是线程安全的,如果在多线程中并发的访问,UI显示状态不可预计。

3. 为何系统不对UI的访问加上锁机制?

答:上锁会让UI访问的逻辑变得复杂,会降低UI访问的效率,也会阻塞某些线程的执行,体现在界面上会显得卡顿运行不畅。

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

推荐阅读更多精彩内容