Android系統--輸入系統(十六)APP跟輸入系統建立聯絡_InputChannel和Connection

0. 核心:socketpair機制

1. 回顧Dispatch處理過程:

1.1 放入佇列前稍加處理

  • 分類:Global Key/System Key/User Key
  • 處理緊急事件(比如來電的時候按下音量鍵靜音)

1.2 InputReader執行緒將讀到的輸入事件稍加處理後放入mInboundQueue佇列中,接著喚醒Dispatch執行緒

1.3 Dispatch從mInboundQueue佇列中將輸入事件取出,並稍加處理。

  • 對於Global Key/System Key按鍵處理:放入mCommandQueue佇列中,依次處理,處理之後就release
  • 對於User Key,放入佇列:查詢目標APP,得到Connection,放入其OutBoundQueue佇列中,稍後取出處理

2. 引入--如何找出目標應用程式,輸入系統和應用程式如何建立聯絡?

PC和安卓系統都是執行著多個應用程式,但是隻有螢幕最前面的應用程式才可以接收到輸入事件,誰來告訴輸入事件哪個是執行在螢幕最前面的應用程式呢?

2.1 引入WindowServiceManager視窗管理服務

  • 對於每一個應用程式,WindowServiceManager都有一個結構體WindowSate來表示該應用程式。假設新啟動一個APP,通過binder通訊,呼叫addToDisplay,會導致AddWindow被呼叫,AddWindow建立了WindowSate表示該應用程式,接著建立一個socketpair得到兩個檔案控制代碼fd0和fd1,fd1直接返回給應用程式,fd0將其封裝為InputChannel類,一方面InputChannel會放進該APPWindowState中,另外一方面,他會將InputChannel註冊給InputDispatch。

2.2 Dispatch執行緒

  • InputDispatch執行緒中裡面有KeyedVector包含含有多個connection,這些connection可以通過註冊實現,建立一個connection(含有InputChannel,InputChannel含有檔案控制代碼fd),將建立好的connection放入Vector當中。
// All registered connections mapped by channel file descriptor.
KeyedVector<int, sp<Connection> > mConnectionsByFd;

2.3 引入connection

  • 對於每一個能夠接受輸入事件應用程式,在InputDispatch當中都有一個connection。假設還有一個應用程式APP,在WindowServiceManager中也有一個WindowState,在InputDispatch中也有一個對應的connection,放入Vector容器中,在connection中含有InputChannel和fd,其中fd來自Socketpair的fd0,另外一個fd1通過Binder通訊返回給APP4。這樣子InputDispatch執行緒想把輸入事件傳送給APP時候,先要找出當前在螢幕最前面的應用程式,然後從vector容器中找到他的connection,然後將資料寫入fd當中既可。

2.4 總結

  • Dispatch執行緒從輸入事件中讀到資料後,可以通過Vector當中找出某個connection,把輸入事件放入fd當中,另外一個應用程式可以從另外一個檔案控制代碼中得到輸入事件,將得到的檔案控制代碼封裝為InputChannel,再封裝為WindowInputEventReceiver,最後把fd放入Looper中,使用Epoll機制查詢等待資料,具體可以見下面關係圖。

2.5 補充

  • InputReader執行緒、InputDispatch執行緒和WindowServiceManager都處於System程序當中,故這三個執行緒可以直接通訊不需要Binder介入,而APP如果想跟這三個執行緒通訊則需要通過Binder機制來實現或者通過事先建立好的Socketpair再通過Binder將檔案控制代碼返回給APP,故其檔案控制代碼可能發生變化。

3. APP獲得SocketPair的fd過程分析

  • 發起addToDisplay的操作
ViewRootImpl.java
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel);
  • 導致同文件下的onTransact被呼叫
IWindowSession.java
mRemote.transact(Stub.TRANSACTION_addToDisplay, _data, _reply, 0);
  • 根據code值呼叫本地的addToDisplay
IWindowSession.java
int _result = this.addToDisplay(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5, _arg6);
- 得到兩個檔案控制代碼
Session.java
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets,
InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outInputChannel);
}
WindowManagerService.java
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
- 把fd1寫給InputChanne
WindowManagerService.java
inputChannels[1].transferTo(outInputChannel); //outInputChannel含有fd1,實際上就是arg6
  • 根據_arg6的返回結果,把fd寫給Binder驅動程式
IWindowSession.java
_arg6.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
- 最終呼叫該函式把fd寫入bind驅動中
android_view_InputChannel.cpp
parcel->writeDupFileDescriptor(inputChannel->getFd());
  • 遠端操作之後,從驅動程式讀出fd
outContentInsets.readFromParcel(_reply);
- 從Binder驅動中讀出fd
android_view_InputChannel.cpp
int rawFd = parcel->readFileDescriptor();
int dupFd = dup(rawFd);

4. 具體呼叫過程時序圖

具體原始碼可以根據下面的時序圖進行具體分析。注:引用韋東山老師