天天看點

Android應用程式鍵盤(Keyboard)消息處理機制分析(9)

       Step 22. poll

        這是一個Linux系統的檔案作業系統調用,它用來查詢指定的檔案清單是否有有可讀寫的,如果有,就馬上傳回,否則的話,就阻塞線程,并等待驅動程式喚醒,重新調用poll函數,或逾時傳回。在我們的這個場景中,就是要查詢是否有鍵盤事件發生,如果有的話,就傳回,否則的話,目前線程就睡眠等待鍵盤事件的發生了。

        這樣,InputManager的啟動過程就分析完了,下面我們再分析應用程式注冊鍵盤消息接收通道的過程。

        2. 應用程式注冊鍵盤消息接收通道的過程分析

        InputManager啟動以後,就開始負責監控鍵盤輸入事件了。當InputManager監控到鍵盤輸入事件時,它應該把這個鍵盤事件分發給誰呢?當然是要把這個鍵盤消息分發給目前激活的Activity視窗了,不過,目前激活的Activity視窗還需要主動注冊一個鍵盤消息接收通道到InputManager中去,InputManager才能把這個鍵盤消息分發給它處理。那麼,目前被激活的Activity視窗又是什麼時候去注冊這個鍵盤消息接收通道的呢?在前面一篇文章Android應用程式啟動過程源代碼分析中,我們分析Android應用程式的啟動過程時,在Step 33中分析到ActivityThread類的handleLaunchActivity函數中,我們曾經說過,當函數handleLaunchActivity調用performLaunchActivity函數來加載這個完畢應用程式的預設Activity後,再次回到handleLaunchActivity函數時,會調用handleResumeActivity函數來使這個Activity進入Resumed狀态。在調用handleResumeActivity函數的過程中,ActivityThread會通過android.view.WindowManagerImpl類為該Activity建立一個ViewRoot執行個體,并且會通過調用ViewRoot類的setView成員函數把與該Activity關聯的View設定到這個ViewRoot中去,而Activity正是通過ViewRoot類的setView成員函數來注冊鍵盤消息接收通道的。

        有了這些背影知識後,接下來,我們就可以從ViewRoot.setView函數開始分析應用程式注冊鍵盤消息接收通道的過程了。首先看一下這個注冊過程的序列圖,然後再詳細分析每一個步驟:

<a href="http://blog.51cto.com/attachment/201208/000206282.png" target="_blank"></a>

  Step 1. ViewRoot.setView

        這個函數定義在frameworks/base/core/java/android/view/ViewRoot.java檔案中:

public final class ViewRoot extends Handler implements ViewParent,  

        View.AttachInfo.Callbacks {  

    ......  

    public void setView(View view, WindowManager.LayoutParams attrs,  

            View panelParentView) {  

        ......  

        synchronized (this) {  

            if (mView == null) {  

                ......  

                // Schedule the first layout -before- adding to the window  

                // manager, to make sure we do the relayout before receiving  

                // any other events from the system.  

                requestLayout();  

                mInputChannel = new InputChannel();  

                try {  

                    res = sWindowSession.add(mWindow, mWindowAttributes,  

                        getHostVisibility(), mAttachInfo.mContentInsets,  

                        mInputChannel);  

                } catch (RemoteException e) {  

                    ......  

                } finally {  

                }  

                if (view instanceof RootViewSurfaceTaker) {  

                    mInputQueueCallback =  

                        ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();  

                if (mInputQueueCallback != null) {  

                    mInputQueue = new InputQueue(mInputChannel);  

                    mInputQueueCallback.onInputQueueCreated(mInputQueue);  

                } else {  

                    InputQueue.registerInputChannel(mInputChannel, mInputHandler,  

                        Looper.myQueue());  

            }  

        }  

    }  

}  

        這個函數中與注冊鍵盤消息接收通道(InputChannel)相關的邏輯主要有三處,一是調用requestLayout函數來通知InputManager,這個Activity視窗是目前被激活的視窗,二是調用sWindowSession(WindowManagerService内部類Session的遠端接口)的add成員函數來把鍵盤消息接收通道的一端注冊在InputManager中,三是調用InputQueue的registerInputChannel成員函數來把鍵盤消息接收通道的另一端注冊在本應用程式的消息循環(Looper)中。這樣,當InputManager監控到有鍵盤消息時,就會先找到目前被激活的視窗,然後找到其在InputManager中對應的鍵盤消息接收通道,通過這個通道在InputManager中的一端來通知在應用程式消息循環中的另一端,就把鍵盤消息分發給目前激活的Activity視窗了。

        在接下來的内容中,我們首先描述requestLayout函數是如何告訴InputManager目前的Activity視窗便是激活視窗的,接着再回過頭來分析應用程式是如何把鍵盤消息接收通道的一端注冊到InputManager中去的,最後分析應用程式是如何鍵盤消息接收通道的另一端注冊到本應用程式的消息循環中去了。

本文轉自 Luoshengyang 51CTO部落格,原文連結:http://blog.51cto.com/shyluo/966622,如需轉載請自行聯系原作者