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