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 :安卓开发艺术探索(任玉刚)