Android Handler 总结

基本概念

Handler

Handler 主要用于异步消息的处理。

Looper 沟通,Push 新消息到 MessageQueue 里,或者接收处理 LooperMessageQueue 里取出的消息。

Message

进行消息的封装。

Message 类中的关键变量:

  1. public int what :
    用于声明此 Message 的类型。
  2. public int arg1 :
    public int arg2 :
    用于传递一些整型数据。
  3. public Object obj :
    用于传递一个对象。

Message 类中的关键接口方法:

  1. public static Message obtain() :
    优先从一个全局的消息池中返回一个新的 Message(复用消息池找那个的消息)。很多情况下可以避免新生成一个消息的额外开销。(尽管可以通过 New Message() 获得一个新的消息对象,但是建议优先使用 obtain() 获得一个空的 Message,以节约资源)
Message msg = Message.obtain();
  1. public Handler getTarget() :
    获取到处理此消息的 Handler 对象。
  2. public void setData(Bundle data) :
    用于传递一个 Bundle 对象。对应的使用 getData()peekData() 取出此 Bundle。(注意:如果只是传递简单的 int 信息,应优先使用 arg1arg2

MessageQueue

Looper 持有,用来保存消息(Message)。
消息队列是先进先出的。
消息不会直接添加到 MessageQueue,而是通过 Handler 对象和 Looper
可以通过 Looper.myQueue() 来获取当前线程的 MessageQueue 对象。

Looper

Looper 字面意思就是循环者。它被设计用来使一个普通线程变成 Looper 线程,也就是可以循环工作的线程。
Looper 内部维护了一个 MessageQueue 消息队列。

例如:

  class LooperThread extends Thread {
        public Handler mHandler;
  
        public void run() {
            Looper.prepare(); //创建Looper对象
  
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };
  
            Looper.loop(); //循环处理消息队列
        }
    }

调用 quit() 方法结束 looper 循环。

Looper 有以下几个要点:

  1. 每个线程有且只能有一个 Looper 对象,它是一个 ThreadLocal
  2. Looper 内部有一个消息队列,loop() 方法调用后线程开始不断从队列中取出消息执行。
  3. Looper 使一个线程变成 Looper 线程。

ThreadLocal

线程本地变量。
每个 Thread 内部有一个 ThreadLocal

ThreadLocal 不是一个线程而是一个线程的本地化对象。当工作于多线程环境中的对象采用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程分配一个独立的副本。每个线程都可以独立的改变自己的副本,而不影响其他线程的副本。

ThreadLocal 类中的接口方法:

  1. public void set(Object value) :
    设置当前线程的线程局部变量的值。
  2. public Object get() :
    返回当前线程的线程局部变量的值。
  3. public void remove() :
    删除当前线程的局部变量的值。
  4. protected Object initialValue() :
    返回当前线程局部变量的初始值。

ThreadLocal 是如何做到为每一个线程维护一份独立的变量副本的呢?
思路很简单,在 ThreadLocal 类中有一个 Map, Map中的键为线程对象,值为对应线程的变量副本。

ThreadLocal 与线程同步机制的比较:

线程同步机制通过对象的锁机制保证同一时间只有一个线程去访问变量,该变量时多个线程共享的。ThreadLocal 则为每一个线程提供了一个变量副本,从而隔离了多个线程访问数据的冲突,ThreadLocal 提供了线程安全的对象封装,在编写多线程代码时,可以把不安全的代码封装进 ThreadLocal。概括的说,对于多线程资源共享的问题,线程同步机制采取了时间换空间的方式,访问串行化,对象共享化;而 ThreadLocal 采取了空间换时间的方式,访问并行化,对象独享化。

使用例子:

public class TestThreadLocal {

    private ThreadLocal<Integer> mNum = new ThreadLocal<Integer>() {
        public Integer initialValue() {
            //设置默认值为0
            return 0;
        }
    };

    public int getNextNum() {
        mNum.set(mNum.get() + 1);
        return mNum.get();
    }

    private static class TestThread extends Thread {
        private TestThreadLocal mTest;

        private TestThread(TestThreadLocal testThreadLocal) {
            mTest = testThreadLocal;
        }

        public void run() {
            for (int i = 0; i < 3; i++) {
                System.out.println("Thread name:" + Thread.currentThread().getName() + ",num:" + mTest.getNextNum());
            }
        }
    }

    public static void main(String [] args) {
        TestThreadLocal testThreadLocal = new TestThreadLocal();
        TestThread thread1 = new TestThread(testThreadLocal);
        TestThread thread2 = new TestThread(testThreadLocal);
        TestThread thread3 = new TestThread(testThreadLocal);
        thread1.start();
        thread2.start();
        thread3.start();
    }

}

打印

Thread name:Thread-0,num:1
Thread name:Thread-1,num:1
Thread name:Thread-2,num:1
Thread name:Thread-1,num:2
Thread name:Thread-0,num:2
Thread name:Thread-2,num:2
Thread name:Thread-0,num:3
Thread name:Thread-2,num:3
Thread name:Thread-1,num:3

Handler 的原理

Handler 原理图

Handler 原理:

  1. Handler 关联线程的 Looper
  2. Handler 发送消息,通过 LooperMessageQueue 把消息插入 MessageQueue 队列中。
  3. Looper 不断循环,取出 MessageQueue 中队头的 Message
  4. 调用 HandlerdispatchMessage 方法,让 Handler 处理消息。

Handler 和 Looper

Handler 的初始化主要有两种方式:

  1. 未指定 Looper方式:
Handler handler = new Handler(); 

通过这种方式 new 出来的 Handler 对象,默认使用当前线程的 Looper
源码如下:

public Handler() {
     this(null, false);
}
    
public Handler(Callback callback, boolean async) {
     ...

     mLooper = Looper.myLooper(); // 默认将关联当前线程的looper
     if (mLooper == null) {
          throw new RuntimeException(
              "Can't create handler inside thread that has not called Looper.prepare()");
      }
      mQueue = mLooper.mQueue; //关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上
      mCallback = callback;
     mAsynchronous = async;
}

注意:
Activity 被创建时就默认创建了 LooperThread 是没有默认创建 Looper 的。

  1. 指定 Looper 的方式:
public Handler(Looper looper) {
    this(looper, null, false);
}
    
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper; //直接设置为传入的 Looper 对象
    mQueue = looper.mQueue; //关联的也是传入的 Looper 中的 MQ
    mCallback = callback;
    mAsynchronous = async;
}

注意:
一个线程可以有对个 Handler,但是只能有一个 Looper

Handler 发送消息

Handler 的使用会有两种方式,一种是发送 Message 、一种是发送 Runnable

  1. 发送 Message

这种方式有以下接口方法:

sendEmptyMessage(int)

sendMessage(Message)

sendMessageAtTime(Message, long)

sendMessageDelayed(Message, long)

几个接口调用如下图:

发送消息接口

可见最后都是调用的 sendMessageAtTime(Message msg, long uptimeMillis) 接口。

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) {
        //重要,这里把当前Handler设置给msg.target,方便后面从MQ中取出消息后,能让对应的Handler处理此消息
        msg.target = this; 
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //把消息放入MQ消息队列
        return queue.enqueueMessage(msg, uptimeMillis);
    }
  1. 发送 Runnable
    这种方式有以下接口方法:
post(Runnable)

postAtTime(Runnable, long)

postDelayed(Runnable, long)

几个接口的调用如下图:

发送Runnable调用图

由上图可见,最后都会调用到 sendMessageDelayed(Message msg, long delayMillis) 接口,而 Runnable 者会封装到 Messagecallback 变量中,而 Message 也是放入到消息队列中的。

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

Looper 中对消息的处理

调用 Looperloop() 方法后,Looper 线程就开始了循环工作,不断的从 MessageQueue 中取出队头的消息执行。

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.");
        }
        //获取当前Looper的MessageQueue
        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;
            }

            // 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 {
                //关键:msg.target为发送消息的Handler,这里把取出来的msg交给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();
        }
    }

Handler 中 dispatchMessage 处理消息

上面 Looper 取出消息后,会调用 HandlerdispatchMessage(Message msg) 接口处理消息。

public void dispatchMessage(Message msg) {
        //msg.callback 为 Runnable,即Handler.post(Runnable r) 这种方式调用的
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
        //如果不是post Runnable 的方式,则为sendMessage的方式,直接会调用handleMessage(msg)处理消息
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    
private static void handleCallback(Message message) {
        //post runnable 的方式,handleCallback中直接调用了Runnable的run方法
        message.callback.run();
    }

关于线程问题

由于 Handler 是在它关联的 Looper 线程中处理(dispatchMessage(Message msg))消息的,所以 Looper 所在的线程决定了 Handler 的处理消息的线程。

Activity 的主线程也是一个 Looper 线程,所以在主线程中创建的 Handler,在子线程中通过 Handler 发送消息,最后处理消息时在主(UI)线程中。这也是我们平时为什么能通过 Handler 来解决非主线程中更新 UI 的问题的原因。


如何让 Handler 关联一个子线程的 Looper,使 Handler 在子线程中处理消息?
Android HandlerThread





参考:

android的消息处理机制

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

推荐阅读更多精彩内容