自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面試官:"Handler的runWithScissors()了解嗎?為什么Google不讓開發(fā)者用?"

開發(fā) 開發(fā)工具
runWithScissors() 是 Handler 的一個方法,被標記為 @hide,不允許普通開發(fā)者調(diào)用。

 [[334762]]

一、序

大家好,這里是承香墨影!

runWithScissors() 是 Handler 的一個方法,被標記為 @hide,不允許普通開發(fā)者調(diào)用。

這個方法算是比較冷門,如果面試中被問及,面試者不知道時,通常面試官會換個問法:"如何在子線程通過 Handler 向主線程發(fā)送一個任務,并等主線程處理此任務后,再繼續(xù)執(zhí)行?"。

這個場景,就可以借助 runWithScissors() 來實現(xiàn)。雖然該方法被標記為 @hide,但是在 Framework 中,也有不少場景使用到它。不過它也有一些隱患,正是因為這些隱患,讓 Android 工程師將其標為 @hide,不允許普通開發(fā)者使用。

今天我們就來聊聊 Handler 的這個冷門的方法 runWithScissors(),以及它可能出現(xiàn)的一些問題。

二、Handler.runWithScissors()

2.1 runWithScissors()

先撇開 runWithScissors() 方法,既然這里存在 2 個線程的通信,那肯定需要考慮多線程同步。

首先想到的就是 Synchronized 鎖和它的等待/通知機制,而通過 Handler 跨線程通信時,想要發(fā)送一個「任務」,Runnable 肯定比 Message 更適合。

接下來,我們看看 runWithScissors() 的實現(xiàn)是不是如我們預想一樣。

  1. public final boolean runWithScissors(final Runnable r, long timeout) { 
  2.   if (r == null) { 
  3.     throw new IllegalArgumentException("runnable must not be null"); 
  4.   } 
  5.   if (timeout < 0) { 
  6.     throw new IllegalArgumentException("timeout must be non-negative"); 
  7.   } 
  8.  
  9.   if (Looper.myLooper() == mLooper) { 
  10.     r.run(); 
  11.     return true
  12.   } 
  13.  
  14.   BlockingRunnable br = new BlockingRunnable(r); 
  15.   return br.postAndWait(this, timeout); 

可以看到,runWithScissors() 接受一個 Runnable,并且可以設置超時時間。

流程也非常簡單:

  1. 先簡單的對入?yún)⑦M行校驗;
  2. 如果當前線程和 Handler 的處理線程一致,則直接運行 run() 方法;
  3. 線程不一致,則通過 BlockingRunnable 包裝一下,并執(zhí)行其 postAndWait() 方法;

那再繼續(xù)看看 BlockingRunnable 的源碼。

  1. private static final class BlockingRunnable implements Runnable { 
  2.   private final Runnable mTask; 
  3.   private boolean mDone; 
  4.  
  5.   public BlockingRunnable(Runnable task) { 
  6.     mTask = task; 
  7.   } 
  8.  
  9.   @Override 
  10.   public void run() { 
  11.     try { 
  12.       // 運行在 Handler 線程 
  13.       mTask.run(); 
  14.     } finally { 
  15.       synchronized (this) { 
  16.         mDone = true
  17.         notifyAll(); 
  18.       } 
  19.     } 
  20.   } 
  21.  
  22.   public boolean postAndWait(Handler handler, long timeout) { 
  23.     if (!handler.post(this)) { 
  24.       return false
  25.     } 
  26.  
  27.     synchronized (this) { 
  28.       if (timeout > 0) { 
  29.         final long expirationTime = SystemClock.uptimeMillis() + timeout; 
  30.         while (!mDone) { 
  31.           long delay = expirationTime - SystemClock.uptimeMillis(); 
  32.           if (delay <= 0) { 
  33.             return false; // timeout 
  34.           } 
  35.           try { 
  36.             wait(delay); 
  37.           } catch (InterruptedException ex) { 
  38.           } 
  39.         } 
  40.       } else { 
  41.         while (!mDone) { 
  42.           try { 
  43.             wait(); 
  44.           } catch (InterruptedException ex) { 
  45.           } 
  46.         } 
  47.       } 
  48.     } 
  49.     return true
  50.   } 

待執(zhí)行的任務,會記入 BlockingRunnable 的 mTask,等待后續(xù)被調(diào)用執(zhí)行。

postAndWait() 的邏輯也很簡單,先通過 handler 嘗試將 BlockingRunnable 發(fā)出去,之后進入 Synchronized 臨界區(qū),嘗試 wait() 阻塞。

如果設置了 timeout,則使用 wait(timeout) 進入阻塞,若被超時喚醒,則直接返回 false,表示任務執(zhí)行失敗。

那么現(xiàn)在可以看到 postAndWait() 返回 false 有 2 個場景:

  1. Handler post() 失敗,表示 Looper 出問題了;
  2. 等待超時,任務還沒有執(zhí)行結(jié)束;

除了超時喚醒外,我們還需要在任務執(zhí)行完后,喚醒當前線程。

回看 BlockingRunnable 的 run() 方法,run() 被 Handler 調(diào)度并在其線程執(zhí)行。在其中調(diào)用 mTask.run(),mTask 即我們需要執(zhí)行的 Runnable 任務。執(zhí)行結(jié)束后,標記 mDone 并通過 notifyAll() 喚醒等待。

任務發(fā)起線程,被喚醒后,會判斷 mDone,若為 true 則任務執(zhí)行完成,直接返回 true 退出。

2.2 Framework 中的使用

runWithScissors() 被標記為 @hide,應用開發(fā)一般是用不上的,但是在 Framework 中,卻有不少使用場景。

例如比較熟悉的 WMS 啟動流程中,分別在 main() 和 initPolicy() 中,通過 runWithScissors() 切換到 "android.display" 和 "android.ui" 線程去做一些初始工作。

  1. private void initPolicy() { 
  2.   UiThread.getHandler().runWithScissors(new Runnable() { 
  3.     public void run() { 
  4.       // 運行在"android.ui"線程 
  5.       WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper()); 
  6.       mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this); 
  7.     } 
  8.   }, 0); 

例如上面代碼,就是從 "android.display" 線程,通過切換到 "android.ui" 線程去執(zhí)行任務。

三、runWithScissors() 的問題

看似 runWithScissors() 通過 Synchronized 的等待通知機制,配合 Handler 發(fā)送 Runnable 執(zhí)行阻塞任務,看似沒有問題,但依然被 Android 工程師設為 @hide。

我們繼續(xù)看看它的問題。

3.1 如果超時了,沒有取消的邏輯

通過 runWithScissors() 發(fā)送 Runnable 時,可以指定超時時間。當超時喚醒時,是直接 false 退出。

當超時退出時,這個 Runnable 依然還在目標線程的 MessageQueue 中,沒有被移除掉,它最終還是會被 Handler 線程調(diào)度并執(zhí)行。

此時的執(zhí)行,顯然并不符合我們的業(yè)務預期。

3.2 可能造成死鎖

而更嚴重的是,使用 runWithScissors() 可能造成調(diào)用線程進入阻塞,而得不到喚醒,如果當前持有別的鎖,還會造成死鎖。

我們通過 Handler 發(fā)送的 MessageQueue 的消息,一般都會得到執(zhí)行,而當線程 Looper 通過 quit() 退出時,會清理掉還未執(zhí)行的任務,此時發(fā)送線程,則永遠得不到喚醒。

那么在使用 runWithScissors() 時,就要求 Handler 所在的線程 Looper,不允許退出,或者使用 quitSafely() 方式退出。

quit() 和 quitSafely() 都表示退出,會去清理對應的 MessageQueue,區(qū)別在于,qiut() 會清理 MessageQueue 中所有的消息,而 quitSafely() 只會清理掉當前時間點之后(when > now)的消息,當前時間之前的消息,依然會得到執(zhí)行。

那么只要使用 quitSafely() 退出,通過 runWithScissors() 發(fā)送的任務,依然會被執(zhí)行。

也就是說,安全使用 runWithScissors() 要滿足 2 個條件:

Handler 的 Looper 不允許退出,例如 Android 主線程 Looper 就不允許退出;

Looper 退出時,使用安全退出 quitSafely() 方式退出;

四、總結(jié)時刻

今天我們介紹了一個冷門的方法 runWithScissors() 以及其原理,可以通過阻塞的方式,向目標線程發(fā)送任務,并等待任務執(zhí)行結(jié)束。

雖然被它標記為 @hide,無法直接使用,但這都是純軟件實現(xiàn),我們其實可以自己實現(xiàn)一個 BlockingRunnable 去使用。當然原本存在的問題,在使用時也需要注意。

我知道就算這個方法不被標記為 @hide,使用的場景也非常的少,但是它依然可以幫助我們思考一些臨界問題,線程的同步、死鎖,以及 Handler 的退出方式對消息的影響。

 

 

責任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2022-07-26 08:40:42

Java并發(fā)工具類

2022-08-02 06:31:32

Java并發(fā)工具類

2017-02-14 15:18:44

GoogleAndroid

2021-02-19 10:02:57

HTTPSJava安全

2022-07-06 13:48:24

RedisSentinel機制

2022-06-30 08:14:05

Java阻塞隊列

2022-07-11 10:47:46

容器JAVA

2023-12-06 09:10:28

JWT微服務

2020-10-24 15:50:54

Java值傳遞代碼

2021-01-21 07:53:29

面試官Promis打印e

2016-03-17 11:06:46

跳槽加薪面試

2024-09-09 08:30:56

代碼

2022-06-30 14:31:57

Java阻塞隊列

2022-12-27 08:39:54

MySQL主鍵索引

2022-06-08 13:54:23

指令重排Java

2010-08-17 09:01:39

jQueryAPI

2021-12-20 10:30:33

forforEach前端

2020-09-26 22:04:32

數(shù)據(jù)安全傳輸HTTPSHTTP 協(xié)議

2024-09-03 07:58:46

2022-06-10 13:56:42

Java
點贊
收藏

51CTO技術(shù)棧公眾號