Log.i(TAG, “main thread, sendMessage”);
Message message = Message.obtain();
message.what = 100;
mHandler.sendMessage(message);
}
1.2Handler的使用背景
Handler可以将子线程中更新UI的任务切换到主线程。为什么要切换呢?我们知道,UI的访问只能在主线程进行。子线程访问UI就会出现异常,因为在ViewRootImpl中对线程做了校验,只有创建了这个View树的线程,才能访问这个view。 一般情况创建View的线程就是主线程,即UI线程,所以子线程访问会异常。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
“Only the original thread that created a view hierarchy can touch its views.”);
}
}
而且,UI线程一般不能做耗时操作,不然会发生ANR。所以当 在子线程做完耗时操作后 又需要更新UI,这时就需要用到Handler了。那为啥一定要用checkThread()保证不让子线程访问UI呢? 因为UI控件不是线程安全的。那为啥不加锁呢?一是加锁会让UI访问变得复杂;二是加锁会降低UI访问效率,会阻塞一些线程访问UI。所以干脆使用单线程模型处理UI操作,使用时用Handler切换即可。
二、Android消息机制分析
前面说了,Android消息机制包含几个概念:Handler、MessageQueue、Looper、Looper内部使用到的ThreadLocal。下面详细介绍下。
2.1 ThreadLocal
外界想要在不同thread中存值,就可以threadLocal = new ThreadLocal,然后在不同线程中threadLocal.set(value)就可以了,获取值用threadLocal.get() 。
举个例子🌰,下面例子中 先只看booleanThreadLocal,在主线程设置true,a线程设置false,b线程设置null,然后每个线程都打印 booleanThreadLocal.get()的结果,发现每个线程get的值是不同的,是在每个线程中set的值。这就是神奇之处,同样的booleanThreadLocal.get(),所在线程不同,结果就不同。
ThreadLocal booleanThreadLocal = new ThreadLocal<>();
ThreadLocal integerThreadLocal = new ThreadLocal<>();
booleanThreadLocal.set(true);
integerThreadLocal.set(0);
Log.i(TAG, "testThreadLocal: main thread, boolean= "+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: main thread, int = "+integerThreadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
booleanThreadLocal.set(false);
integerThreadLocal.set(1);
Log.i(TAG, “testThreadLocal: a thread, boolean=”+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: a thread, int = "+integerThreadLocal.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
booleanThreadLocal.set(null);
integerThreadLocal.set(2);
Log.i(TAG, “testThreadLocal: b thread, boolean=”+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: b thread, int = "+integerThreadLocal.get());
}
}).start();
结果:
2020-01-08 10:15:38.623 8976-8976/com.hfy.demo01 I/hfy: testThreadLocal: main thread, boolean= true
2020-01-08 10:15:38.623 8976-8976/com.hfy.demo01 I/hfy: testThreadLocal: main thread, int = 0
2020-01-08 10:15:38.624 8976-9226/com.hfy.demo01 I/hfy: testThreadLocal: a thread, boolean=false
2020-01-08 10:15:38.624 8976-9226/com.hfy.demo01 I/hfy: testThreadLocal: a thread, int = 1
2020-01-08 10:15:38.626 8976-9227/com.hfy.demo01 I/hfy: testThreadLocal: b thread, boolean=null
2020-01-08 10:15:38.626 8976-9227/com.hfy.demo01 I/hfy: testThreadLocal: b thread, int = 2
下面看下ThreadLocal的get()、set()方法。
/**
- Returns the value in the current thread’s copy of this
- thread-local variable. If the variable has no value for the
- current thread, it is first initialized to the value returned
- by an invocation of the {@link #initialValue} method.
- @return the current thread’s value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings(“unchecked”)
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
/**
- Variant of set() to establish initialValue. Used instead
- of set() in case user has overridden the set() method.
- @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
get():获取当前线程的ThreadLocalMap,这里可以先理解成普通 键值对的Map。然后传入threadLocal实例,获取键值对Entry,然后获取Entry的value。如果map为空或value为空则会初始化map、value。
/**
- Sets the current thread’s copy of this thread-local variable
- to the specified value. Most subclasses will have no need to
- override this method, relying solely on the {@link #initialValue}
- method to set the values of thread-locals.
- @param value the value to be stored in the current thread’s copy of
-
this thread-local.
/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/*
- Create the map associated with a ThreadLocal. Overridden in
- InheritableThreadLocal.
- @param t the current thread
- @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
set()中也是获取当前线程的ThreadLocalMap,然后ThreadLocal实例作为key, 和value一起设置给map。没有map就去创建并把value初始化进去。
我们再去看下Thread,有个默认为空的ThreadLocalMap实例threadLocals。
/* ThreadLocal values pertaining to this thread. This map is maintained
- by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
那ThreadLocalMap是啥呢?ThreadLocalMap是ThreadLocal的内部类,作用类似Map,内部有个Entry[]的属性table。所以上面看的get、set方法就是对ThreadLocalMap的Entry[]取和存 。下面详细看下。
/**
- 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.
*/
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);
}
/**
- Get the entry associated with key. This method
- itself handles only the fast path: a direct hit of existing
- key. It otherwise relays to getEntryAfterMiss. This is
- designed to maximize performance for direct hits, in part
- by making this method readily inlinable.
- @param key the thread local object
- @return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
/**
- 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();
}
使用Entry[] 存多个threadLocal-value键值对,数组下标index与是ThreadLocal 实例的hashCode相关。而ThreadLocalMap唯一实例是createMap(Thread t, T firstValue)赋给Thread的变量threadLocals。 例如 线程A threadLocalMap的table[] 可以存储 int、String、boolean类型的3个键值对threadLocal-int, threadLocal-String、threadLocal-Boolean。还是上面的例子。
(常规的HashMap的键值得类型是固定的;threadLocalMap的key是ThreadLocal,value是T,即可以存多种类型的value)
ThreadLocal booleanThreadLocal = new ThreadLocal<>();
ThreadLocal integerThreadLocal = new ThreadLocal<>();
booleanThreadLocal.set(true);
integerThreadLocal.set(0);
Log.i(TAG, "testThreadLocal: main thread, boolean= "+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: main thread, int = "+integerThreadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
booleanThreadLocal.set(false);
integerThreadLocal.set(1);
Log.i(TAG, “testThreadLocal: a thread, boolean=”+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: a thread, int = "+integerThreadLocal.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
booleanThreadLocal.set(null);
integerThreadLocal.set(2);
Log.i(TAG, “testThreadLocal: b thread, boolean=”+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: b thread, int = "+integerThreadLocal.get());
}
}).start();
结果:
2020-01-08 10:15:38.623 8976-8976/com.hfy.demo01 I/hfy: testThreadLocal: main thread, boolean= true
2020-01-08 10:15:38.623 8976-8976/com.hfy.demo01 I/hfy: testThreadLocal: main thread, int = 0
2020-01-08 10:15:38.624 8976-9226/com.hfy.demo01 I/hfy: testThreadLocal: a thread, boolean=false
2020-01-08 10:15:38.624 8976-9226/com.hfy.demo01 I/hfy: testThreadLocal: a thread, int = 1
2020-01-08 10:15:38.626 8976-9227/com.hfy.demo01 I/hfy: testThreadLocal: b thread, boolean=null
2020-01-08 10:15:38.626 8976-9227/com.hfy.demo01 I/hfy: testThreadLocal: b thread, int = 2
到目前为止我们知道,ThreadLocal的作用,就是操作线程内部的threadLocals,存和取value。value的实际类型就是 实例化ThreadLocal时定义的泛型T。
2.2 messageQueue
messageQueue,消息队列,实际是单向链表。看下存、取消息。
enqueueMessage(),存消息,单链表的插入。
boolean enqueueMessage(Message msg, long when) {
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.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;
}
next():取一条消息,没有消息就无限循环,会阻塞。
Message next() {
//…
//有msg就return,没有消息就无限循环,会阻塞。如quit,return null。
for (;😉 {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
//有消息就return
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
//quit后返回null
return null;
}
// …
}
2.3 Looper
looper,消息循环器。
先看静态方法prepare():
// sThreadLocal.get() will return null unless you’ve called prepare().
static final ThreadLocal sThreadLocal = new ThreadLocal();
/** Initialize the current thread as a looper.
- This gives you a chance to create handlers that then reference
- this looper, before actually starting the loop. Be sure to call
- {@link #loop()} after calling this method, and end it by calling
- {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
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));
}
/**
- 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 Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
可见sThreadLocal是个静态常量,value类型是Looper。 prepare()方法调sThreadLocal.set(new Looper),创建looper实例,设置给当前线程ThreadLocalMap属性中的table[i](i是threadLocal实例的hashCode相关)。
且创建looper实例时默认创建了对应的消息队列mQueue实例。另外,prepareMainLooper()是主线程,是给主线程创建looper实例。
再看下获取looper实例、queue实例的方法:
/**
- Returns the application’s main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
/**
- Return the Looper object associated with the current thread. Returns
- null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
/**
- Return the {@link MessageQueue} object associated with the current
- thread. This must be called from a thread running a Looper, or a
- NullPointerException will be thrown.
*/
public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
myLooper() 方法,调用sThreadLocal.get()。就是上面讲解的ThreadLocal的使用方法。通过静态常量sThreadLocal获取对应每个线程的Looper实例。
looper的quit,两种,立即退出,执行完消息再退出。
/**
- Quits the looper.
-
- Causes the {@link #loop} method to terminate without processing any
- more messages in the message queue.
-
- Any attempt to post messages to the queue after the looper is asked to quit will fail.
- For example, the {@link Handler#sendMessage(Message)} method will return false.
-
- Using this method may be unsafe because some messages may not be delivered
- before the looper terminates. Consider using {@link #quitSafely} instead to ensure
- that all pending work is completed in an orderly manner.
- @see #quitSafely
*/
public void quit() {
mQueue.quit(false);
}
/**
- Quits the looper safely.
-
- Causes the {@link #loop} method to terminate as soon as all remaining messages
- in the message queue that are already due to be delivered have been handled.
- However pending delayed messages with due times in the future will not be
- delivered before the loop terminates.
-
- Any attempt to post messages to the queue after the looper is asked to quit will fail.
- For example, the {@link Handler#sendMessage(Message)} method will return false.
*/
public void quitSafely() {
mQueue.quit(true);
}
写在最后
本次我的分享也接近尾声了,感谢你们在百忙中花上一下午来这里聆听我的宣讲,希望在接下来的日子,我们共同成长,一起进步!!!
最后放上一个大概的Android学习方向及思路(详细的内容太多了~),提供给大家:
对于程序员来说,要学习的知识内容、技术有太多太多,这里就先放上一部分,其他的内容有机会在后面的文章向大家呈现出来,不过我自己所有的学习资料都整理成了一个文档,一直在不断学习,希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!
Android架构师之路很漫长,一起共勉吧!
如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言,一定会认真查询,修正不足,谢谢。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
放上一个大概的Android学习方向及思路(详细的内容太多了~),提供给大家:
[外链图片转存中…(img-ppEl667w-1715769170170)]
对于程序员来说,要学习的知识内容、技术有太多太多,这里就先放上一部分,其他的内容有机会在后面的文章向大家呈现出来,不过我自己所有的学习资料都整理成了一个文档,一直在不断学习,希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!
Android架构师之路很漫长,一起共勉吧!
如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言,一定会认真查询,修正不足,谢谢。
[外链图片转存中…(img-0c8fbSEP-1715769170172)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!