美文网首页
Android Handler 总结

Android Handler 总结

作者: 安静的蓝孩子 | 来源:发表于2019-01-22 17:14 被阅读0次

    基本概念

    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的消息处理机制

    相关文章

      网友评论

          本文标题:Android Handler 总结

          本文链接:https://www.haomeiwen.com/subject/qtlrjqtx.html