• Android Handler机制


    1. ThreadLocal

    在研究Handler的机制之前我们想明确一个知识点,ThreadLocal. ThreadLocal字面意思是线程本地变量,他保存的是一个变量的副本,并且只对当前线程可见。我们做个例子:

    final ThreadLocal<Object> threadLocal = new ThreadLocal<>();
        for(int i = 0; i < 10; i ++){
            new Thread(new Runnable() {
                @Override
                public void run() {
    
                    String local = "Thread" + Math.random();
                    Log.i(TAG, "local1:" + local);
                    threadLocal.set(local);
                    Log.i(TAG, "local2:" + threadLocal.get());
    
                }
            }).start();
        }
    

    结果如下:

    可以看到第一个和第二个打印的日志是一样的,也可能你觉得这个不能说明啥,一会我们看看源码也许你就明白了。 OK那我们看看他的源码:

        public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    

    在set值得时候,拿到当前的线程,并拿到一个map,就这个值存放到map中。这个TheaLocalMap是什么呢?

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    

    就一句话,还是从当前的线程里面去拿的,继续看下threadLocals:

     /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    

    那么这个ThreadLocalMap 是一个什么东西呢,继续往下看:

     static class ThreadLocalMap {
    
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
    
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    

    可以看到这个ThreadLocalMap中有一个存储结构用来存储键值对,这个value其实就是我们set进去的变量,继续看下set方法:

     /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) {
    
            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.
    
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
    
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
    
                if (k == key) {
                    e.value = value;
                    return;
                }
    
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
    
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
    

    这个方法也不复杂,就是看看当前的数组table中是不是已经存过了,如果已经存过了那么久覆盖掉之前的Value,没有的话就创建一个Entry放到数组中。所以同一个ThreadLocal在一个线程中只能存放一个值,不然会被覆盖掉。

    当然拿到当前的值时候也不难,明白了这个get方法也就很简单了。 总结下 ThreadLocal的作用 当前线程是绑定一个变量,只能当前线程访问,别的线程访问不了也修改不了他的值。在多线程的环境中, 当多个线程需要对某一个变量进行频繁的操作,但又不需要同步的时候,可以用ThreadLocal,因为他存储在当前的线程中的,效率恨到,其实就是空间换时间。

    2. Handler Looper, MessageQueue, Message

    Handler Looper MessageQueue Message他们之间关系其实不难理解,我们new一个Handler并实现里面的handMessage方法,参数是一个message, 然后用Handler发送message,发送的message到了MessageQuueue这个队列里面,Looper维持一个死循环,一直从MessageQueue里面取Message,之后调用相应的Handler里面的handMessage方法,这样就完成了一个循环。 说起来你不难,但是要理解里面的机制,还需要看源码: 首先一个应用在启动的时候,系统会调用ActivityThread.java中的main方法,在main方法里面会去初始化Looper。

        Looper.prepareMainLooper();
    
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
    
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
    
        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
    
        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();
    

    可以看到调用了Looper.prepareMainLooper();这个方法其实就是初始化了一个Looper,这个Looper是给主线程调用,接着下面调用了Looper.loop() 这个方法是开启一个死循环,一会再看,先看下Looper.prepareMainLooper()

     /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    
     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));
    }
    
     public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    

    在上面的源码prepare中new了一个Looper并把它放到了ThreadLocal中,上面我们已经说了,ThreadLocal存储的变量和线程绑定在一起,那么当前的线程其实是主线程。 sMainLooper 这变量其实就是我们常用的Looper.getMainLooper().

      private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    

    那么MessageQueue是什么时候创建的,其实就是new Looper的时候创建的。

    我们平常用Handler的时候都是new Handler 如下:

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
    
            switch (msg.what){
                case xx
                    break
    
            }
            super.handleMessage(msg);
        }
    };
    mHandler.sendEmptyMessageDelayed(1, 100);
    

    那么Handler发送的的message是怎么到队列里面,并且之后是怎么回调的,继续看源码:

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

    所有的send方法都会掉到上面这个方法里面来,然后插进队列:

      private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    

    这个时候要注意上面的那个msg.target = this;这一句,可以看到message将会绑定当前发送的Handler,这也就为以后出来message埋下了伏笔,其实说白了就是拿到这个target指向的Handler来调用handmessage方法处理message。

    那你有没有注意到那个mQueue 是从哪里冒出来的?

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

    其实就是在new Handler的时候初始化的,可以看到上面Looper.myLooper();这句话,如果你有心的话其实上面已经有这个方法的实现了:

       public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    

    其实就拿的当前线程里面的Looper,因为这个是在主线程里面的new的所以其实就是拿到ActivityThread里面初始化的Looper。 既然已经拿到messageQueue并且将message放到队列里面的那么接下来是怎么出队列进行处理的呢,那就是我们的一开始就说的Looper.loop()了:

       public static void loop() {
        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;
    
    
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
    
    
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
    
    
            msg.recycleUnchecked();
        }
    }
    

    看上面的代码,我删除了一些东西,最主要的是就拿到messageQueue,并有一个死循环,不断的从messageQueue里面拿去message,接着调用msg.target.dispatchMessage(msg); 在直接发送message的时候,我们看过这个target其实就是绑定的当前的Handler,在dispatchMessage这个方法中就会去调用handMessage这个方法,到这里,整个流程就走完了。

    关于死循环的问题,可以看看https://www.zhihu.com/question/34652589/answer/90344494 可以看看这个链接,牵扯到了Linux系统管道的问题了。

    通过上面的分析我们也就知道了在主线程中可以newHandler 而在子线程中new Hander 必须自己手动调用Looper的原因了,因为主线程已经帮我实现好了

  • 相关阅读:
    @responseBody注解的使用
    springmvc下的省市县三级联动
    select 动态添加option函数
    清空select标签中option选项的4种不同方式
    javascript删除option选项的多种方法总结
    js如何获取select下拉框的value以及文本内容
    如何设置select下拉禁止选择
    java.sql.SQLSyntaxErrorException: ORA-00911: 无效字符
    转:通过他人完成任务的艺术
    ***周鸿祎谈创业:很多程序员高智商 但我一看就知道他们不会成功
  • 原文地址:https://www.cnblogs.com/xlurenjia/p/8393159.html
Copyright © 2020-2023  润新知