深入理解Android消息机制

at android.os.Handler.(Handler.java:206)

at android.os.Handler.(Handler.java:119)

at lonbon.com.hanlderlooperdemo.MainActivity$1.run(MainActivity.java:24)

at java.lang.Thread.run(Thread.java:764)

这只因为默认的线程中没有Looper,所以我们要为当前线程创建Looper对象:

Looper.prepare();

Handler handler = new Handler();

Looper.loop();

我们通过Looper.prepare()方法创建Looper对象,.loop()方法开启消息循环。关于looper详细的下面讲解。

那么我们可能有疑问了,我们在主线程中使用Handler的时候没有创建looper对象也可以正常使用,那是因为默认的UI线程创建的时候默认创建了looper对象。

创建完Handler之后,通过handler的send或者post方法发送消息,这个消息会被存储到消息队列,Looper发现消息队列中有新的消息便会处理这个消息,然后handlermessage方法或者Runable方法会被调用,大致过程如图所示。

三、ThreadLocal


ThreadLocal是Looper中的特殊概念,用来在当前线程中存储数据,我们获取当前线程的Looper也是通过ThreadLocal操作的,当然,日常开发中我们能使用的ThreadLocal的地方并不多。比如我们在两个不同线程中进行如下操作:

首先我们声明一个String类型的ThreadLocal变量,创建两个线程分别使用set方法赋值,然后打印。

private ThreadLocal threadLocal = new ThreadLocal<>();

/**

  • 测试线程1

*/

private void ThreadTest1() {

new Thread(new Runnable() {

@Override

public void run() {

threadLocal.set(“BigYellow”);

Log.d(TAG,threadLocal.get());

}

}).start();

}

/**

  • 测试线程2

*/

private void ThreadTest2() {

new Thread(new Runnable() {

@Override

public void run() {

threadLocal.set(“大黄”);

Log.d(TAG,threadLocal.get());

}

}).start();

}

运行打印,日志如下:

02-12 10:21:37.961 11719-12135/? D/TAG: BigYellow

02-12 10:21:37.966 11719-12136/? D/TAG: 大黄

我们可以看到取出的分别是各自线程对应的值,如果我们在主线程中呢?显然是null因为我们没有在主线程中存值。

接下来我们从源码的角度来分析ThreadLocal的存取值过程,首先我们看set方法。

public void set(T value) {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null)

map.set(this, value);

else

createMap(t, value);

}

首先通过getMap方法获取当前线程的ThreadLocalMap,如果map不为空就通过map的set方法将值存储,如果为空则创建map,

我们来看下ThreadLocalMap,ThreadLocalMap是一个存储当前线程数据的Map集合,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类型的数组,我们主要来看for循环中的操作,for循环主要做的就是为插入值得位置找到合适的位置,通过不断到table数组中去寻找,直到存放的entry为null

if (k == key) {

e.value = value;

return;

}

如果key的值相同说明该线程曾经设置过Threadlocal,直接赋值即可。

if (k == null) {

replaceStaleEntry(key, value, i);

return;

}

Entry继承的是WeakReference,这是弱引用带来的坑

所以要判断是否为null,如果为null就进行置换操作,即

replaceStaleEntry(key, value, i);

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).

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

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

tab[staleSlot].value = null;

tab[staleSlot] = new Entry(key, value);

// If there are any other stale entries in run, expunge them

if (slotToExpunge != staleSlot)

cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);

}

当table数组中存储的ThreadLocal对应的值还在但是key不存在了,就认为Entry过期了,

int slotToExpunge = staleSlot;

for (int i = prevIndex(staleSlot, len);

(e = tab[i]) != null;

i = prevIndex(i, len))

if (e.get() == null)

slotToExpunge = i;

上述代码检查脏数据,清理整个table,否则会因为GC问题导致很严重的后果。

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;

}

如果找到key了我们需要进行替换,将过期数据进行删除刷新。源代码中注释的很清楚了,这里就不一一解释了。

个人感觉和之前早期版本(6.0之前)的set方法变化很大。

我们接下来来看ThreadLocal的get方法,首先同样的获取当前线程的ThreadLocalMap,获取map的entry对象,如果不为空的话就从中取值即可。如果map为空就回到setInitialValue初始化方法.

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

}

四、Looper


Looper我们上面说了是用来构建消息循环系统,我们通过ThreadLocal来获取当前线程的Looper对象.我们上面也说到了如何在子线程中创建looper,通过Looper的prepare方法为当前线程创建一个looper,通过loop方法开启消息循环。在主线程中创建Looper是通过

Looper.prepareMainLooper();

方法,因为UI线程的Looper比较特殊是默认创建好的,所以我们可以通过下列代码来获取主线程的looper

Looper.getMainLooper();

我们可以开启looper肯定也可以关闭looper,关闭looper有这个方法,一个是

getMainLooper().quit();

public void quit() {

mQueue.quit(false);

}

quit方法会直接退出looper,另一种方法是

getMainLooper().quitSafely();

和quit方法不同的是quitSafely方法调用后在消息队列中的消息处理完成之后在退出,就像方法名一样是安全退出。所以如果我们在子线程中手动创建了looper,记得在执行完线程后调用退出方法,否则子线程会一直处于等待状态,影响性能。

接下来我们看looper是如何通过loop方法开启消息循环的,loop方法源码如下所示:

/**

  • Run the message queue in this thread. Be sure to call

  • {@link #quit()} to end the 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.”);

总结:

面试是一个不断学习、不断自我提升的过程,有机会还是出去面面,至少能想到查漏补缺效果,而且有些知识点,可能你自以为知道,但让你说,并不一定能说得很好。

有些东西有压力才有动力,而学到的知识点,都是钱(因为技术人员大部分情况是根据你的能力来定级、来发薪水的),技多不压身。

附上我的面试各大专题整理: 面试指南,满满的都是干货,希望对大家有帮助!

线程后调用退出方法,否则子线程会一直处于等待状态,影响性能。

接下来我们看looper是如何通过loop方法开启消息循环的,loop方法源码如下所示:

/**

  • Run the message queue in this thread. Be sure to call

  • {@link #quit()} to end the 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.”);

总结:

面试是一个不断学习、不断自我提升的过程,有机会还是出去面面,至少能想到查漏补缺效果,而且有些知识点,可能你自以为知道,但让你说,并不一定能说得很好。

有些东西有压力才有动力,而学到的知识点,都是钱(因为技术人员大部分情况是根据你的能力来定级、来发薪水的),技多不压身。

附上我的面试各大专题整理: 面试指南,满满的都是干货,希望对大家有帮助!
[外链图片转存中…(img-QrQxQ6g0-1726044494747)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值