1.一個線程可以有多個Handler嗎斜友?
答:可以。例如垃它,Android 的 每一個Activity都可以創(chuàng)建Handler對象鲜屏,但它們都是運(yùn)行在同一個主線程中的。
2. 一個線程有幾個Looper国拇?如何保證的墙歪?
答:一個線程只有一個Looper對象。
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));
}
從Looper的源碼得知贝奇,在looper的prepare()函數(shù)中虹菲,其通過threadlocal來獲取looper的對象。
/**
* 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();
}
上面為threadlocal的get()函數(shù)掉瞳”显矗可以看出threadlocal的內(nèi)部維護(hù)了一個ThreadLocalMap類。該類是以當(dāng)前thread做為key的陕习。因此可以得知霎褐,一個線程最多只能有一個looper對象。
/**
* 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);
}
以上為threadlocal的set方法该镣。
3.Handler內(nèi)存泄露的原因冻璃?為什么其他的內(nèi)部類沒有說有這個問題?
1.所有的非靜態(tài)內(nèi)部類都會持有外部類的引用损合。當(dāng)外部類需要銷毀而內(nèi)部類仍在運(yùn)行時省艳,java的GC機(jī)制將導(dǎo)致外部類的引用不會被銷毀,從而導(dǎo)致內(nèi)存泄露嫁审。所以所有的非靜態(tài)內(nèi)部類都可能會導(dǎo)致內(nèi)存泄露跋炕。
2.MessageQueue存儲了Message,而Message的target屬性為handler對象律适,handler又持有的Activity等context的對象辐烂,這就導(dǎo)致了內(nèi)存泄露遏插。
public static void loop() {
//省略代碼
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//省略代碼
}
以上為looper的loop方法。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
以上為handler的enqueueMessage函數(shù)(所有的send和post方法最終都會調(diào)用該函數(shù))
4.怎么處理handler的內(nèi)存泄露纠修;
1.靜態(tài)內(nèi)部類+弱引用胳嘲;
2.針對與在子線程開啟的消息隊(duì)列,可以在需要結(jié)束的時候扣草,調(diào)用lopper的quitSafely()方法了牛。
上代碼:
public void quit() {
mQueue.quit(false);
}
/**
* Quits the looper safely.
* <p>
* 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.
* </p><p>
* 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.
* </p>
*/
public void quitSafely() {
mQueue.quit(true);
}
以上為looper的quit函數(shù)。
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
以上為MessageQueue的quit函數(shù)德召。在此函數(shù)里,消息隊(duì)列會移除隊(duì)列里的所有消息汽纤。此時MessageQueue將不會持有message,更不會間接的持有handler的引用上岗。從而消除handler的內(nèi)存泄露問題。
補(bǔ)充:
在子線程內(nèi)開啟消息隊(duì)列蕴坪,
1.先通過調(diào)用Looper.prepare()函數(shù)來創(chuàng)建looper對象肴掷;
2.創(chuàng)建handler對象;
3.調(diào)用looper.loop()函數(shù)來循環(huán)不斷的從messagequeue里獲取message背传;
4.結(jié)束消息隊(duì)列時呆瞻,調(diào)用looper.quit()函數(shù)來清空messagequeue里的消息,來防止內(nèi)存泄露径玖。
5.既然可以存在多個Handler往MessageQueue中添加數(shù)據(jù)(發(fā)送消息時痴脾,各個Handler可能處于不同的線程),它內(nèi)部是如何保持線程安全的梳星?
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;
}
通過synchronized鎖機(jī)制保證線程安全
6.使用Handler的postDelay后消息隊(duì)列有什么變化赞赖?
消息隊(duì)列會重新排序。
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;
}
handler的postDelay()函數(shù)最終會調(diào)用messagequeue的enqueueMessage()函數(shù)冤灾。
該函數(shù)里會按照執(zhí)行開始時間前域,對messagequeue里的message進(jìn)行重新排序。
7.Looper死循環(huán)為什么不會導(dǎo)致應(yīng)用卡死韵吨?
1.導(dǎo)致應(yīng)用卡死的原因只有一下幾種情況:
a)輸入事件在5秒內(nèi)未響應(yīng)匿垄;
b)在主線程執(zhí)行耗時操作;
2.Looper.loop()函數(shù)會在死循環(huán)里不斷的去獲取messagequeue里的message归粉,當(dāng)消息隊(duì)列里沒有消息的時候椿疗,looper會進(jìn)入睡眠階段。Looper為了防止message執(zhí)行過于耗時的操作糠悼,導(dǎo)致隊(duì)列阻塞变丧,就給message設(shè)置一個ANR的狀態(tài)【铌可見痒蓬,looper的死循環(huán)和應(yīng)用卡死是兩個不同的概念童擎。