1. InputManagerService啟動流程分析

        共 11806字,需瀏覽 24分鐘

         ·

        2022-11-04 03:45

        b3eff740c292a7e4ce755636bff75525.webp

        和你一起終身學(xué) 習(xí),這里是程序員Android

        經(jīng)典好文推薦,通過閱讀本文,您將收獲以下知識點:

        一、前言
        二、啟動流程
        2.1 創(chuàng)建輸入系統(tǒng)
        2.2 啟動輸入系統(tǒng)
        2.3 輸入系統(tǒng)就緒

        一、前言

        之前寫過幾篇關(guān)于輸入系統(tǒng)的文章,但是還沒有寫完,后來由于工作的變動,這個事情就一直耽擱了。而現(xiàn)在,在工作中,遇到輸入系統(tǒng)相關(guān)的事情也越來越多,其中有一個非常有意思的需求,因此是時候繼續(xù)分析 InputManagerService。

        InputManagerService 系統(tǒng)文章,基于 Android 12 進行分析。

        本文將以 IMS 簡稱 InputManagerService。

        二、啟動流程

        InputManagerService 是一個系統(tǒng)服務(wù),啟動流程如下

            // SystemServer.java

        private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
        // ..

        // 1. 創(chuàng)建
        inputManager = new InputManagerService(context);
        // 注冊服務(wù)
        ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
        /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);

        // 保存 wms 的回調(diào)
        inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());
        // 2. 啟動
        inputManager.start();


        try {
        // 3. 就緒
        if (inputManagerF != null) {
        inputManagerF.systemRunning();
        }
        } catch (Throwable e) {
        reportWtf("Notifying InputManagerService running", e);
        }

        // ...
        }

        IMS 的啟動流程分為三步

        1.創(chuàng)建輸入系統(tǒng),建立上層與底層的映射關(guān)系。
        2.啟動輸入系統(tǒng),其實就是啟動底層輸入系統(tǒng)的幾個模塊。
        3.輸入系統(tǒng)就緒,上層會同步一些配置給底層輸入系統(tǒng)。

        下面分三個模塊,分別講解這三步。

        2.1 創(chuàng)建輸入系統(tǒng)
            // InputManagerService.java

        public InputManagerService(Context context) {
        this.mContext = context;
        this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());

        // 配置為空
        mStaticAssociations = loadStaticInputPortAssociations();

        // 默認 false
        mUseDevInputEventForAudioJack =
        context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);

        // 1. 底層進行初始化
        // mPtr 指向底層創(chuàng)建的 NativeInputManager 對象
        mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());

        // 空
        String doubleTouchGestureEnablePath = context.getResources().getString(
        R.string.config_doubleTouchGestureEnableFile);
        // null
        mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
        new File(doubleTouchGestureEnablePath);

        LocalServices.addService(InputManagerInternal.class, new LocalService());
        }

        IMS 構(gòu)造函數(shù),主要就是調(diào)用 nativeInit() 來初始化底層輸入系統(tǒng)。

            // com_android_server_input_InputManagerService.cpp

        static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
        // 從Java層的MessageQueue中獲取底層映射的MessageQueue
        sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
        if (messageQueue == nullptr) {
        jniThrowRuntimeException(env, "MessageQueue is not initialized.");
        return 0;
        }

        // 創(chuàng)建 NativeInputManager
        NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
        messageQueue->getLooper());
        im->incStrong(0);

        // 返回指向 NativeInputManager 對象的指針
        return reinterpret_cast<jlong>(im);
        }

        原來底層創(chuàng)建了 NativeInputManager 對象,然后返回給上層。

        但是 NativeInputManager 并不是底層輸入系統(tǒng)的服務(wù),它只是一個連接上層輸入系統(tǒng)和底層輸入系統(tǒng)的橋梁而已。來看下它的創(chuàng)建過程

            // com_android_server_input_InputManagerService.cpp

        NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
        JNIEnv* env = jniEnv();

        // 1.保存上層的InputManagerService對象
        mServiceObj = env->NewGlobalRef(serviceObj);

        // 2. 初始化一些參數(shù)
        {
        AutoMutex _l(mLock);
        // mLocked 的類型是 struct Locked,這里初始化了一些參數(shù)
        // 這些參數(shù)會被上層改變
        mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
        mLocked.pointerSpeed = 0;
        mLocked.pointerGesturesEnabled = true;
        mLocked.showTouches = false;
        mLocked.pointerCapture = false;
        mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
        }
        mInteractive = true;

        // 3.創(chuàng)建并注冊服務(wù) InputManager
        mInputManager = new InputManager(this, this);
        defaultServiceManager()->addService(String16("inputflinger"),
        mInputManager, false);
        }

        NativeInputManager 構(gòu)造過程如下

        1.創(chuàng)建一個全局引用,并通過 mServiceObj 指向上層的 InputManagerService 對象。
        2.初始化參數(shù)。這里要注意一個結(jié)構(gòu)體變量 mLocked,它的一些參數(shù)都是由上層控制的。例如,mLocked.showTouches 是由開發(fā)者選項中 "Show taps" 決定的,它的功能是在屏幕上顯示一個觸摸點。
        3.創(chuàng)建并注冊服務(wù) InputManager。

        原來,InputManager 才是底層輸入系統(tǒng)的服務(wù),而 NativeInputManagerService 通過 mServiceObj 保存了上層 InputManagerService 引用,并且上層 InputManagerService 通過 mPtr 指向底層的 NativeInputManager。因此,我們可以判定 NativeInputManager 就是一個連接上層與底層的橋梁。

        我們注意到創(chuàng)建 InputManager 使用了兩個 this 參數(shù),這里介紹下 NativeInputManager 和 InputManager 的結(jié)構(gòu)圖

        67b93e42ddc11fbe83733b3762616535.webp

        InputManager 構(gòu)造函數(shù)需要的兩個接口正好是由 NativeInputManager 實現(xiàn)的,然而,具體使用這兩個接口的不是 InputManager,而是它的子模塊。這些子模塊都是在 InputManager 的構(gòu)造函數(shù)中創(chuàng)建的

            // InputManager.cpp
        InputManager::InputManager(
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
        // 1. 創(chuàng)建InputDispatcher對象,使用 InputDispatcherPolicyInterface 接口
        mDispatcher = createInputDispatcher(dispatcherPolicy);

        // 2. 創(chuàng)建InputClassifier對象,使用 InputListenerInterface
        mClassifier = new InputClassifier(mDispatcher);

        // 3. 創(chuàng)建InputReader對象,使用 InputReaderPolicyInterface 和 InputListenerInterface
        mReader = createInputReader(readerPolicy, mClassifier);
        }

        // InputDispatcherFactory.cpp
        sp<InputDispatcherInterface> createInputDispatcher(
        const sp<InputDispatcherPolicyInterface>& policy) {
        return new android::inputdispatcher::InputDispatcher(policy);
        }

        // InputReaderFactory.cpp
        sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,
        const sp<InputListenerInterface>& listener) {
        return new InputReader(std::make_unique<EventHub>(), policy, listener);
        }

        InputManager 構(gòu)造函數(shù)所使用的兩個接口,分別由 InputDispatcher 和 InputReader 所使用。因此 InputManager 向上通信的能力是由子模塊 InputDispatcher 和 InputReader 實現(xiàn)的。

        InputManager 創(chuàng)建了三個模塊,InputReader、InputClassifier、InputDispatcher。 InputReader 負責(zé)從 EventHub 中獲取事件,然后把事件加工后,發(fā)送給 InputClassfier。InputClassifer 會把事件發(fā)送給 InputDispatcher,但是它會對觸摸事件進行一個分類工作。最后 InputDispatcher 對進行事件分發(fā)。

        那么現(xiàn)在我們可以大致推算下輸入系統(tǒng)的關(guān)系圖,如下

        71766f48ab96b4914a342f1c0ad25654.webp

        這個關(guān)系圖很好的體現(xiàn)了設(shè)計模式的單一職責(zé)原則。

        EventHub 其實只屬于 InputReader,因此要想解剖整個輸入系統(tǒng),我們得逐一解剖 InputReader、InputClassifier、InputDispatcher。后面的一系列的文章將逐個來剖析。

        2.2 啟動輸入系統(tǒng)
            // InputManagerService.java

        public void start() {
        Slog.i(TAG, "Starting input manager");
        // 1.啟動native層
        nativeStart(mPtr);

        // Add ourself to the Watchdog monitors.
        Watchdog.getInstance().addMonitor(this);

        // 2.監(jiān)聽數(shù)據(jù)庫,當(dāng)值發(fā)生改變時,通過 native 層
        // 監(jiān)聽Settings.System.POINTER_SPEED,這個表示手指的速度
        registerPointerSpeedSettingObserver();
        // 監(jiān)聽Settings.System.SHOW_TOUCHES,這個表示是否在屏幕上顯示觸摸坐標(biāo)
        registerShowTouchesSettingObserver();
        // 監(jiān)聽Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON
        registerAccessibilityLargePointerSettingObserver();
        // 監(jiān)聽Settings.Secure.LONG_PRESS_TIMEOUT,這個多少毫秒觸發(fā)長按事件
        registerLongPressTimeoutObserver();
        // 監(jiān)聽用戶切換
        mContext.registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
        updatePointerSpeedFromSettings();
        updateShowTouchesFromSettings();
        updateAccessibilityLargePointerFromSettings();
        updateDeepPressStatusFromSettings("user switched");
        }
        }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);

        // 3. 從數(shù)據(jù)庫獲取值,并傳遞給 native 層
        updatePointerSpeedFromSettings();
        updateShowTouchesFromSettings();
        updateAccessibilityLargePointerFromSettings();
        updateDeepPressStatusFromSettings("just booted");
        }

        輸入系統(tǒng)的啟動過程如下

        1.啟動底層輸入系統(tǒng)。其實就是啟動剛剛說到的 InputReader, InputDispatcher。
        2.監(jiān)聽一些廣播。因為這些廣播與輸入系統(tǒng)的配置有關(guān),當(dāng)接收到這些廣播,會更新配置到底層。
        3.直接讀取配置,更新到底層輸入系統(tǒng)。

        第2步和第3步,本質(zhì)上其實都是更新配置到底層,但是需要我們對 InputReader 的運行過程比較熟悉,因此這個配置更新過程,留到后面分析。

        現(xiàn)在我們直接看下如何啟動底層的輸入系統(tǒng)

            // com_android_server_input_InputManagerService.cpp

        static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
        NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

        // 調(diào)用InputManager::start()
        status_t result = im->getInputManager()->start();
        if (result) {
        jniThrowRuntimeException(env, "Input manager could not be started.");
        }
        }

        通過 JNI 層的 NativeInputManager 這個橋梁來啟動 InputManager。

        前面用一幅圖表明了 NativeInputManager 的橋梁作用,現(xiàn)在感受到了嗎?

            status_t InputManager::start() {
        // 啟動 Dispatcher
        status_t result = mDispatcher->start();
        if (result) {
        ALOGE("Could not start InputDispatcher thread due to error %d.", result);
        return result;
        }

        // 啟動 InputReader
        result = mReader->start();
        if (result) {
        ALOGE("Could not start InputReader due to error %d.", result);

        mDispatcher->stop();
        return result;
        }

        return OK;
        }

        InputManager 的啟動過程很簡單,就是直接啟動它的子模塊 InputDispatcher 和 InputReader。

        InputDispatcher 和 InputReader 的啟動,都是通過 InputThread 創(chuàng)建一個線程來執(zhí)行任務(wù)。

            //InputThread.cpp

        InputThread::InputThread(std::string name, std::function<void()> loop, std::function<void()> wake)
        : mName(name), mThreadWake(wake) {
        mThread = new InputThreadImpl(loop);
        mThread->run(mName.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
        }

        注意 InputThread 可不是一個線程,InputThreadImpl 才是一個線程,如下

            //InputThread.cpp

        class InputThreadImpl : public Thread {
        public:
        explicit InputThreadImpl(std::function<void()> loop)
        : Thread(/* canCallJava */ true), mThreadLoop(loop) {}

        ~InputThreadImpl() {}

        private:
        std::function<void()> mThreadLoop;

        bool threadLoop() override {
        mThreadLoop();
        return true;
        }
        };

        當(dāng)線程啟動后,會循環(huán)調(diào)用 threadLoop(),直到這個函數(shù)返回 false。從 InputThreadImpl 的定義可以看出,threadLoop() 會一直保持循環(huán),并且每一次循環(huán),會調(diào)用一次 mThreadLoop(),而函數(shù) mThreadLoop 是由 InputReader 和 InputDispacher 在啟動時傳入

            // InputReader.cpp
        status_t InputReader::start() {
        if (mThread) {
        return ALREADY_EXISTS;
        }
        // 線程啟動后,循環(huán)調(diào)用 loopOnce()
        mThread = std::make_unique<InputThread>(
        "InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
        return OK;
        }

        // InputDispatcher.cpp
        status_t InputDispatcher::start() {
        if (mThread) {
        return ALREADY_EXISTS;
        }
        // 線程啟動后,循環(huán)調(diào)用 dispatchOnce()
        mThread = std::make_unique<InputThread>(
        "InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });
        return OK;
        }

        現(xiàn)在,我們可以明白,InputReader 啟動時,會創(chuàng)建一個線程,然后循環(huán)調(diào)用 loopOnce() 函數(shù),而 InputDispatcher 啟動時,也會創(chuàng)建一個線程,然后循環(huán)調(diào)用 dispatchOnce()。

        2.3 輸入系統(tǒng)就緒
            // InputManagerService.java

        public void systemRunning() {
        mNotificationManager = (NotificationManager)mContext.getSystemService(
        Context.NOTIFICATION_SERVICE);

        synchronized (mLidSwitchLock) {
        mSystemReady = true;

        // Send the initial lid switch state to any callback registered before the system was
        // ready.
        int switchState = getSwitchState(-1 /* deviceId */, InputDevice.SOURCE_ANY, SW_LID);
        for (int i = 0; i < mLidSwitchCallbacks.size(); i++) {
        LidSwitchCallback callback = mLidSwitchCallbacks.get(i);
        callback.notifyLidSwitchChanged(0 /* whenNanos */, switchState == KEY_STATE_UP);
        }
        }

        // 監(jiān)聽廣播,通知底層加載鍵盤布局
        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
        filter.addDataScheme("package");
        mContext.registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
        updateKeyboardLayouts();
        }
        }, filter, null, mHandler);

        // 監(jiān)聽廣播,通知底層加載設(shè)備別名
        filter = new IntentFilter(BluetoothDevice.ACTION_ALIAS_CHANGED);
        mContext.registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
        reloadDeviceAliases();
        }
        }, filter, null, mHandler);

        // 直接通知一次底層加載鍵盤布局和加載設(shè)備別名
        mHandler.sendEmptyMessage(MSG_RELOAD_DEVICE_ALIASES);
        mHandler.sendEmptyMessage(MSG_UPDATE_KEYBOARD_LAYOUTS);

        if (mWiredAccessoryCallbacks != null) {
        mWiredAccessoryCallbacks.systemReady();
        }
        }

        private void reloadKeyboardLayouts() {
        nativeReloadKeyboardLayouts(mPtr);
        }

        private void reloadDeviceAliases() {
        nativeReloadDeviceAliases(mPtr);
        }

        無論是通知底層加載鍵盤布局,還是加載設(shè)備別名,其實都是讓底層更新配置。與前面一樣,更新配置的過程,留到后面分析。

        作者:大胃粥
        鏈接:https://juejin.cn/post/7161376731096432653

        友情推薦:

        Android 開發(fā)干貨集錦

        至此,本篇已結(jié)束。轉(zhuǎn)載網(wǎng)絡(luò)的文章,小編覺得很優(yōu)秀,歡迎點擊閱讀原文,支持原創(chuàng)作者,如有侵權(quán),懇請聯(lián)系小編刪除,歡迎您的建議與指正。同時期待您的關(guān)注,感謝您的閱讀,謝謝!

        點擊閱讀原文,為大佬點贊!

        瀏覽 80
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 禁漫天堂羞羞小说 | 日韩精品123 | 插吧插吧综合网 | 美女骚逼久久久久久久久久久久 | 男女上下摸激情视频 |