runOnUiThread 竖配、Handler.post何址、View.post分析

本文源碼基于 Android API 26 Platform

一、 示例

首先进胯,看如下代碼用爪,請判斷輸出結(jié)果:

public class MainThreadTestActivity extends AppCompatActivity {

  private static final String TAG = MainThreadTestActivity.class.getSimpleName();

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main_thread_test);

    View view = new View(this);
    view.post(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "[view.post] >>>> 1 ");
      }
    });

    new Handler(Looper.getMainLooper()).post(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "[handler.post] >>>> 2");
      }
    });

    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        Log.i(TAG, "[runOnUiThread] >>>>> 3");
      }
    });

    new Thread(new Runnable() {
      @Override
      public void run() {
        runOnUiThread(new Runnable() {
          @Override
          public void run() {
            Log.i(TAG, "[runOnUiThread from thread] >>>> 4");
          }
        });
      }
    }).start();
  }
}

首先預(yù)測下,輸出結(jié)果會是怎樣的呢龄减?

下面給出運(yùn)行結(jié)果:

...I/MainThreadTestActivity: [runOnUiThread] >>>>> 3
...I/MainThreadTestActivity: [handler.post] >>>> 2
...I/MainThreadTestActivity: [runOnUiThread from thread] >>>> 4

那么项钮,問題來了:

  • 第一步的 View.post 為什么沒有執(zhí)行班眯?
  • 為什么 runOnUiThread 會執(zhí)行的比 Handler.post 快希停?
  • 在正常情況下,runOnUiThread 署隘、 Handler.post 宠能、 View.post 這三者的執(zhí)行順序又會是怎樣的呢?

下面我們分別進(jìn)行解析磁餐。

二违崇、 解析

2.1 View.post

2.1.1 View.post 不執(zhí)行問題

首先,我們來看 View.post 源碼:

//View.java
public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().post(action);
    return true;
}

即:當(dāng)執(zhí)行 View.post 方法時诊霹,如果 AttachInfo 不為空羞延,則通過 AttachInfoHandler 來執(zhí)行 Runnable;否則脾还,將這個 Runnable 拋到 View 的執(zhí)行隊(duì)列 HandlerActionQueue 中伴箩。

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        //...
}

也就是只有當(dāng) View attch 到 Window 后,才會給 AttachInfo 賦值鄙漏。所以嗤谚,在 示例 里的代碼會直接走入 getRunQueue().post(action) 。我們繼續(xù)順著源碼看下去怔蚌。

View 里巩步,通過 HandlerActionQueue 封裝了可執(zhí)行請求隊(duì)列。官方給它的注釋是 Class used to enqueue pending work from Views when no Handler is attached.,也就是view還沒有 Handler 來執(zhí)行后續(xù)任務(wù)(沒有attach 到 Window上)桦踊,將所有請求入隊(duì)椅野。

// HandlerActionQueue.java
public class HandlerActionQueue {

    public void removeCallbacks(Runnable action) {
        synchronized (this) {
            final int count = mCount;
            int j = 0;

            final HandlerAction[] actions = mActions;
            for (int i = 0; i < count; i++) {
                if (actions[i].matches(action)) {
                    // Remove this action by overwriting it within
                    // this loop or nulling it out later.
                    continue;
                }

                if (j != i) {
                    // At least one previous entry was removed, so
                    // this one needs to move to the "new" list.
                    actions[j] = actions[i];
                }

                j++;
            }

            // The "new" list only has j entries.
            mCount = j;

            // Null out any remaining entries.
            for (; j < count; j++) {
                actions[j] = null;
            }
        }
    }

    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }

}

而在 View 中,

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
       ...
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        ...
}

綜上我們就可以分析出:
由于 View view = new View(this); 沒有將view attch到window上,所以執(zhí)行的 View.post 方法將可執(zhí)行請求都緩存到請求隊(duì)列里籍胯。

所以竟闪,示例 中的代碼可改為:

View view = new View(this);
    rootView.addView(view);
    view.post(new Runnable() {

輸出結(jié)果為:

...I/MainThreadTestActivity: [runOnUiThread] >>>>> 3
...I/MainThreadTestActivity: [handler.post] >>>> 2
...I/MainThreadTestActivity: [runOnUiThread from thread] >>>> 4
...I/MainThreadTestActivity: [view.post] >>>> 1 

成功執(zhí)行 View.post 方法,修改正確芒炼。


View.post() 只有在 View attachedToWindow 的時候才會立即執(zhí)行

2.1.2 View.post 源碼解析

通過 2.1.1 節(jié)我們知道瘫怜,View.post() 只有在 View attachedToWindow 的時候才會立即執(zhí)行 。通過執(zhí)行 ViewRootImplViewRootHandler 執(zhí)行本刽。

2.2 runOnUiThread

//Activity.java
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

注意:

  • 如果當(dāng)前線程是 主線程 , 請求會立即執(zhí)行
  • 如果當(dāng)前線程不是 主線程 , 請求會發(fā)到 UI 線程的時間隊(duì)列鲸湃。

即赠涮,在非主線程調(diào)用該方法,存在線程切換的開銷暗挑。

2.3 Handler.post

需要先把請求加入到 Handler 隊(duì)列笋除,然后執(zhí)行。

三炸裆、執(zhí)行順序分析

綜上垃它,當(dāng)在主線程分別調(diào)用 View.postHandler.post 烹看、 runOnUiThread , new Thread() - [runOnUiThread] 四個方法執(zhí)行順序從快到慢為:

runOnUiThread - Handler.post - new Thread() - [runOnUiThread] - View.post

(符合前面驗(yàn)證結(jié)果)

分析:

  • runOnUiThread 因?yàn)楫?dāng)前執(zhí)行在 UI線程国拇,無需線程切換,直接執(zhí)行
  • Handler.post 需要將請求加入 UI線程 Handler , 多了 入隊(duì)出隊(duì) 時間
  • new Thread() - [runOnUiThread] 開啟新線程惯殊,在啟動完成后將請求加入 UI線程 Handler酱吝, 多了 線程切換入隊(duì)出隊(duì) 時間
  • View.post 需要在view attach 到 Window 后土思,通過 ViewRootImplViewRootHandler 執(zhí)行請求务热。線程切換時間遠(yuǎn)小于UI渲染時間,所以執(zhí)行最慢

擴(kuò)展: 當(dāng) 示例 中代碼在 非UI線程 運(yùn)行時己儒,runOnUiThreadHandler.post 開銷幾乎一樣崎岂,所以執(zhí)行結(jié)果也是順序完成。
故當(dāng)在 非UI線程 分別調(diào)用 View.post 闪湾、Handler.post 冲甘、 runOnUiThread , new Thread() - [runOnUiThread] 四個方法執(zhí)行順序從快到慢為:

Handler.post - runOnUiThread - new Thread() - [runOnUiThread] - View.post

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市响谓,隨后出現(xiàn)的幾起案子损合,更是在濱河造成了極大的恐慌,老刑警劉巖娘纷,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嫁审,死亡現(xiàn)場離奇詭異,居然都是意外死亡赖晶,警方通過查閱死者的電腦和手機(jī)律适,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遏插,“玉大人捂贿,你說我怎么就攤上這事「斐埃” “怎么了厂僧?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長了牛。 經(jīng)常有香客問我颜屠,道長辰妙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任甫窟,我火速辦了婚禮密浑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘粗井。我一直安慰自己尔破,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布浇衬。 她就那樣靜靜地躺著懒构,像睡著了一般。 火紅的嫁衣襯著肌膚如雪径玖。 梳的紋絲不亂的頭發(fā)上痴脾,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天颤介,我揣著相機(jī)與錄音梳星,去河邊找鬼。 笑死滚朵,一個胖子當(dāng)著我的面吹牛冤灾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播辕近,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了亿乳?” 一聲冷哼從身側(cè)響起泡徙,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎漏峰,沒想到半個月后糠悼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浅乔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年倔喂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靖苇。...
    茶點(diǎn)故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡席噩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贤壁,到底是詐尸還是另有隱情悼枢,我是刑警寧澤,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布脾拆,位于F島的核電站馒索,受9級特大地震影響给梅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜双揪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一动羽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧渔期,春花似錦运吓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至信峻,卻和暖如春倦青,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盹舞。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工产镐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人踢步。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓癣亚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親获印。 傳聞我的和親對象是個殘疾皇子述雾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評論 2 354

推薦閱讀更多精彩內(nèi)容