欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時間:2021年09月09日 09:17:11   作者:承香墨影  
這篇文章主要介紹了Android開發(fā)中Google為什么不讓用Handler的runWithScissors(),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

一、序

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

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

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

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

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

二、Handler.runWithScissors()

2.1 runWithScissors()

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

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

接下來,我們看看 runWithScissors() 的實現(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() 接受一個 Runnable,并且可以設(shè)置超時時間。

流程也非常簡單:

  1. 先簡單的對入?yún)⑦M(jìn)行校驗;
  2. 如果當(dāng)前線程和 Handler 的處理線程一致,則直接運行 run() 方法;
  3. 線程不一致,則通過 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 {
      // 運行在 Handler 線程
      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ù),會記入 BlockingRunnable 的 mTask,等待后續(xù)被調(diào)用執(zhí)行。

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

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

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

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

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

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

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

2.2 Framework 中的使用

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

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

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

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

三、runWithScissors() 的問題

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

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

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

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

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

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

3.2 可能造成死鎖

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

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

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

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

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

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

  1. Handler 的 Looper 不允許退出,例如 Android 主線程 Looper 就不允許退出;
  2. Looper 退出時,使用安全退出 quitSafely() 方式退出;

四、總結(jié)時刻

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

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

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

到此這篇關(guān)于Android開發(fā)中Google為什么不讓用Handler的runWithScissors()的文章就介紹到這了,更多相關(guān)Android runWithScissors()內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論