Android開(kāi)發(fā)中Google為什么不讓用Handler的runWithScissors()

一、序
大家好,這里是承香墨影!
runWithScissors() 是 Handler 的一個(gè)方法,被標(biāo)記為 @hide,不允許普通開(kāi)發(fā)者調(diào)用。
這個(gè)方法算是比較冷門(mén),如果面試中被問(wèn)及,面試者不知道時(shí),通常面試官會(huì)換個(gè)問(wèn)法:"如何在子線(xiàn)程通過(guò) Handler 向主線(xiàn)程發(fā)送一個(gè)任務(wù),并等主線(xiàn)程處理此任務(wù)后,再繼續(xù)執(zhí)行?"。
這個(gè)場(chǎng)景,就可以借助 runWithScissors() 來(lái)實(shí)現(xiàn)。雖然該方法被標(biāo)記為 @hide,但是在 Framework 中,也有不少場(chǎng)景使用到它。不過(guò)它也有一些隱患,正是因?yàn)檫@些隱患,讓 Android 工程師將其標(biāo)為 @hide,不允許普通開(kāi)發(fā)者使用。
今天我們就來(lái)聊聊 Handler 的這個(gè)冷門(mén)的方法 runWithScissors(),以及它可能出現(xiàn)的一些問(wèn)題。
二、Handler.runWithScissors()
2.1 runWithScissors()
先撇開(kāi) runWithScissors() 方法,既然這里存在 2 個(gè)線(xiàn)程的通信,那肯定需要考慮多線(xiàn)程同步。
首先想到的就是 Synchronized 鎖和它的等待/通知機(jī)制,而通過(guò) Handler 跨線(xiàn)程通信時(shí),想要發(fā)送一個(gè)「任務(wù)」,Runnable 肯定比 Message 更適合。
接下來(lái),我們看看 runWithScissors() 的實(shí)現(xiàn)是不是如我們預(yù)想一樣。
public final boolean runWithScissors(final Runnable r, long timeout) {
if (r == null) {
throw new IllegalArgumentException("runnable must not be null");
}
if (timeout < 0) {
throw new IllegalArgumentException("timeout must be non-negative");
}
if (Looper.myLooper() == mLooper) {
r.run();
return true;
}
BlockingRunnable br = new BlockingRunnable(r);
return br.postAndWait(this, timeout);
}
可以看到,runWithScissors() 接受一個(gè) Runnable,并且可以設(shè)置超時(shí)時(shí)間。
流程也非常簡(jiǎn)單:
- 先簡(jiǎn)單的對(duì)入?yún)⑦M(jìn)行校驗(yàn);
- 如果當(dāng)前線(xiàn)程和 Handler 的處理線(xiàn)程一致,則直接運(yùn)行
run()方法; - 線(xiàn)程不一致,則通過(guò) BlockingRunnable 包裝一下,并執(zhí)行其
postAndWait()方法;
那再繼續(xù)看看 BlockingRunnable 的源碼。
private static final class BlockingRunnable implements Runnable {
private final Runnable mTask;
private boolean mDone;
public BlockingRunnable(Runnable task) {
mTask = task;
}
@Override
public void run() {
try {
// 運(yùn)行在 Handler 線(xiàn)程
mTask.run();
} finally {
synchronized (this) {
mDone = true;
notifyAll();
}
}
}
public boolean postAndWait(Handler handler, long timeout) {
if (!handler.post(this)) {
return false;
}
synchronized (this) {
if (timeout > 0) {
final long expirationTime = SystemClock.uptimeMillis() + timeout;
while (!mDone) {
long delay = expirationTime - SystemClock.uptimeMillis();
if (delay <= 0) {
return false; // timeout
}
try {
wait(delay);
} catch (InterruptedException ex) {
}
}
} else {
while (!mDone) {
try {
wait();
} catch (InterruptedException ex) {
}
}
}
}
return true;
}
}
待執(zhí)行的任務(wù),會(huì)記入 BlockingRunnable 的 mTask,等待后續(xù)被調(diào)用執(zhí)行。
postAndWait() 的邏輯也很簡(jiǎn)單,先通過(guò) handler 嘗試將 BlockingRunnable 發(fā)出去,之后進(jìn)入 Synchronized 臨界區(qū),嘗試 wait() 阻塞。
如果設(shè)置了 timeout,則使用 wait(timeout) 進(jìn)入阻塞,若被超時(shí)喚醒,則直接返回 false,表示任務(wù)執(zhí)行失敗。
那么現(xiàn)在可以看到 postAndWait() 返回 false 有 2 個(gè)場(chǎng)景:
- Handler
post()失敗,表示 Looper 出問(wèn)題了; - 等待超時(shí),任務(wù)還沒(méi)有執(zhí)行結(jié)束;
除了超時(shí)喚醒外,我們還需要在任務(wù)執(zhí)行完后,喚醒當(dāng)前線(xiàn)程。
回看 BlockingRunnable 的 run() 方法,run() 被 Handler 調(diào)度并在其線(xiàn)程執(zhí)行。在其中調(diào)用 mTask.run(),mTask 即我們需要執(zhí)行的 Runnable 任務(wù)。執(zhí)行結(jié)束后,標(biāo)記 mDone 并通過(guò) notifyAll() 喚醒等待。
任務(wù)發(fā)起線(xiàn)程,被喚醒后,會(huì)判斷 mDone,若為 true 則任務(wù)執(zhí)行完成,直接返回 true 退出。
2.2 Framework 中的使用
runWithScissors() 被標(biāo)記為 @hide,應(yīng)用開(kāi)發(fā)一般是用不上的,但是在 Framework 中,卻有不少使用場(chǎng)景。

例如比較熟悉的 WMS 啟動(dòng)流程中,分別在 main() 和 initPolicy() 中,通過(guò) runWithScissors() 切換到 "android.display" 和 "android.ui" 線(xiàn)程去做一些初始工作。
private void initPolicy() {
UiThread.getHandler().runWithScissors(new Runnable() {
public void run() {
// 運(yùn)行在"android.ui"線(xiàn)程
WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());
mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
}
}, 0);
}
例如上面代碼,就是從 "android.display" 線(xiàn)程,通過(guò)切換到 "android.ui" 線(xiàn)程去執(zhí)行任務(wù)。
三、runWithScissors() 的問(wèn)題
看似 runWithScissors() 通過(guò) Synchronized 的等待通知機(jī)制,配合 Handler 發(fā)送 Runnable 執(zhí)行阻塞任務(wù),看似沒(méi)有問(wèn)題,但依然被 Android 工程師設(shè)為 @hide。
我們繼續(xù)看看它的問(wèn)題。
3.1 如果超時(shí)了,沒(méi)有取消的邏輯
通過(guò) runWithScissors() 發(fā)送 Runnable 時(shí),可以指定超時(shí)時(shí)間。當(dāng)超時(shí)喚醒時(shí),是直接 false 退出。
當(dāng)超時(shí)退出時(shí),這個(gè) Runnable 依然還在目標(biāo)線(xiàn)程的 MessageQueue 中,沒(méi)有被移除掉,它最終還是會(huì)被 Handler 線(xiàn)程調(diào)度并執(zhí)行。
此時(shí)的執(zhí)行,顯然并不符合我們的業(yè)務(wù)預(yù)期。
3.2 可能造成死鎖
而更嚴(yán)重的是,使用 runWithScissors() 可能造成調(diào)用線(xiàn)程進(jìn)入阻塞,而得不到喚醒,如果當(dāng)前持有別的鎖,還會(huì)造成死鎖。
我們通過(guò) Handler 發(fā)送的 MessageQueue 的消息,一般都會(huì)得到執(zhí)行,而當(dāng)線(xiàn)程 Looper 通過(guò) quit() 退出時(shí),會(huì)清理掉還未執(zhí)行的任務(wù),此時(shí)發(fā)送線(xiàn)程,則永遠(yuǎn)得不到喚醒。
那么在使用 runWithScissors() 時(shí),就要求 Handler 所在的線(xiàn)程 Looper,不允許退出,或者使用 quitSafely() 方式退出。
quit() 和 quitSafely() 都表示退出,會(huì)去清理對(duì)應(yīng)的 MessageQueue,區(qū)別在于,qiut() 會(huì)清理 MessageQueue 中所有的消息,而 quitSafely() 只會(huì)清理掉當(dāng)前時(shí)間點(diǎn)之后(when > now)的消息,當(dāng)前時(shí)間之前的消息,依然會(huì)得到執(zhí)行。
那么只要使用 quitSafely() 退出,通過(guò) runWithScissors() 發(fā)送的任務(wù),依然會(huì)被執(zhí)行。
也就是說(shuō),安全使用 runWithScissors() 要滿(mǎn)足 2 個(gè)條件:
- Handler 的 Looper 不允許退出,例如 Android 主線(xiàn)程 Looper 就不允許退出;
- Looper 退出時(shí),使用安全退出
quitSafely()方式退出;
四、總結(jié)時(shí)刻
今天我們介紹了一個(gè)冷門(mén)的方法 runWithScissors() 以及其原理,可以通過(guò)阻塞的方式,向目標(biāo)線(xiàn)程發(fā)送任務(wù),并等待任務(wù)執(zhí)行結(jié)束。
雖然被它標(biāo)記為 @hide,無(wú)法直接使用,但這都是純軟件實(shí)現(xiàn),我們其實(shí)可以自己實(shí)現(xiàn)一個(gè) BlockingRunnable 去使用。當(dāng)然原本存在的問(wèn)題,在使用時(shí)也需要注意。
我知道就算這個(gè)方法不被標(biāo)記為 @hide,使用的場(chǎng)景也非常的少,但是它依然可以幫助我們思考一些臨界問(wèn)題,線(xiàn)程的同步、死鎖,以及 Handler 的退出方式對(duì)消息的影響。
到此這篇關(guān)于Android開(kāi)發(fā)中Google為什么不讓用Handler的runWithScissors()的文章就介紹到這了,更多相關(guān)Android runWithScissors()內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
實(shí)例講解Android應(yīng)用開(kāi)發(fā)中TabHost的使用要點(diǎn)
這篇文章主要介紹了Android應(yīng)用開(kāi)發(fā)中TabHost的使用要點(diǎn),文中以實(shí)例講解了TabHost與Tab的布局方法,需要的朋友可以參考下2016-04-04
Android自定義View實(shí)現(xiàn)公交成軌跡圖
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)公交成軌跡圖,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06
Android中LeakCanary檢測(cè)內(nèi)存泄漏的方法
本篇文章主要介紹了Android中LeakCanary檢測(cè)內(nèi)存泄漏的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09
Android TabLayout 實(shí)現(xiàn)底部Tab的示例代碼
本篇文章主要介紹了Android TabLayout 實(shí)現(xiàn)底部Tab的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
Android強(qiáng)制下線(xiàn)功能實(shí)現(xiàn)的代碼示例
本篇文章主要介紹了Android強(qiáng)制下線(xiàn)功能實(shí)現(xiàn)的代碼示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02
百度地圖API提示230 錯(cuò)誤app scode碼校驗(yàn)失敗的解決辦法
筆者近2天在 Android Studio上玩了一下百度地圖,碰到了常見(jiàn)的"230錯(cuò)誤 APP Scode校驗(yàn)失敗",下面我來(lái)介紹一下具體的解決辦法2016-01-01
Android如何自定義升級(jí)對(duì)話(huà)框示例詳解
對(duì)話(huà)框是我們?cè)谄綍r(shí)經(jīng)常會(huì)遇到的一個(gè)功能,但自帶的對(duì)話(huà)框不夠美觀,大家一般都會(huì)自定義,下面這篇文章主要給大家介紹了關(guān)于Android如何自定義升級(jí)對(duì)話(huà)框的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-08-08
Android中實(shí)現(xiàn)用命令行同步網(wǎng)絡(luò)時(shí)間
這篇文章主要介紹了Android中實(shí)現(xiàn)用命令行同步網(wǎng)絡(luò)時(shí)間,本文講解使用BusyBox實(shí)現(xiàn)同步網(wǎng)絡(luò)時(shí)間,并給出了詳細(xì)操作步驟,需要的朋友可以參考下2015-07-07
Android開(kāi)發(fā)實(shí)現(xiàn)TextView顯示豐富的文本
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)TextView顯示豐富的文本,涉及Android中TextView的使用技巧,需要的朋友可以參考下2015-12-12

