Android开发Android技术知识Android开发

六、Android 消息机制之Handler

2018-10-14  本文已影响2人  Yjnull

从开发的角度来讲,Handler 是 Android 消息机制的上层接口。因此我们主要讨论的是 Handler 的运行机制。

那么首先回答个问题,为什么要有 Handler 机制?

0. 为什么要有 Handler 机制?

回答这个问题,首先我们得知道 Handler 有什么作用。

作用: Handler 的主要作用是将一个 任务 切换到 Handler 所在的线程中去执行。

而 Android 规定访问 UI 这个 任务 只能在主线程中进行,如果在子线程中访问 UI,就会抛出异常。每次操作 UI 时会进行验证,这个验证是通过 ViewRootImpl 类里面的 checkThread 方法 完成的。

void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

只能在主线程就只能在主线程访问呗,那为什么还要这个 Handler 呢,因为 Android 又建议在主线程中不能进行耗时操作,否则会导致 ANR 。那考虑这样一种情况,比如我们要从服务端拉取一些数据并将其显示在 UI 上,这个必须在子线程中进行拉取动作,拉取完毕后又不能在子线程中访问 UI,那我该如何将访问 UI 的工作切换到主线程中呢?对,就是 Handler。

因此系统之所以提供 Handler,主要原因就是为了 解决在子线程中无法访问 UI 的矛盾。

Q 为什么不允许在子线程中访问 UI?

因为 Android 的 UI 控件不是线程安全的,如果在 多线程中并发访问 可能会导致 UI 控件处于不可预期的状态。

Q 那为什么不对 UI 控件的访问加上 锁机制呢?

1. Looper、MessageQueue、Message、Handler

消息机制的模型主要由以上四个类组成:

2. 示意图

下图完整类图取自 http://gityuan.com/2015/12/26/handler-message-framework/

消息机制Main.jpg

下图为简化版本:


消息机制.png

针对简化版做个解释:

上述是整个 消息机制的 大致流程,嗯,这么长估计没人看,我自己都不想看。
看过这么一大段后,对于 Handler 的主要作用 想必还是一头雾水。 还记得 Handler 的主要作用吗?
Handler主要作用:将一个任务切换到 Handler 所在的线程中去执行。

了解流程后,我知道了一个 消息 怎么发给消息队列, 然后 Handler 会自己处理这个消息。但是这个线程是怎么切换的,完全不知道。

上述简化图中有个灰色的块块, ThreadLocal。 对,它就是关键。

3. ThreadLocal 工作原理

ThreadLocal 是一个线程内部的数据存储类。 还是自己去找定义看吧,这里就不写了,我们还是关心它有什么用。

它的主要作用就是:可以在不同的线程中维护一套数据的副本并且彼此互不干扰。

这又是什么意思,别慌,这里有个例子可以方便理解。

public class ThreadLocalSample {

    private ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<>();

    public void test() {
        //1、在主线程中设置 mThreadLocal 的值为 true
        mThreadLocal.set(true);
        System.out.println(Thread.currentThread().getName() + " : mThreadLocal = " + mThreadLocal.get());

        new Thread("Thread#1") {
            @Override
            public void run() {
                //1、在子线程1中设置 mThreadLocal 的值为 false
                mThreadLocal.set(false);
                System.out.println(Thread.currentThread().getName() + " : mThreadLocal = " + mThreadLocal.get());
            }
        }.start();

        new Thread("Thread#2") {
            @Override
            public void run() {
                //1、在子线程2中 不设置 mThreadLocal,那么get得到的值应该为 null
                System.out.println(Thread.currentThread().getName() + " : mThreadLocal = " + mThreadLocal.get());
            }
        }.start();

    }

    public static void main(String[] args) {
        new ThreadLocalSample().test();
    }

}

这段代码执行的结果如下:

threadlocal_code.png

从结果可知,虽然在不同的线程访问的是同一个 ThreadLocal 对象,但是他们通过 ThreadLocal 获取到的值却是不一样的。

这是为什么呢?

3.1 ThreadLocal.Values

ThreadLoacal 内部有个 静态内部类 Values,Values 内部维护的是一个 Object [ ] ,当我们通过 ThreadLoacal 进行 set() 方法调用时,实际是在 Values.put 。 当然不同版本的api,实现不一样,比如最新版本把Values改为了ThreadLocalMap,并且内部维护的是 Entry [ ] 数组。但是原理都一样,这里就 Values 分析简单点。

/**
     * Sets the value of this variable for the current thread. If set to
     * {@code null}, the value will be set to null and the underlying entry will
     * still be present.
     *
     * @param value the new value of the variable for the caller thread.
     */
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

可以看到,当我们调用 set 方法时, 会先取得当前的 Thread, 然后把 值 put 进去。 那么中间那句 Values values = values(currentThread); 是怎么回事呢。 看 Thread 源码。

    /**
     * Normal thread local values.
     */
    ThreadLocal.Values localValues;

你可以看到 Thread 的成员变量里持有 ThreadLocal.Values 。 所以当我们 set 时,会先从当前线程那里获取到 Values 对象, 也就是说我们实际是在给 每个线程的 Values 赋值。那么 values(currentThread) 做了什么呢。

    /**
     * Gets Values instance for this thread and variable type.
     */
    Values values(Thread current) {
        return current.localValues;
    }

很简单,就是返回 线程的 localValues 变量。 那么当我们第一次 set 时, 这个 Values 肯定为空, 那么就会调用 values = initializeValues(currentThread); 来进行初始化。

那么 ThreadLocal 类里的 initializeValues 又做了什么呢。

    /**
     * Creates Values instance for this thread and variable type.
     */
    Values initializeValues(Thread current) {
        return current.localValues = new Values();
    }

没错,直接 new Values(),并且把它赋给 Thread 类的 localValues 变量。 这样当前线程就拥有了 ThreadLocal.Values ,当下次set时,就会从这个线程中取出这个 Values 并对它进行赋值。 每个线程的 Values 是不一样的。 那 get 就不用说了,也是从当前线程中取出这个 Values ,然后获取相应的值,具体可自行查看源码。

到这里 ThreadLocal 就分析的差不多了,想必有了个大概印象,针对上述例子给个图理解。

ThreadLocalValues.png

4. Looper 中的ThreadLocal

分析过 ThreadLocal 后,看 Looper 就不一样了。
再来看一看 Looper.prepare() 方法。

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

sThreadLocal 就是 ThreadLocal 的引用,如下

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

这段我们说过,会new 一个 looper, 同时会把这个 looper 赋值给 ThreadLocal,我们知道 ThreadLocal 调用 set 方法的时候,会先获取当前线程,然后调用 Values.put , 那么这个时候,我们就把 主线程 与 Looper 绑定在一起了。

Handler 的构造方法如下:

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

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

我们可以看到首先会获取 Looper 对象, 而 Looper.myLooper() 的实现很简单,就一句话 return sThreadLocal.get(); 这样,如果你是在主线程中 new 的 handler, 那么你也就会在 主线程中 处理消息了。

Handler工作过程.png

5. Handler 中的dispatchMessage 消息分发

这个方法比较简单,看源码就能看懂。

    /**
     * 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);
        }
    }
    public interface Callback {
        public boolean handleMessage(Message msg);
    }
    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }
上一篇 下一篇

猜你喜欢

热点阅读