Handler源码精解读,Android消息机制全面掌握!(1)

}

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

静态方法loop():用threadLocal.get()获取当前线程的Looper,然后拿到queue,循环取消息,给到handler的dispatchMessage方法-handleMessage方法。唯一跳出循环是取到null,null是因为调用了quit或quitSafly。 因为静态方法loop()是在线程中调用的,所以不论handler从哪里发送msg都会在loop的线程中执行

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-OaYNGWi3-1715622957265)]

[外链图片转存中…(img-AUaApj9k-1715622957267)]

[外链图片转存中…(img-on1PpJa3-1715622957268)]

[外链图片转存中…(img-4Cux7ozG-1715622957269)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值