React Native 分析(二)事件驅(qū)動(dòng)

為什么 GUI 是單線程事件驅(qū)動(dòng)的?
不是沒人嘗試多線程的GUI 框架,只是最終都由于死鎖導(dǎo)致的穩(wěn)定性問題重新回到單線程的事件列隊(duì)模型。
多線程GUI 框架中更容易發(fā)生死鎖的一部分原因,是輸入事件的處理過程與GUI 組件的面相對(duì)象模型之間會(huì)存在互相沖突的交互,導(dǎo)致鎖定順序死鎖。
另外,一般 MVC 模式中model 和 view 之間的變化消息相互傳遞,也很容易造成鎖定順序死鎖。

一般來講,一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù),執(zhí)行完成后線程就會(huì)退出。如果我們需要一個(gè)機(jī)制,讓線程能隨時(shí)處理事件但并不退出


function loop() {
    initialize();
    do {
        var message = get_next_message();
        process_message(message);
    } while (message != quit);
}

我們需要一個(gè)事件循環(huán),在不同系統(tǒng)中這個(gè)循環(huán)叫做不同名字,有稱呼其為 EventLoop, Looper(Android),RunLoop(iOS),我們下面拿 iOS中的 RunLoop 繼續(xù)講述。

iOS

RunLoop 實(shí)際上就是一個(gè)對(duì)象,這個(gè)對(duì)象管理了其需要處理的事件和消息,并提供了一個(gè)入口函數(shù)來執(zhí)行上面的邏輯。
線程執(zhí)行了這個(gè)函數(shù)后,就會(huì)一直處于這個(gè)函數(shù)內(nèi)部 "接受消息->等待->處理" 的循環(huán)中,直到這個(gè)循環(huán)結(jié)束(比如傳入 quit 的消息),函數(shù)返回。
線程和 RunLoop 之間是一一對(duì)應(yīng)的,其關(guān)系是保存在一個(gè)全局的 Dictionary 里。線程剛創(chuàng)建時(shí)并沒有 RunLoop,如果你不主動(dòng)獲取,那它一直都不會(huì)有。
RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(shí),RunLoop 的自動(dòng)銷毀是發(fā)生在線程結(jié)束時(shí)。

iOS 的顯示系統(tǒng)是由 VSync 信號(hào)驅(qū)動(dòng)的,VSync 信號(hào)由硬件時(shí)鐘生成,每秒鐘發(fā)出 60 次。iOS 圖形服務(wù)接收到 VSync 信號(hào)后,會(huì)通過 IPC 通知到 App 內(nèi)。

App 的 Runloop 在啟動(dòng)后會(huì)注冊(cè)對(duì)應(yīng)的 CFRunLoopSource 通過 mach_port 接收傳過來的時(shí)鐘信號(hào)通知,隨后 Source 的回調(diào)會(huì)驅(qū)動(dòng)整個(gè) App 的動(dòng)畫與顯示。(1秒60次)

在 VSync 信號(hào)到來后,系統(tǒng)圖形服務(wù)會(huì)通過 CADisplayLink 等機(jī)制通知 App,App 主線程開始在 CPU 中計(jì)算顯示內(nèi)容,比如視圖的創(chuàng)建、布局計(jì)算、圖片解碼、文本繪制等。隨后 CPU 會(huì)將計(jì)算好的內(nèi)容提交到 GPU 去,由 GPU 進(jìn)行變換、合成、渲染。隨后 GPU 會(huì)把渲染結(jié)果提交到幀緩沖區(qū)去,等待下一次 VSync 信號(hào)到來時(shí)顯示到屏幕上。由于垂直同步的機(jī)制,如果在一個(gè) VSync 時(shí)間內(nèi),CPU 或者 GPU 沒有完成內(nèi)容提交,則那一幀就會(huì)被丟棄,等待下一次機(jī)會(huì)再顯示,而這時(shí)顯示屏?xí)A糁暗膬?nèi)容不變。這就是界面卡頓的原因。

Core Animation 在 RunLoop 中注冊(cè)了一個(gè) Observer,監(jiān)聽了 BeforeWaiting 和 Exit 事件。這個(gè) Observer 的優(yōu)先級(jí)是 2000000,低于常見的其他 Observer。
當(dāng)一個(gè)觸摸事件到來時(shí),RunLoop 被喚醒,App 中的代碼會(huì)執(zhí)行一些操作,比如創(chuàng)建和調(diào)整視圖層級(jí)、設(shè)置 UIView 的 frame、修改 CALayer 的透明度、為視圖添加一個(gè)動(dòng)畫;這些操作最終都會(huì)被 CALayer 捕獲,并通過 CATransaction 提交到一個(gè)中間狀態(tài)去。當(dāng)上面所有操作結(jié)束后,RunLoop 即將進(jìn)入休眠(或者退出)時(shí),關(guān)注該事件的 Observer 都會(huì)得到通知。這時(shí) CA 注冊(cè)的那個(gè) Observer 就會(huì)在回調(diào)中,把所有的中間狀態(tài)合并提交到 GPU 去顯示;如果此處有動(dòng)畫,CA 會(huì)通過 DisplayLink 等機(jī)制多次觸發(fā)相關(guān)流程。
和安卓一樣,都是先處理 input 相關(guān)事件

系統(tǒng)實(shí)現(xiàn)中使用
事件響應(yīng)
蘋果注冊(cè)了一個(gè) Source1 (基于 mach port 的) 用來接收系統(tǒng)事件,其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()。
當(dāng)一個(gè)硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后,首先由 IOKit.framework 生成一個(gè) IOHIDEvent 事件并由 SpringBoard 接收。這個(gè)過程的詳細(xì)情況可以參考這里。SpringBoard 只接收按鍵(鎖屏/靜音等),觸摸,加速,接近傳感器等幾種 Event,隨后用 mach port 轉(zhuǎn)發(fā)給需要的App進(jìn)程。隨后蘋果注冊(cè)的那個(gè) Source1 就會(huì)觸發(fā)回調(diào),并調(diào)用 _UIApplicationHandleEventQueue() 進(jìn)行應(yīng)用內(nèi)部的分發(fā)。
_UIApplicationHandleEventQueue() 會(huì)把 IOHIDEvent 處理并包裝成 UIEvent 進(jìn)行處理或分發(fā),其中包括識(shí)別 UIGesture/處理屏幕旋轉(zhuǎn)/發(fā)送給 UIWindow 等。通常事件比如 UIButton 點(diǎn)擊、touchesBegin/Move/End/Cancel 事件都是在這個(gè)回調(diào)中完成的。

手勢(shì)識(shí)別
當(dāng)上面的 _UIApplicationHandleEventQueue() 識(shí)別了一個(gè)手勢(shì)時(shí),其首先會(huì)調(diào)用 Cancel 將當(dāng)前的 touchesBegin/Move/End 系列回調(diào)打斷。隨后系統(tǒng)將對(duì)應(yīng)的 UIGestureRecognizer 標(biāo)記為待處理。
蘋果注冊(cè)了一個(gè) Observer 監(jiān)測(cè) BeforeWaiting (Loop即將進(jìn)入休眠) 事件,這個(gè)Observer的回調(diào)函數(shù)是 _UIGestureRecognizerUpdateObserver(),其內(nèi)部會(huì)獲取所有剛被標(biāo)記為待處理的 GestureRecognizer,并執(zhí)行GestureRecognizer的回調(diào)。
當(dāng)有 UIGestureRecognizer 的變化(創(chuàng)建/銷毀/狀態(tài)改變)時(shí),這個(gè)回調(diào)都會(huì)進(jìn)行相應(yīng)處理。

界面更新
當(dāng)在操作 UI 時(shí),比如改變了 Frame、更新了 UIView/CALayer 的層次時(shí),或者手動(dòng)調(diào)用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,這個(gè) UIView/CALayer 就被標(biāo)記為待處理,并被提交到一個(gè)全局的容器去。(取代了安卓中的 RootViewImpl角色)
蘋果注冊(cè)了一個(gè) Observer 監(jiān)聽 BeforeWaiting(即將進(jìn)入休眠) 和 Exit (即將退出Loop) 事件,回調(diào)去執(zhí)行一個(gè)很長(zhǎng)的函數(shù):
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。這個(gè)函數(shù)里會(huì)遍歷所有待處理的 UIView/CAlayer 以執(zhí)行實(shí)際的繪制和調(diào)整,并更新 UI 界面。這里是生產(chǎn)者,CADisplayLink 是消費(fèi)者。
函數(shù)調(diào)用棧:

_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
    QuartzCore:CA::Transaction::observer_callback:
        CA::Transaction::commit();
            CA::Context::commit_transaction();
                CA::Layer::layout_and_display_if_needed();
                    CA::Layer::layout_if_needed();
                        [CALayer layoutSublayers];
                            [UIView layoutSubviews];
                    CA::Layer::display_if_needed();
                        [CALayer display];
                            [UIView drawRect];

定時(shí)器
CADisplayLink 是一個(gè)和屏幕刷新率一致的定時(shí)器(其內(nèi)部實(shí)際是操作了一個(gè) Source)。如果在兩次屏幕刷新之間執(zhí)行了一個(gè)長(zhǎng)任務(wù),那其中就會(huì)有一幀被跳過去(和 NSTimer 相似),造成界面卡頓的感覺。在快速滑動(dòng)TableView時(shí),即使一幀的卡頓也會(huì)讓用戶有所察覺。Facebook 開源的 AsyncDisplayLink 就是為了解決界面卡頓的問題,其內(nèi)部也用到了 RunLoop。

Android

Android 中的 VSync信號(hào)處理,是由Choreographer 來實(shí)現(xiàn)的。
Choreographer是線程單例的,而且必須要和一個(gè)Looper綁定,因?yàn)槠鋬?nèi)部有一個(gè)Handler需要和Looper綁定。
DisplayEventReceiver是一個(gè)abstract class,其JNI的代碼部分會(huì)創(chuàng)建一個(gè)IDisplayEventConnection的VSYNC監(jiān)聽者對(duì)象。這樣,來自EventThread的VSYNC中斷信號(hào)就可以傳遞給Choreographer對(duì)象了。由圖8可知,當(dāng)VSYNC信號(hào)到來時(shí),DisplayEventReceiver的onVsync函數(shù)將被調(diào)用。
另外,DisplayEventReceiver還有一個(gè)scheduleVsync函數(shù)。當(dāng)應(yīng)用需要繪制UI時(shí),將首先申請(qǐng)一次VSYNC中斷,然后再在中斷處理的onVsync函數(shù)去進(jìn)行繪制。
Choreographer定義了一個(gè)FrameCallback interface,每當(dāng)VSYNC到來時(shí),其doFrame函數(shù)將被調(diào)用。這個(gè)接口對(duì)Android Animation的實(shí)現(xiàn)起了很大的幫助作用。以前都是自己控制時(shí)間,現(xiàn)在終于有了固定的時(shí)間中斷。
Choreographer的主要功能是,當(dāng)收到VSYNC信號(hào)時(shí),去調(diào)用使用者通過postCallback設(shè)置的回調(diào)函數(shù)。目前一共定義了三種類型的回調(diào),它們分別是:
CALLBACK_INPUT:優(yōu)先級(jí)最高,和輸入事件處理有關(guān)。
CALLBACK_ANIMATION:優(yōu)先級(jí)其次,和Animation的處理有關(guān)。
CALLBACK_TRAVERSAL:優(yōu)先級(jí)最低,和UI等控件繪制有關(guān)。

優(yōu)先級(jí)高低和處理順序有關(guān)。當(dāng)收到VSYNC中斷時(shí),Choreographer將首先處理INPUT類型的回調(diào),然后是ANIMATION類型,最后才是TRAVERSAL類型。
為什么 Input 優(yōu)先級(jí)最高?因?yàn)?Input 事件最有可能引發(fā)界面變化.

Choreographer是Android 4.1中的新事物,下面將通過一個(gè)實(shí)例來簡(jiǎn)單介紹Choreographer的工作原理。
假如UI中有一個(gè)控件invalidate了,那么它將觸發(fā)ViewRootImpl的invalidate函數(shù),該函數(shù)將最終調(diào)用ViewRootImpl的scheduleTraversals

   void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

scheduleTraversals首先禁止了后續(xù)的消息處理功能,這是由設(shè)置Looper的postSyncBarrier來完成的。一旦設(shè)置了SyncBarrier,所有非Asynchronous的消息便將停止派發(fā)。
然后,為Choreographer設(shè)置了CALLBACK類型為TRAVERSAL的處理對(duì)象,即mTraversalRunnable。
最后調(diào)用scheduleConsumeBatchedInput,這個(gè)函數(shù)將為Choreographer設(shè)置了CALLBACK類型為INPUT的處理對(duì)象。
Choreographer的postCallback函數(shù)將會(huì)申請(qǐng)一次VSYNC中斷(通過調(diào)用DisplayEventReceiver的scheduleVsync實(shí)現(xiàn))。當(dāng)VSYNC信號(hào)到達(dá)時(shí),Choreographer doFrame函數(shù)被調(diào)用,內(nèi)部代碼會(huì)觸發(fā)回調(diào)處理。

安卓的代碼是開源的,如果感興趣,可以點(diǎn)擊這里scheduleTraversals去參看對(duì)應(yīng)的邏輯

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,520評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,541評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,896評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,062評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,608評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,356評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,555評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,769評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評(píng)論 1 295
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,289評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,516評(píng)論 2 379

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

  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling閱讀 1,457評(píng)論 0 13
  • 一、RunLoop 的概念 字面意思是“消息循環(huán)、運(yùn)行循環(huán)”,RunLoop 實(shí)際上就是一個(gè)事件循環(huán)對(duì)象,這個(gè)對(duì)象...
    風(fēng)輕魚蛋閱讀 493評(píng)論 0 1
  • RunLoop 是 iOS 和 OS X 開發(fā)中非常基礎(chǔ)的一個(gè)概念,這篇文章將從 CFRunLoop 的源碼入手,...
    呵呵噠1991閱讀 209評(píng)論 0 0
  • 你總得需要一個(gè)這樣的朋友,雖然你們?nèi)^不同,天差地別,但是她能在你需要幫助時(shí)給你溫暖,讓你感動(dòng)。star 就是這樣...
    冷兔仙子閱讀 216評(píng)論 0 0
  • 東漢末年,分三國(guó),烽火連天不休。 如果,亂世是男子造就英雄夢(mèng)的機(jī)遇,那么,亂世于女子而言,就是無法逃離的災(zāi)難。 出...
    檸可_閱讀 1,476評(píng)論 6 34