framework 學習筆記23. input輸入事件番外6(事件分發(fā) APP獲取事件)

上一節(jié)中講到 dispatchMotionLocked() 向目標窗口分發(fā)事件苛蒲,接下來繼續(xù)學習目標窗口是如何獲取和綁定的迎罗;窗口的綁定是在 Activity 的啟動流程中(具體可以參考 筆記14 Activity 啟動流程)茉继,這里先簡單介紹一下 Activity 的創(chuàng)建佳鳖。

1. Activity 的創(chuàng)建

其中跨進程通信完成后,使用 ActivityThread 的內(nèi)部類 ApplicationThread 中 scheduleLaunchActivity() 方法進行處理了:其實在這個方法中也很簡單坟岔,就是使用主線程的 handler 發(fā)送了一條 H.LAUNCH_ACTIVITY 的消息豫缨,然后 handle r處理消息時調(diào)用了 handleLaunchActivity(r, null) 方法独令,具體有以下兩步:

(1)創(chuàng)建 activity 第 1 步 activity.attach():
handleLaunchActivity() -> performLaunchActivity() -> activity.attach(),這個 activity.attach() 方法好芭,是對 activity進行綁定燃箭,完成這一步activity才成為四大組件之一,未完成時都只能算一個對象栓撞。
(2)創(chuàng)建 activity 第 2 步 handleResumeActivity():
handleLaunchActivity() -> handleResumeActivity()遍膜,執(zhí)行 onResume() 方法并渲染;

1.1 activity.attach():

// activity 的 attach() 方法:
final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, IVoiceInteractor voiceInteractor) {

    attachBaseContext(context);
    mFragments.attachActivity(this, mContainer, null);
    // 創(chuàng)建 Window
    // 這里的 mWindow 時 PhoneWindow瓤湘,后續(xù)版本的 mWindow 初始化如下,更加直觀:
    // mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow = PolicyManager.makeNewWindow(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    // ...
    // 在 window 中創(chuàng)建時 mWindowManager 其實是 WindowManagerImpl恩尾,代碼不復雜弛说,可以跟進去看看
    mWindow.setWindowManager(  // 設置 WindowManager
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    if (mParent != null) {
        mWindow.setContainer(mParent.getWindow());
    }
    mWindowManager = mWindow.getWindowManager();  // 這里就可以獲取 WindowManager 了
    mCurrentConfig = config;
}

在這里記住三個知識點(后續(xù)講setContentView時再詳細分析):
a. Window 類是一個抽象類,它的唯一實現(xiàn)類是 PhoneWindow翰意;
b. PhoneWindow 有一個內(nèi)部類 DecorView木人,DecorView 是 Activity 的根 View信柿;
c. DecorView 繼承自 FramLayout;

關于創(chuàng)建Window對象:
PolicyManager 為策略類醒第,其實現(xiàn)類 Policy 的makeNewWindow內(nèi)部創(chuàng)建了window對象渔嚷;

// mWindow = PolicyManager.makeNewWindow(this):
public final class PolicyManager {
    private static final String POLICY_IMPL_CLASS_NAME =
        "com.android.internal.policy.impl.Policy";

    private static final IPolicy sPolicy;

    static {  // 通過反射創(chuàng)建 sPolicy
        try {
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME); 
            sPolicy = (IPolicy)policyClass.newInstance();
        } catch (ClassNotFoundException ex) {
            // ...
        }
    }

    private PolicyManager() {}

    // 創(chuàng)建PhoneWindow
    public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context);
    }

    public static LayoutInflater makeNewLayoutInflater(Context context) {
        return sPolicy.makeNewLayoutInflater(context);
    }

    public static WindowManagerPolicy makeNewWindowManager() {
        return sPolicy.makeNewWindowManager();
    }

    public static FallbackEventHandler makeNewFallbackEventHandler(Context context) {
        return sPolicy.makeNewFallbackEventHandler(context);
    }
}

// Policy.java 類中的 makeNewWindow() 方法:
public window makeNewWindow(Context context){
        return new PhoneWindow(context);
}

1.2 handleResumeActivity():

    // 第二步:
    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
        
        // 主要作用是調(diào)用 performResumeActivity() 到 activity 的 onResume 狀態(tài),然后獲取
        // DecorView稠曼,創(chuàng)建一個關聯(lián)的 ViewRootImpl 對象,用來配合 WindowManagerService 服
        // 務來管理該 Activity 組件的窗口狀態(tài)形病,最后 addView
        ActivityClientRecord r = performResumeActivity(token, clearHide);

        if (r != null) {
            final Activity a = r.activity;
            // ...

            //activity創(chuàng)建成功,window此時為空霞幅,進入此分支漠吻;
            if (r.window == null && !a.mFinished && willBeVisible) { 
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    // 一層層的看最終調(diào)用的是:WindowManagerGlobal.java -> addView()
                    wm.addView(decor, l);  // 這里就是測量,擺放司恳,繪制途乃;  也是下面要講的內(nèi)容;
                }

            } else if (!willBeVisible) {
                r.hideForNow = true;
            }
            // Get rid of anything left hanging around.
            cleanUpPendingRemoveWindows(r);

            // ...
        } 
    }

2. 從 addView() 到 服務端(WMS) InputChannel (也就是 fd ) 的注冊

整個過程如下所示:服務端的注冊即 WMS 中 InputChannel 注冊到 InputDispatcher扔傅;


Input輸入事件 - App與輸入系統(tǒng)的聯(lián)系.png

2.1 從 addView 開始:

// WindowManagerImpl.addView():
    // 單例獲取 WindowManagerGlobal
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }


// WindowManagerGlobal.addView():
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {

        // ... 省略部分代碼:參數(shù)的校驗
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
        // ...
        ViewRootImpl root;
        View panelParentView = null;

            //...
     
            // 實例化 ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);
            // 添加 view 到全局集合中
            // 如果想 hook 全部的 view 時耍共,可以通過反射獲取 WindowManagerGlobal -> mViews;
            mViews.add(view);  
            mRoots.add(root);
            mParams.add(wparams);
        }
       
        // do this last because it fires off messages to start doing things
        try {  // 將 view 添加到 ViewRootImpl 中去
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
           // ...
        }
    }

ViewRootImpl.setView():

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
        // 這里先將 mView 保存了 DecorView 的實例猎塞,
        // 然后調(diào)用 requestLayout() 方法试读,以完成應用程序用戶界面的初次布局。
        if (mView == null) {
            mView = view;
            // mWindowAttributes 保存了窗口所對應的 LayoutParams
           mWindowAttributes.copyFrom(attrs);
            /**
            * 在添加窗口之前邢享,先通過requestLayout方法在主線程上安排一次“遍歷”鹏往。
            * 所謂“遍歷”是指 ViewRootImpl 中的核心方法 performTraversal()。
            * 這個方法實現(xiàn)對控件樹進行測量骇塘、布局伊履、向 WMS 申請修改窗口屬性以及重繪的所有工作。
            */
           requestLayout();  
           /*初始化 mInputChannel款违。InputChannel 是窗口接受來自 InputDispatcher 的輸入事件的管
             道唐瀑。注意,僅當窗口的屬性 inputFeatures 不含有 INPUT_FEATURE_NO_INPUT_CHANNEL 時
             才會創(chuàng)建 InputChannel插爹,否則 mInputChannel 為空哄辣,從而導致此窗口無法接受任何輸入事件 */
           if ((mWindowAttributes.inputFeatures
                   & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
               mInputChannel = new InputChannel();  // 見 注釋1
           }
           try {
             // ...
             /* 將窗口添加到 WMS 中。完成這個操作之后赠尾,mWindow 已經(jīng)被添加到指定的Display中去
               而且 mInputChannel(如果不為空)已經(jīng)準備好接受事件了力穗。只是由于這個窗口沒有進行
               過 relayout() ,因此它還沒有有效的 Surface 可以進行繪制 */
             res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                       getHostVisibility(), mDisplay.getDisplayId(),
                       mAttachInfo.mContentInsets, mInputChannel);  // 重點代碼
              // 這里的 mWindowSession 是向WMS跨進程請求獲取的 
           } catch (RemoteException e) {  
              // ... 
           } finally { 
              // ... 
           }

        }
    }

(注釋1)InputChannel的構(gòu)造函數(shù):此時 mInputChannel = new InputChannel() 這里還是一個 Java 的對象气嫁;

public final class InputChannel implements Parcelable {
    private static final String TAG = "InputChannel";
    
    @SuppressWarnings("unused")
    private long mPtr; // used by native code
    private static native InputChannel[] nativeOpenInputChannelPair(String name);
    
    private native void nativeDispose(boolean finalized);
    private native void nativeTransferTo(InputChannel other);
    private native void nativeReadFromParcel(Parcel parcel);
    private native void nativeWriteToParcel(Parcel parcel);
    private native void nativeDup(InputChannel target);
    
    private native String nativeGetName();

    // 構(gòu)造函數(shù)中沒有任何操作当窗,此時 mInputChannel = new InputChannel() 只是一個普通的java對象;
    // 那么要想具有 c++ 的屬性寸宵,唯一的方法就是持有 c++ 對象的指針崖面,也就是將 mPtr 賦值元咙;
    public InputChannel() {
    }
    // ...
  }

2.2 mWindowSession.addToDisplay():重點的開始
(1)首先,mWindowSession 是如何獲取到的:ViewRootImpl 的構(gòu)造函數(shù)中進行初始化的巫员;

// mWindowSession 的初始化:ViewRootImpl 的構(gòu)造函數(shù)中進行初始化的庶香;
public ViewRootImpl(Context context, Display display) {
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();  // 通過 WMS 獲取
}


// WindowManagerGlobal 類中的 getWindowSession() 方法
public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                InputMethodManager imm = InputMethodManager.getInstance();
                IWindowManager windowManager = getWindowManagerService();
                // 通過 WMS 獲取 mWindowSession
                sWindowSession = windowManager.openSession(
                        new IWindowSessionCallback.Stub() {
                            @Override
                            public void onAnimatorScaleChanged(float scale) {
                                ValueAnimator.setDurationScale(scale);
                            }
                        },
                        imm.getClient(), imm.getInputContext());
                ValueAnimator.setDurationScale(windowManager.getCurrentAnimatorScale());
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to open window session", e);
            }
        }
        return sWindowSession;
    }
}


// WMS 中 openSession():
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
        IInputContext inputContext) {
    if (client == null) throw new IllegalArgumentException("null client");
    if (inputContext == null) throw new IllegalArgumentException("null inputContext");
    Session session = new Session(this, callback, client, inputContext);  // 參數(shù) this 就是 WMS;
    return session;
}

IWindowSession:一個aidl接口简识,它的真的實現(xiàn)類是Session赶掖,是一個 Binder 對象,也是 ViewRootImpl 和 WMS 進行通信的代理财异。在 ViewRootImpl 的 setView() 中最終也是通過它和 WMS 通信完成了 Window 的添加倘零。這個 Session 是應用唯一的,它的創(chuàng)建是在 WindowManagerGloable 中通過 getWindowSession() 獲取的戳寸。

(2)mWindowSession.addToDisplay():就是調(diào)用了 Session 的 addToDisplay() 方法呈驶;

// Session 的 addToDisplay() 方法:又是跨進程通訊
    @Override
    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);   // 調(diào)用了 WMS 中的 addWindow() 方法
    }

該方法的參數(shù) outInputChannel 是 ViewRootImpl.setView() 中 mInputChannel = new InputChannel(),不過此時 InputChannel 中的 c++ 對象指針還未賦值疫鹊,mService 就是 new Session 時傳入的 WMS袖瞻。

(3)WMS 的addWindow() 方法:App 端 InputChannel 賦值與建立 socketpair 雙向通信;

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, InputChannel outInputChannel) {
        int[] appOp = new int[1];
        int res = mPolicy.checkAddPermission(attrs, appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }

        boolean reportNewConfig = false;
        WindowState attachedWindow = null;
        WindowState win = null;  // window 對象的信息
        long origId;
        final int type = attrs.type;

        synchronized(mWindowMap) {
            // ...
            win = new WindowState(this, session, client, token, 
                    attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
            // ...
            if (outInputChannel != null && (attrs.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                String name = win.makeInputChannelName();
                // 關鍵代碼拆吆,打開一對 InputChannel聋迎,客戶端和服務端
                InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); 
                // WMS 設置 Channel 為 inputChannels[0] 
                win.setInputChannel(inputChannels[0]); 
                // 客戶端設置 Channel 為 inputChannels[1],此時給上面 Java層的 mInputChannel 中 mPtr 賦值
                // 經(jīng)過賦值后枣耀,Java 層 mInputChannel 才具有 c++ 的對象
                inputChannels[1].transferTo(outInputChannel);  

                // 將服務端的 socket 注冊到 InputDispatcher 中 
                // 這里的 win.mInputWindowHandle 是在 win 初始化的時候 new 出來的霉晕;
                mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
            }

            // ...
            // 每次添加都會更新
            mInputMonitor.updateInputWindowsLw(false /*force*/);
            // ...
        }
        // ...
}

在 WMS 的 addWindow() 方法中有很多的關鍵點,這里列舉一些下面內(nèi)容中需要關注的關鍵點:

  • (a)win = new WindowState():創(chuàng)建出 WindowState 來描述 App 端的信息捞奕;
  • (b)InputChannel.openInputChannelPair():使用 socketpair() 打開一對 InputChannel牺堰;
  • (c)win.setInputChannel(inputChannels[0]):設置服務端 WMS 的 InputChannel 為 inputChannels[0];
  • (d)inputChannels[1].transferTo(outInputChannel):客戶端設置 Channel 為 inputChannels[1]颅围,給上面 Java層的 mInputChannel 中 mPtr 賦值伟葫,經(jīng)過賦值后,Java 層 mInputChannel 才具有了 c++ 的對象院促;
  • (e)mInputManager.registerInputChannel():將服務端的 InputChannel 注冊到 InputDispatcher 中 筏养;

2.3 雙向通信 socketpair 的建立:承接上面的關鍵點進行分析;
(1)InputChannel.openInputChannelPair():在 InputChannel.java 中很簡單常拓,只是調(diào)用了一個 native 方法 nativeOpenInputChannelPair(name)渐溶,在 frameworks/base/core/jni/android_view_InputChannel.cpp 中:

// android_view_InputChannel.cpp 中:
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
        jclass clazz, jstring nameObj) {
    const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
    String8 name(nameChars);
    env->ReleaseStringUTFChars(nameObj, nameChars);

    sp<InputChannel> serverChannel;
    sp<InputChannel> clientChannel;
    // 第一步(詳見下方代碼):創(chuàng)建一對 socket 通信;   
    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);

    if (result) {
        String8 message;
        message.appendFormat("Could not open input channel pair.  status=%d", result);
        jniThrowRuntimeException(env, message.string());
        return NULL;
    }

    // 第二步:創(chuàng)建 java 數(shù)組對象弄抬;用于保存這對 InputChannel
    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
    if (env->ExceptionCheck()) {
        return NULL;
    }

    // 第三步(詳見下方代碼):創(chuàng)建 NativeInputChannel掌猛,將 InputChannel 指針保存于 mInputChannel 中;
    // 第四步  (詳見下方代碼):設置 Java InputChannel 的 mPtr 成員變量眉睹;
    jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
            new NativeInputChannel(serverChannel));  
    if (env->ExceptionCheck()) {
        return NULL;
    }

    jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
            new NativeInputChannel(clientChannel));
    if (env->ExceptionCheck()) {
        return NULL;
    }

    env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
    env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
    return channelPair;
}


// 第一步:創(chuàng)建一對 InputChannel荔茬;(InputChannel.cpp中)
status_t InputChannel::openInputChannelPair(const String8& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        status_t result = -errno;
    // ...    
    // 封裝成 InputChannel
    String8 serverChannelName = name;
    serverChannelName.append(" (server)");
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);

    String8 clientChannelName = name;
    clientChannelName.append(" (client)");
    outClientChannel = new InputChannel(clientChannelName, sockets[1]);
    return OK;
}

InputChannel::InputChannel(const String8& name, int fd) :
        mName(name), mFd(fd) {
    // 代碼很簡單,有用的只有一行(將 fd 設置成為非阻塞方式)
    int result = fcntl(mFd, F_SETFL, O_NONBLOCK);
}

// 第二步:創(chuàng)建 java 數(shù)組對象竹海;用于保存這對 InputChannel(通過調(diào)用 jni 的方法慕蔚,省略介紹)

// 第三步:創(chuàng)建 NativeInputChannel,將傳入的 InputChannel 指針保存于 mInputChannel 成員變量中斋配;
NativeInputChannel::NativeInputChannel(const sp<InputChannel>& inputChannel) :
    mInputChannel(inputChannel), mDisposeCallback(NULL) {
}

// 第四步:
static jobject android_view_InputChannel_createInputChannel(JNIEnv* env,
        NativeInputChannel* nativeInputChannel) {
    jobject inputChannelObj = env->NewObject(gInputChannelClassInfo.clazz,
            gInputChannelClassInfo.ctor);  // 調(diào)用 jni 方法 NewObject 創(chuàng)建一個 Java InputChannel 對象
    if (inputChannelObj) {
        // 調(diào)用 android_view_InputChannel_setNativeInputChannel 將 nativeInputChannel 對象指針強
        // 轉(zhuǎn)為 jlong孔飒,然后將其設置到 Java InputChannel 類的 mPtr 成員變量上;
        android_view_InputChannel_setNativeInputChannel(env, inputChannelObj, nativeInputChannel);
    }
    return inputChannelObj;
}

經(jīng)過 InputChannel.cpp 中 openInputChannelPair() 中調(diào)用 socketpair()艰争,并封裝成一對 InputChannel坏瞄,然后轉(zhuǎn)換成 Java 對象返回給 WMS 供其使用(這里注意一下,客戶端的 InputChannel 是 WMS 獲取到之后甩卓,經(jīng)過 Binder 通信發(fā)送給 app 的鸠匀,這一套 socketpair + Binder 的通信方式,實現(xiàn)了任意進程間的雙向通信)逾柿;另外缀棍,socketpair() 在前面的【輸入事件番外篇1 Linux知識點】中有講過。

(2)設置服務端(WMS)和客戶端(App)端的 InputChannel:

// 設置 WMS 的 InputChannel:win.setInputChannel(inputChannels[0])
// WindowState.java 中
void setInputChannel(InputChannel inputChannel) {
    if (mInputChannel != null) {
        throw new IllegalStateException("Window already has an input channel.");
    }
    // 代碼很簡單机错,只需要把 WindowState 中的兩個成員變量的值置為傳入的參數(shù) inputChannels[0]爬范;
    mInputChannel = inputChannel;
    mInputWindowHandle.inputChannel = inputChannel;
}



// 設置 App 的 InputChannel:inputChannels[1].transferTo(outInputChannel)
// InputChannel.java 中
public void transferTo(InputChannel outParameter) {
    if (outParameter == null) {
        throw new IllegalArgumentException("outParameter must not be null");
    }
    // 執(zhí)行了native 方法,調(diào)用了 android_view_InputChannel.cpp 中的方法
    // 將 InputChannel 內(nèi)部狀態(tài)的所有權(quán)轉(zhuǎn)移到另一個實例弱匪,并使該實例無效青瀑。
    nativeTransferTo(outParameter);
}

// android_view_InputChannel.cpp 中
static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj,
        jobject otherObj) {

    /* otherObj 即對應的入?yún)ⅲ{(diào)用 android_view_InputChannel_getNativeInputChannel 方法可將 Java 
        InputChannel 對象的 mPtr 成員變量強轉(zhuǎn)為 NativeInputChannel 對象萧诫,由于傳遞過來的 Java 層的
        InputChannel 對象中 mPtr 還沒有賦值斥难,是一個個“空殼”,所以強轉(zhuǎn)以后一定為空财搁,否則拋出異常 */
    if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                "Other object already has a native input channel.");
        return;
    }

    // 這次調(diào)用是將 obj 轉(zhuǎn)化為 NativeInputChannel 對象蘸炸。
    NativeInputChannel* nativeInputChannel =
            android_view_InputChannel_getNativeInputChannel(env, obj);
    // 將obj 轉(zhuǎn)化的 NativeInputChannel 對象(實際是指針)設置到 otherObj mPtr 成員變量上。*/
    android_view_InputChannel_setNativeInputChannel(env, otherObj, nativeInputChannel);
    // 最后將 obj mPtr 成員變量設置為 0尖奔,即表示 NativeInputChannel 對象為 NULL搭儒。
    android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
}

// 這兩個被調(diào)用的方法,主要調(diào)用 jni 方法 GetLongField 和 SetLongField提茁,實現(xiàn) Native 層操作 java 對象成員變量淹禾。
static NativeInputChannel* android_view_InputChannel_getNativeInputChannel(JNIEnv* env,
        jobject inputChannelObj) {
    jlong longPtr = env->GetLongField(inputChannelObj, gInputChannelClassInfo.mPtr);
    return reinterpret_cast<NativeInputChannel*>(longPtr);
}
static void android_view_InputChannel_setNativeInputChannel(JNIEnv* env, jobject inputChannelObj,
        NativeInputChannel* nativeInputChannel) {
    env->SetLongField(inputChannelObj, gInputChannelClassInfo.mPtr,
             reinterpret_cast<jlong>(nativeInputChannel));
}

經(jīng)過上面的賦值操作,WMS 端和 App 端各自持有了一對經(jīng)過 socketpair 創(chuàng)建封裝的 InputChannel茴扁,那么接下來就是看看如何與事件分發(fā)系統(tǒng)聯(lián)系起來了铃岔;

(3)mInputManager.registerInputChannel():將 WMS 端的 InputChannel 注冊到 InputDispatcher 中;這里的 mInputManager 是 IMS,在 WMS 的構(gòu)造函數(shù)中作為參數(shù)傳入毁习;也就是說調(diào)用的是 IMS 的 registerInputChannel() 方法:

// InputManagerService.java 中:
public void registerInputChannel(InputChannel inputChannel,
        InputWindowHandle inputWindowHandle) {
    if (inputChannel == null) {
        throw new IllegalArgumentException("inputChannel must not be null.");
    }
    // native 方法
    nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle, false);
}
// frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
static void nativeRegisterInputChannel(JNIEnv* env, jclass clazz,
        jlong ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) {

    // 將 ptr 強轉(zhuǎn)為 NativeInputManager 對象 (這里的 ptr 是 IMS 的 c++ 指針智嚷,不是 InputChannel 的)
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    // 將 inputChannelObj 轉(zhuǎn)化為 NativeInputChannel 對象
    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
    if (inputChannel == NULL) {
        throwInputChannelNotInitialized(env);
        return;
    }

    // 將 inputWindowHandleObj 轉(zhuǎn)化為 NativeInputWindowHandle 對象
    sp<InputWindowHandle> inputWindowHandle =
            android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj);

    // 最終調(diào)用 NativeInputManager 類 registerInputChannel() 方法完成實際注冊工作
    status_t status = im->registerInputChannel(
            env, inputChannel, inputWindowHandle, monitor);
    if (status) {
        String8 message;
        message.appendFormat("Failed to register input channel.  status=%d", status);
        jniThrowRuntimeException(env, message.string());
        return;
    }

    if (! monitor) {
        android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
                handleInputChannelDisposed, im);
    }
}
// com_android_server_input_InputManagerService.cpp 中:
// 這里是一個過渡的方法,主要是獲取到之前 IMS 啟動時創(chuàng)建的 InputDispatcher纺且,執(zhí)行里面的方法盏道;
status_t NativeInputManager::registerInputChannel(JNIEnv* env,
        const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
    return mInputManager->getDispatcher()->registerInputChannel(  // 回到了 InputDispatcher 中了
            inputChannel, inputWindowHandle, monitor);
}
// frameworks/native/services/inputflinger/InputDispatcher.cpp 中:
// 在這個方法中,WMS 的 InputChannel 終于注冊到 InputDispatcher 中了载碌,也就是說猜嘱,當有事件分發(fā)時,服
// 務端的 InputChannel 獲取到事件分發(fā)然后通過數(shù)據(jù)通道發(fā)送到 App 端了嫁艇;其主要的工作有以下四步:

status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {

    { // acquire lock
        AutoMutex _l(mLock);
        // 第一步:檢查 InputChannel 是否已經(jīng)注冊過
        if (getConnectionIndexLocked(inputChannel) >= 0) {
            ALOGW("Attempted to register already registered input channel '%s'",
                    inputChannel->getName().string());
            return BAD_VALUE;
        }

        // 第二步:創(chuàng)建 Connection 對象朗伶,Connection 表示客戶端和服務端的一個輸入數(shù)據(jù)通道
        sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);
        int fd = inputChannel->getFd();  // // 獲取 socketpair 的 fd
        // 第三步:以 fd 為 key 將 Connection 添加到容器(mConnectionsByFd)中
        mConnectionsByFd.add(fd, connection);

        if (monitor) {
            mMonitoringChannels.push(inputChannel);
        }
        
        /* 第四步:將 fd 添加到 Looper 監(jiān)聽列表中。一旦對端的 Socket 寫入數(shù)據(jù)步咪,Looper 就會被喚醒论皆,接著
           就會調(diào)用 handleReceiveCallback */
        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
    } // release lock

    //  喚醒 Looper,因為一些連接(Connection)已經(jīng)改變
    mLooper->wake();
    return OK;
}

經(jīng)過這一步歧斟,WMS 的 InputChannel 就和 InputDispatcher 建立了關聯(lián)〈客瑁現(xiàn)在該回到 ViewRootImpl.setView() 方法中具體分析如何關聯(lián)客戶端 InputChannel 了。

3. App 端 InputChannel (也就是 fd ) 的注冊

在上面 WindowManagerGlobal.addView() -> ViewRootImpl.setView() -> Session.addToDisplay -> WMS.addWindow() 最終完成了 WMS 端的 InputChannel 的注冊静袖;接下來分析客戶端 InputDispatcher 的關聯(lián)觉鼻,也是從 ViewRootImpl.setView() 這個方法中開始的:

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            // ... 省略部分代碼

            // 在add to window manager之前執(zhí)行第一次 requestLayout,以確保在 requestLayout 之前已經(jīng)接收
            // 到系統(tǒng)的任何事件队橙;
            requestLayout();
            if ((mWindowAttributes.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                mInputChannel = new InputChannel();
            }
            try {
                // ...
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mInputChannel);
            } catch (RemoteException e) {
                //...
            } finally {
                //...                   
            }

            // ... 省略部分代碼

            if (mInputChannel != null) {
                if (mInputQueueCallback != null) {
                    mInputQueue = new InputQueue();
                    mInputQueueCallback.onInputQueueCreated(mInputQueue);
                }
                // 將 InputChannel 封裝成 WindowInputEventReceiver坠陈,這里是重點
                mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                        Looper.myLooper());  // 注意一下,這里使用的是主線程的 Looper捐康;
            }
            // ... 省略部分代碼    
        }
    }

將 App 端的 InputChannel 封裝成 WindowInputEventReceiver仇矾,這里也沒有執(zhí)行它的任何方法,那就看一下這個 WindowInputEventReceiver 的構(gòu)造函數(shù)解总,看看有沒有執(zhí)行一些方法:

(1)封裝 WindowInputEventReceiver:從類名上來看贮匕,這個類是 App 端窗口的事件接收器;

// WindowInputEventReceiver.java 中:ViewRootImpl 的內(nèi)部類花枫;
final class WindowInputEventReceiver extends InputEventReceiver {
    public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
        super(inputChannel, looper);  // 執(zhí)行了父類中的方法刻盐,繼續(xù)跟進
    }

    @Override
    public void onInputEvent(InputEvent event) {
        enqueueInputEvent(event, this, 0, true);
    }

    @Override
    public void onBatchedInputEventPending() {
        if (mUnbufferedInputDispatch) {
            super.onBatchedInputEventPending();
        } else {
            scheduleConsumeBatchedInput();
        }
    }

    @Override
    public void dispose() {
        unscheduleConsumeBatchedInput();
        super.dispose();
    }
 }


// InputEventReceiver.java 中:
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
    if (inputChannel == null) {
        throw new IllegalArgumentException("inputChannel must not be null");
    }
    if (looper == null) {
        throw new IllegalArgumentException("looper must not be null");
    }

     mInputChannel = inputChannel;
    mMessageQueue = looper.getQueue();
    mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
            inputChannel, mMessageQueue);  // 這里就執(zhí)行到 c++ 代碼中了
    mCloseGuard.open("dispose");
    }

InputEventReceiver 構(gòu)造器中首先檢查入?yún)?inputChannel 和 looper 是否為 null,接著從 looper 中獲取 MessageQueue劳翰,最后調(diào)用 nativeInit() 進一步初始化敦锌;

(2)JNI 層初始化:nativeInit();

// frameworks/base/core/jni/android_view_InputEventReceiver.cpp

// 主要有以下四個點:
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
        jobject inputChannelObj, jobject messageQueueObj) {
    
    //(1)將 Java 層對象 InputChannel 對象轉(zhuǎn)化為 NativeInputChannel 對象
    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
    if (inputChannel == NULL) {
        jniThrowRuntimeException(env, "InputChannel is not initialized.");
        return 0;
    }
    //(2)將 Java 層 MessageQueue 對象轉(zhuǎn)化為 NativeMessageQueue 對象
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
    if (messageQueue == NULL) {
        jniThrowRuntimeException(env, "MessageQueue is not initialized.");
        return 0;
    }
    //(3)創(chuàng)建 NativeInputEventReceiver 對象
    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
            receiverWeak, inputChannel, messageQueue);
    //(4)執(zhí)行 NativeInputEventReceiver 對象 initialize() 方法
    status_t status = receiver->initialize();
    if (status) {
        String8 message;
        message.appendFormat("Failed to initialize input event receiver.  status=%d", status);
        jniThrowRuntimeException(env, message.string());
        return 0;
    }

    receiver->incStrong(gInputEventReceiverClassInfo.clazz); // 保留對象的引用
    return reinterpret_cast<jlong>(receiver.get());
}


// NativeInputEventReceiver 構(gòu)造函數(shù):將傳遞來的入?yún)⒈4娴綄某蓡T變量中佳簸。
// class NativeInputEventReceiver : public LooperCallback 繼承自 LooperCallback乙墙,
// 并實現(xiàn) LooperCallback 的 handleEvent() 方法;
NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env,
        jobject receiverWeak, const sp<InputChannel>& inputChannel,
        const sp<MessageQueue>& messageQueue) :
        mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
        mInputConsumer(inputChannel), mMessageQueue(messageQueue),
        mBatchedInputEventPending(false), mFdEvents(0) {
}


// initialize() 方法:執(zhí)行了 setFdEvents,其中  ALOOPER_EVENT_INPUT = 1 << 0听想,左移0位腥刹,結(jié)果為 1;
status_t NativeInputEventReceiver::initialize() {
    setFdEvents(ALOOPER_EVENT_INPUT);
    return OK;
}


// setFdEvents() 方法:根據(jù)獲取 MessageQueue->getLooper()哗魂,然后調(diào)用 Looper 的 addFd()
void NativeInputEventReceiver::setFdEvents(int events) { // 傳入的參數(shù)為 1<< 0 為 1
    if (mFdEvents != events) {  // mFdEvents 初始化的值為 0肛走;
        mFdEvents = events;
        int fd = mInputConsumer.getChannel()->getFd();
        if (events) {  // 所以執(zhí)行此分支  
            mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
        } else {
            mMessageQueue->getLooper()->removeFd(fd);
        }
    }
}

從 nativeInit() 開始,經(jīng)過一系列對象轉(zhuǎn)換(將 Java 對象轉(zhuǎn)換成 native 層的 c++ 對象)录别,并創(chuàng)建 NativeInputEventReceiver,然后執(zhí)行了 Looper 的 addFd() 方法邻吞;

(3)Looper.cpp 中:addFd()

int Looper::addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data) {
    // // 這里的 callback 是傳入的參數(shù) NativeInputEventReceiver 
    return addFd(fd, ident, events, callback ? new SimpleLooperCallback(callback) : NULL, data);
}


int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
    if (!callback.get()) {  
        if (! mAllowNonCallbacks) {
            ALOGE("Invalid attempt to set NULL callback but not allowed for this looper.");
            return -1;
        }

        if (ident < 0) {
            ALOGE("Invalid attempt to set NULL callback with ident < 0.");
            return -1;
        }
    } else {
        ident = POLL_CALLBACK;
    }

    int epollEvents = 0;
    if (events & EVENT_INPUT) epollEvents |= EPOLLIN;
    if (events & EVENT_OUTPUT) epollEvents |= EPOLLOUT;

    { // acquire lock
        AutoMutex _l(mLock);
        // 封裝成 Request
        Request request;
        request.fd = fd;
        request.ident = ident;
        request.callback = callback;
        request.data = data;

        struct epoll_event eventItem;
        memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
        eventItem.events = epollEvents;
        eventItem.data.fd = fd;

        ssize_t requestIndex = mRequests.indexOfKey(fd); // 查找當前是否保存過
        if (requestIndex < 0) {  // 如果沒有則保存
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
            if (epollResult < 0) {
                ALOGE("Error adding epoll events for fd %d, errno=%d", fd, errno);
                return -1;
            }
            mRequests.add(fd, request);  // 保存 request 
        } else {  // 如果有則替換
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
            if (epollResult < 0) {
                ALOGE("Error modifying epoll events for fd %d, errno=%d", fd, errno);
                return -1;
            }
            mRequests.replaceValueAt(requestIndex, request);
        }
    } // 釋放鎖
    return 1;
}

在 addFd() 方法中组题,先將參數(shù)封裝成 Request,然后向保存 Request 的容器中查詢是否保存過當前 fd抱冷,沒有保存則保存崔列,反之則替換;通過 epoll_ctl() 添加 fd 描述符后旺遮,當有數(shù)據(jù)從服務端寫入赵讯,就會喚醒 Looper,最終回調(diào)到 NativeInputEventReceiver(繼承自LooperCallback) 的 handleEvent() 方法耿眉。

(4)pollInner() 執(zhí)行回調(diào):

int Looper::pollInner(int timeoutMillis) {
    // ...
    // Invoke all response callbacks.
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
            // 執(zhí)行 回調(diào)函數(shù) handleEvent()
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd);
            }
            // Clear the callback reference in the response structure promptly because we
            // will not clear the response vector itself until the next poll.
            response.request.callback.clear();
            result = POLL_CALLBACK;
        }
    }
    return result;
}

還記得上面說到傳入的 Looper 是主線程的 Looper 嗎边翼?在 Java 層主線程中,執(zhí)行了 looper.loop() 方法后鸣剪,會循環(huán)執(zhí)行 pollInner()组底,所以這里的回調(diào)執(zhí)行后就會返回給 Java 層處理了;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末筐骇,一起剝皮案震驚了整個濱河市债鸡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铛纬,老刑警劉巖厌均,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異告唆,居然都是意外死亡棺弊,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門悔详,熙熙樓的掌柜王于貴愁眉苦臉地迎上來镊屎,“玉大人,你說我怎么就攤上這事茄螃》觳担” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長用狱。 經(jīng)常有香客問我运怖,道長,這世上最難降的妖魔是什么夏伊? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任摇展,我火速辦了婚禮,結(jié)果婚禮上溺忧,老公的妹妹穿的比我還像新娘咏连。我一直安慰自己,他們只是感情好鲁森,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布祟滴。 她就那樣靜靜地躺著,像睡著了一般歌溉。 火紅的嫁衣襯著肌膚如雪垄懂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天痛垛,我揣著相機與錄音草慧,去河邊找鬼。 笑死匙头,一個胖子當著我的面吹牛漫谷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乾胶,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼抖剿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了识窿?” 一聲冷哼從身側(cè)響起斩郎,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎喻频,沒想到半個月后缩宜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡甥温,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年锻煌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姻蚓。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡宋梧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出狰挡,到底是詐尸還是另有隱情拱燃,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布旨指,位于F島的核電站译株,受9級特大地震影響铛只,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窖认。 院中可真熱鬧,春花似錦告希、人聲如沸扑浸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽首装。三九已至,卻和暖如春杭跪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驰吓。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工涧尿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人檬贰。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓姑廉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親翁涤。 傳聞我的和親對象是個殘疾皇子桥言,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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