InputManagerService 啓動流程分析

和你一起終身學習,這裏是程序員 Android

經典好文推薦,通過閱讀本文,您將收穫以下知識點:

一、前言
二、啓動流程
2.1 創建輸入系統
2.2 啓動輸入系統
2.3 輸入系統就緒

一、前言

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

InputManagerService 系統文章,基於 Android 12 進行分析。

本文將以 IMS 簡稱 InputManagerService。

二、啓動流程

InputManagerService 是一個系統服務,啓動流程如下

// SystemServer.java

private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
            // ..
            
    // 1. 創建
    inputManager = new InputManagerService(context);
    // 註冊服務    
    ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
                    /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);
            
    // 保存 wms 的回調
    inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());
    // 2. 啓動
    inputManager.start();    


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

    // ...
}

IMS 的啓動流程分爲三步

1.創建輸入系統,建立上層與底層的映射關係。
2.啓動輸入系統,其實就是啓動底層輸入系統的幾個模塊。
3.輸入系統就緒,上層會同步一些配置給底層輸入系統。

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

2.1 創建輸入系統
// 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 指向底層創建的 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 構造函數,主要就是調用 nativeInit() 來初始化底層輸入系統。

// 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;
    }

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

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

原來底層創建了 NativeInputManager 對象,然後返回給上層。

但是 NativeInputManager 並不是底層輸入系統的服務,它只是一個連接上層輸入系統和底層輸入系統的橋樑而已。來看下它的創建過程

// 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. 初始化一些參數
    {
        AutoMutex _l(mLock);
        // mLocked 的類型是 struct Locked,這裏初始化了一些參數
        // 這些參數會被上層改變
        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.創建並註冊服務 InputManager
    mInputManager = new InputManager(this, this);
    defaultServiceManager()->addService(String16("inputflinger"),
            mInputManager, false);
}

NativeInputManager 構造過程如下

1.創建一個全局引用,並通過 mServiceObj 指向上層的 InputManagerService 對象。
2.初始化參數。這裏要注意一個結構體變量 mLocked,它的一些參數都是由上層控制的。例如,mLocked.showTouches 是由開發者選項中 "Show taps" 決定的,它的功能是在屏幕上顯示一個觸摸點。
3.創建並註冊服務 InputManager。

原來,InputManager 纔是底層輸入系統的服務,而 NativeInputManagerService 通過 mServiceObj 保存了上層 InputManagerService 引用,並且上層 InputManagerService 通過 mPtr 指向底層的 NativeInputManager。因此,我們可以判定 NativeInputManager 就是一個連接上層與底層的橋樑。

我們注意到創建 InputManager 使用了兩個 this 參數,這裏介紹下 NativeInputManager 和 InputManager 的結構圖

InputManager 構造函數需要的兩個接口正好是由 NativeInputManager 實現的,然而,具體使用這兩個接口的不是 InputManager,而是它的子模塊。這些子模塊都是在 InputManager 的構造函數中創建的

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

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

    // 3. 創建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 構造函數所使用的兩個接口,分別由 InputDispatcher 和 InputReader 所使用。因此 InputManager 向上通信的能力是由子模塊 InputDispatcher 和 InputReader 實現的。

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

那麼現在我們可以大致推算下輸入系統的關係圖,如下

這個關係圖很好的體現了設計模式的單一職責原則。

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

2.2 啓動輸入系統
// 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.監聽數據庫,當值發生改變時,通過 native 層
        // 監聽Settings.System.POINTER_SPEED,這個表示手指的速度
        registerPointerSpeedSettingObserver();
        // 監聽Settings.System.SHOW_TOUCHES,這個表示是否在屏幕上顯示觸摸座標
        registerShowTouchesSettingObserver();
        // 監聽Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON
        registerAccessibilityLargePointerSettingObserver();
        // 監聽Settings.Secure.LONG_PRESS_TIMEOUT,這個多少毫秒觸發長按事件
        registerLongPressTimeoutObserver();
        // 監聽用戶切換
        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. 從數據庫獲取值,並傳遞給 native 層
        updatePointerSpeedFromSettings();
        updateShowTouchesFromSettings();
        updateAccessibilityLargePointerFromSettings();
        updateDeepPressStatusFromSettings("just booted");
    }

輸入系統的啓動過程如下

1.啓動底層輸入系統。其實就是啓動剛剛說到的 InputReader, InputDispatcher。
2.監聽一些廣播。因爲這些廣播與輸入系統的配置有關,當接收到這些廣播,會更新配置到底層。
3.直接讀取配置,更新到底層輸入系統。

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

現在我們直接看下如何啓動底層的輸入系統

// com_android_server_input_InputManagerService.cpp

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

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

通過 JNI 層的 NativeInputManager 這個橋樑來啓動 InputManager。

前面用一幅圖表明瞭 NativeInputManager 的橋樑作用,現在感受到了嗎?

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 創建一個線程來執行任務。

//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;
    }
};

當線程啓動後,會循環調用 threadLoop(),直到這個函數返回 false。從 InputThreadImpl 的定義可以看出,threadLoop() 會一直保持循環,並且每一次循環,會調用一次 mThreadLoop(),而函數 mThreadLoop 是由 InputReader 和 InputDispacher 在啓動時傳入

// InputReader.cpp
status_t InputReader::start() {
    if (mThread) {
        return ALREADY_EXISTS;
    }
    // 線程啓動後,循環調用 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;
    }
    // 線程啓動後,循環調用 dispatchOnce()
    mThread = std::make_unique<InputThread>(
            "InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });
    return OK;
}

現在,我們可以明白,InputReader 啓動時,會創建一個線程,然後循環調用 loopOnce() 函數,而 InputDispatcher 啓動時,也會創建一個線程,然後循環調用 dispatchOnce()。

2.3 輸入系統就緒
// 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);
        }
    }

    // 監聽廣播,通知底層加載鍵盤佈局
    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);

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

    // 直接通知一次底層加載鍵盤佈局和加載設備別名
    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);
}

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

結束
通過本文,我們能大致掌握輸入系統的輪廓。後面,我們將逐步分析子模塊 InputReader 和 InputDispatcher 的功能

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

至此,本篇已結束。轉載網絡的文章,小編覺得很優秀,歡迎點擊閱讀原文,支持原創作者,如有侵權,懇請聯繫小編刪除,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章