Android 消息机制(Handler运行机制)

 

 1 Android 消息机制

        Android 的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑

2 为什么要用Handler消息传递机制

          多个线程并发更新UI的同时 保证线程安全

          

        在子线程中访问UI,程序会抛出异常,是通过ViewRootImpl类的checkThread方法来进行验证。

public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, 
       ThreadedRenderer.DrawCallbacks {
     void checkThread() {
           if (mThread != Thread.currentThread()) {
                 throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can 
                     touch its views.");
           }
     }
}

3  相关概念图解

             

4  MessageQueue(消息队列)

       内部存储了一组消息,以队列的形式对外提供插入和删除的工作。采用单链表的数据结构来存储消息列表。只是一个消息的存储单元,无法处理消息。

                   

  • 构造方法和相关属性
public final class MessageQueue {
    private static final String TAG = "MessageQueue";
    private static final boolean DEBUG = false;

    // True if the message queue can be quit.
    // MessageQueue是否允许退出(系统创建的UI线程的MessageQueue不允许)
    private final boolean mQuitAllowed;

    @SuppressWarnings("unused")
    //native代码相关,指向C/C++代码中的某些指针
    private long mPtr; // used by native code

    //消息队列头Head
    Message mMessages;

    //集合类型的IdldHandler
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;

    //数组类型的IdldHandler
    private IdleHandler[] mPendingIdleHandlers;

    //当前队列是否处于正在退出状态
    private boolean mQuitting;

    // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
    //next()调用是否被block在timeout不为0的pollOnce上
    private boolean mBlocked;

    // The next barrier token.
    // Barriers are indicated by messages with a null target whose arg1 field carries the token.
    //下一个barrier token
    //barrier用target==null, arg1==token的Message对象表示
    private int mNextBarrierToken;

    private native static long nativeInit();
    private native static void nativeDestroy(long ptr);
    private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
    private native static void nativeWake(long ptr);
    private native static boolean nativeIsPolling(long ptr);
    private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        //native代码中相应函数
        mPtr = nativeInit();
    }

}

      framework层nativeInit() 代码

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }
    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

    MessageQueue在Looper 中进行初始化

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}
  •  主要方法 

          1 enqueneMessage()方法

    boolean enqueueMessage(Message msg, long when) {
        //消息需要关联Handler
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //消息正在使用中
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        synchronized (this) {
            // 消息队列处于正在退出的状态,消息不能入队
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }
            //标记为已在用,设置msg的when字段,临时变量p指向队列头
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //添加消息到链表中
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                //添加新消息唤醒,空链表时读取消息阻塞
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

 

 

  

5  Looper

         以无限循环的形式去查找是否有新消息,有则处理消息,否则一直等待。Handler创建的时候会采用当前线程的Looper来构造消息循环系统,如果当前线程没有Looper,就会报错。

    E/AndroidRuntime: FATAL EXCEPTION: Thread-9009
    java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:209)
        at android.os.Handler.<init>(Handler.java:123)
        at android.widget.Toast$TN.<init>(Toast.java:363)
        at android.widget.Toast.<init>(Toast.java:112)
        at android.widget.Toast.makeText(Toast.java:277)
        at com.example.hp.jnitest.MainActivity$1.run(MainActivity.java:18)

6 ThreadLocal

        线程内部的数据存储类。不是线程,但可以在每个线程中存储数据,在不同的线程中互不干扰地存储并提供数据。通过ThreadLocal可以轻松获取每个线程的Looper。

线程默认没有Looper,如果需要使用Handler就必须为线程创建Looper。主线程(UI线程),就是ActivityThread,ActivityThread被创建时就会初始化Looper.

        6.1  ThreadLocal 工作原理

     代码体现:

public class MainActivity extends AppCompatActivity {

    private ThreadLocal<Boolean> mBooleanThreadLocal=new ThreadLocal<Boolean>();
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBooleanThreadLocal.set(true);
        Log.e(TAG,"[Thread#main]mBooleanThreadLocal="+mBooleanThreadLocal.get());
        new Thread("Thread#1"){
            @Override
            public void run() {
                mBooleanThreadLocal.set(false);
                Log.e(TAG,"[Thread#1]mBooleanThreadLocal="+mBooleanThreadLocal.get());
            }
        }.start();
        new Thread("Thread#2"){
            @Override
            public void run() {
                Log.e(TAG,"[Thread#2]mBooleanThreadLocal="+mBooleanThreadLocal.get());
            }
        }.start();
    }


}

-----------------------------------------------------------------------------------------
E/MainActivity: [Thread#main]mBooleanThreadLocal=true
E/MainActivity: [Thread#1]mBooleanThreadLocal=false
E/MainActivity: [Thread#2]mBooleanThreadLocal=null

在不同线程中访问同一个ThreadLocal对象,获取到的值却不一样。这是因为不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后从数组中根据当前ThreadLocal的索引去查找出对应的value值,而不同线程中的数组是不一样的。

     6.2  ThreadLocal 内部实现

         

  • 内部实现图解                       

             

       每个Thread维护一个ThreadLocalMap,存储在ThreadLocalMap内的就是一个以Entry为元素的table数组,Entry是一个key-value结构,key为ThreadLocal,value为存储的值。

 static class Entry extends WeakReference<ThreadLocal<?>> {
         
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
 }
  •    主要方法分析

            1 set 方法

public void set(T value) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 根据线程获取ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //调用map.set(ThreadLocal<?> key, Object value)方法
            map.set(this, value);
        else
            //调用createMap创建ThreadLocalMap 
            createMap(t, value);
 }

            getMap(Thread t)

 ThreadLocalMap getMap(Thread t) {
        //返回线程中ThreadLocalMap
        return t.threadLocals;
 }

          threadLocals 在Thread类中进行定义和初始化:

public class Thread implements Runnable {

    ThreadLocal.ThreadLocalMap threadLocals = null;

}

           map.set(ThreadLocal<?> key, Object value)

public class ThreadLocal<T> {
   static class ThreadLocalMap {
       
        private void set(ThreadLocal<?> key, Object value) {
            //创建新的Entry数组并赋值  
            Entry[] tab = table;
            //获取数组长度
            int len = tab.length;
            //根据key值计算出位置
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //Entry存在并且key等于传入的key,直接把新的值赋给Entry。
                if (k == key) {
                    e.value = value;
                    return;
                }
                //Entry存在,key为null,调用replaceStaleEntry来更换key为空的Entry  
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //Entry为null,并且循环过程中没有return掉,在null的位置新建一个Entry,并且插入
            tab[i] = new Entry(key, value);
            //Size增加1
            int sz = ++size;
            //cleanSomeSlots(i, sz) 内部调用expungeStaleEntry函数清理key为null的Entry
            //满足条件,调用rehash()方法    
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

    }   

}

        replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot)

 private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            // Back up to check for prior stale entry in current run.
            // We clean out whole runs at a time to avoid continual
            // incremental rehashing due to garbage collector freeing
            // up refs in bunches (i.e., whenever the collector runs).

            // 向前找到key为null的位置,记录为slotToExpunge
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            // Find either the key or trailing null slot of run, whichever
            // occurs first

            //从staleSlot起到下一个null为止,若是找到key和传入key相等的Entry,就给这个Entry赋 
            //新的value值,并且把它和staleSlot位置的Entry交换,然后调用CleanSomeSlots清理key 
            //为null的Entry。
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();

                // If we find key, then we need to swap it
                // with the stale entry to maintain hash table order.
                // The newly stale slot, or any other stale slot
                // encountered above it, can then be sent to expungeStaleEntry
                // to remove or rehash all of the other entries in run.
                if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    // Start expunge at preceding stale entry if it exists
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                // If we didn't find stale entry on backward scan, the
                // first stale entry seen while scanning for key is the
                // first still present in the run.
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }
         
            // If key not found, put new entry in stale slot

            //若是一直没有key和传入key相等的Entry,在staleSlot处新建一个Entry
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);
  
            // If there are any other stale entries in run, expunge them

            // 清理空key的Entry
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
 }

          rehash()

private void rehash() {
   //调用 expungeStaleEntries()
   expungeStaleEntries();

   //size大于3/4的threshold,则调用resize():
   if (size >= threshold - threshold / 4)
   resize();
}

      expungeStaleEntries()

private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);
            }
 }

    expungeStaleEntry(int staleSlot)

 private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            //将Entry的value设为null,Entry的引用也设为null,系统GC时会清理掉内存
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            //遍历位置staleSlot之后,null之前的Entry数组,清除每一个key为null的Entry,
            //同时若是key不为空,做rehash,调整其位置。
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
 }

       resize()

 private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            //扩容到原来的2倍
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        //key值为空的Entry,对应位置value值置为null,方便GC.
                        e.value = null; 
                    } else {
                        //重新计算key不为空的Entry的hash值,更新位置
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }
            //更新ThreadLocalMap的所有属性。
            setThreshold(newLen);
            size = count;
            table = newTab;
}

         setThreshold(int len)

 private void setThreshold(int len) {
            threshold = len * 2 / 3;
 }

  threshold是扩容上限,当size到达threashold时,需要resize整个Map,threshold的初始值为len * 2 / 3;

 

        2 get 方法

public T get() {
        //获取当前的Thread对象
        Thread t = Thread.currentThread();
        //获取Thread内的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //以当前的ThreadLocal为键,获取Entry对象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                //从Entry中取值
                T result = (T)e.value;
                return result;
            }
        }
        //调用setInitialValue进行初始化
        return setInitialValue();
 }

           map.getEntry(ThreadLocal<?> key)

private Entry getEntry(ThreadLocal<?> key) {
            //计算索引位置
            int i = key.threadLocalHashCode & (table.length - 1);
            //根据索引获取Entry
            Entry e = table[i];
            if (e != null && e.get() == key)
               //直接返回Entry对象
                return e;
            else
                //找不到对应Entry,调用getEntryAfterMiss
                return getEntryAfterMiss(key, i, e);
 }

            getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)

 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
            //Entry不为null,且不满足e.get()==key,进入循环  
            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    //找到所需Entry,直接返回;
                    return e;
                if (k == null)
                    //Entry中key已经为null,Entry是一个过期对象,调用expungeStaleEntry清理Entry
                    expungeStaleEntry(i);
                else
                    //递增i
                    i = nextIndex(i, len);
                //获取新的Entry
                e = tab[i];
            }
            //Entry为null,直接返回
            return null;
 }

            nextIndex(int i, int len)

 private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
 }

      为什么要在getEntryAfterMiss中循环查找Entry?

              主要是因为处理哈希冲突的方法,HashMap采用拉链法处理哈希冲突,即在一个位置已经有元素了,就采用链表把冲突的元素链接在该元素后面,而ThreadLocal采用的是开放地址法,即有冲突后,把要插入的元素放在要插入的位置后面为null的地方。所以上面的循环就是因为我们在第一次计算出来的i位置不一定存在key与我们想查找的key恰好相等的Entry,所以只能不断在后面循环,来查找是不是被插到后面了,直到找到为null的元素,因为若是插入也是到null为止。

             setInitialValue()

 private T setInitialValue() {
        //调用initialValue生成一个初始的value值
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //调用map.set方法
            map.set(this, value);
        else
            //调用createMap创建ThreadLocalMap
            createMap(t, value);
        return value;
 }

            createMap(Thread t, T firstValue)

    void createMap(Thread t, T firstValue) {
        // 调用ThreadLocalMap的构造函数生成一个ThreadLocalMap 
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

           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.
         */
        //定义Entry
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        // 初始容量
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        //主要数据结构(Entry对象的数组table);
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        //用于记录Map中实际存在的entry个数;
        private int size = 0;

        /**
         * The next size value at which to resize.
         */
        //扩容上限,当size到达threashold时,需要resize整个Map 
        private int threshold; // Default to 0

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        //设置初始值为len * 2 /3 
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        //nextIndex和prevIndex用来安全的移动索引
        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        //使用firstKey和firstValue创建一个Entry,并计算索引i
        //把创建好的Entry插入table中 的i位置,再设置size和threshold。
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        /**
         * Construct a new map including all Inheritable ThreadLocals
         * from given parent map. Called only by createInheritedMap.
         *
         * @param parentMap the map associated with parent thread.
         */
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }
 ......      
}

       3 remove 方法             

 private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
 }
  •  注意事项       

       由内部实现图解可知,Entry继承自WeakReference,当ThreadLocal Ref销毁时,指向堆中ThreadLocal实例的唯一 一条强引用消失,只有Entry有一条指向ThreadLocal实例的弱引用。Entry里的key值会为null,直到线程结束前,Entry中的value都将无法回收,会存在内存泄漏的问题。

  • 解决方法

          1 手动调用remove函数,删除不再使用的ThreadLocal。

          2 调用set、get方法, 擦除key为null的Entry。

          2 将ThreadLocal设置成静态,让ThreadLocal尽量和线程本身一起消亡。

 

参考内容:1 :https://juejin.im/post/5a5efb1b518825732b19dca4

                  2 :安卓开发艺术探索(任玉刚)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值