Android Input輸入事件處理流程分享(3)

努比亞技術團隊原創內容,轉載請務必注明出處。

  • 實例介紹(開發者模式中的觸摸小白點)
    • 開發者模式中的觸摸小白點實現原理
      • 如何開啟觸摸小白點
      • 觸摸小白點的開啟
        • 設置中開啟觸摸小白點
        • IMS處理觸摸小白點開關狀態的改變
        • InputReader中配置觸摸小白點開關的變更
      • 觸摸小白點的繪制
        • 觸摸小白點在觸摸事件處理中的入口
        • 觸摸小白點的參數獲取
        • 觸摸小白點之Sprite的準備
        • 觸摸小白點的繪制
          • 觸摸小白點Sprite列表鎖定
          • SurfaceControl的構建
          • SurfaceControl的size修改
          • 繪制Sprite
          • Sprite參數調整
          • 更新SurfaceControl
      • 小結
    • 通過Systrace看觸摸小白點繪制過程中輸入事件的傳遞
      • Systrace抓取
      • Systrace的打開方式
      • Systrace上的InputReader
      • Systrace上的InputDispatcher
      • Systrace上的應用進程
  • 總結

實例介紹(開發者模式中的觸摸小白點)

通過以上理論上的介紹,相信大家對與整個輸入事件的傳輸過程有了一個概念,但是對應實際中的傳遞流程可能還是有些生疏,下面我會通過介紹安卓開發者模式中的觸摸小白點來實例介紹下具體的傳遞過程。

開發者模式中的觸摸小白點實現原理

在設置中打開開發者模式,然后進入開發者模式對應的頁面,找到“顯示點按操作反饋”,然后打開。之后再在屏幕上觸摸,就會看到在觸摸的地方顯示出一個白色的小圓點。本節我會帶領大家一起了解這個小圓點的實現原理。

觸摸小白點繪制流程

如何開啟觸摸小白點

打開設置,然后找到開發者模式,如果手機還未開啟開發者模式,請找到系統信息界面,并多次點擊“版本號”,之后就會開啟開發者模式。進入開發者模式界面,找到“顯示點按操作反饋”,并打開后面的開關。這樣就看起了觸摸小白點。

打開觸摸小白點 小白點效果
打開觸摸小白點
小白點效果

觸摸小白點的開啟

要搞清楚觸摸小白點的原理,我們需要先搞明白開關的觸發,觸發后系統會做什么操作,以及后續界面以及輸入事件該如何響應等。

設置中開啟觸摸小白點

通過開啟小圓點的過程,我們能夠了解到它的入口是在設置中的開發者模式界面,于是我們去設置中找到此界面,然后看對應頁面的實現:

private static List<AbstractPreferenceController> buildPreferenceControllers(
    Context context,
    Activity activity,
    Lifecycle lifecycle,
    DevelopmentSettingsDashboardFragment fragment,
    BluetoothA2dpConfigStore bluetoothA2dpConfigStore) {
        // 省略若干行
        // 這里顯示觸摸小白點對應的controller是ShowTapsPreferenceController
        controllers.add(new ShowTapsPreferenceController(context));
        controllers.add(new PointerLocationPreferenceController(context));
}

在DevelopmentSettingsDashboardFragment的中buildPreferenceControllers方法中,我們發現觸摸小白點對應的controller是ShowTapsPreferenceController,繼續看。

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        // 獲取開關狀態
        final boolean isEnabled = (Boolean) newValue;
        // 根據開關狀態修改System的值
        Settings.System.putInt(mContext.getContentResolver(),
                Settings.System.SHOW_TOUCHES, isEnabled
                ? SETTING_VALUE_ON : SETTING_VALUE_OFF);
        return true;
    }
IMS處理觸摸小白點開關狀態的改變

在ShowTapsPreferenceController的onPreferenceChange方法中,我們能夠看到當界面開關的狀態發生改變時,程序會修改Settings.System中對應的值Settings.System.SHOW_TOUCHES,于是我們繼續去frameworks中查找Settings.System.SHOW_TOUCHES值的監聽者。

    private void registerShowTouchesSettingObserver() {
        // 注冊Settings.System.SHOW_TOUCHES值改變的監聽
        mContext.getContentResolver().registerContentObserver(
                Settings.System.getUriFor(Settings.System.SHOW_TOUCHES), true,
                new ContentObserver(mHandler) {
                    @Override
                    public void onChange(boolean selfChange) {
                        // 當值發生改變時
                        // 調用updateShowTouchesFromSettings進行處理
                        updateShowTouchesFromSettings();
                    }
                }, UserHandle.USER_ALL);
    }
  
    private void updateShowTouchesFromSettings() {
        int setting = getShowTouchesSetting(0);
        // 繼續調用native方法做進一步處理
        nativeSetShowTouches(mPtr, setting != 0);
    }

通過搜索我們發現,在InputManagerService中有對Settings.System.SHOW_TOUCHES這個值進行監聽,并且當其值發生改變時,會繼續調用到native當中進行處理。

static void nativeSetShowTouches(JNIEnv* /* env */,
        jclass /* clazz */, jlong ptr, jboolean enabled) {
    // 獲取native中的InputManager
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
    // 調用其setShowTouches方法設置狀態
    im->setShowTouches(enabled);
}

在native方法中,首先會獲取到native層的InputManager,然后調用其setShowTouches方法將狀態設置后去。

void NativeInputManager::setShowTouches(bool enabled) {
    { // acquire lock
        AutoMutex _l(mLock);
        // 若待改變的值和現在的狀態相同則不處理
        if (mLocked.showTouches == enabled) {
            return;
        }
  
        ALOGI("Setting show touches feature to %s.", enabled ? "enabled" : "disabled");
        // 改變狀態值
        mLocked.showTouches = enabled;
    } // release lock
    // 調用InputReader進行更新配置
    mInputManager->getReader()->requestRefreshConfiguration(
            InputReaderConfiguration::CHANGE_SHOW_TOUCHES);
}
InputReader中配置觸摸小白點開關的變更

setShowTouches方法中,首先會判斷改變狀態和現有狀態是否一致,不一致則改變現有狀態,然后繼續通知InputReader刷新配置。

void InputReader::requestRefreshConfiguration(uint32_t changes) {
    AutoMutex _l(mLock);
    // 只處理開啟狀態
    if (changes) {
        bool needWake = !mConfigurationChangesToRefresh;
        // 修改配置變量
        mConfigurationChangesToRefresh |= changes;
        // 若需要喚醒EventHub,則進行喚醒
        if (needWake) {
            mEventHub->wake();
        }
    }
}

InputReader的requestRefreshConfiguration只會處理開啟狀態的事件,首先改變配置狀態,然后繼續喚醒EventHub進行處理。

void EventHub::wake() {
    ALOGV("wake() called");
  
    ssize_t nWrite;
    do {
        // 向管道寫入內容以喚醒EventHuab繼續工作
        nWrite = write(mWakeWritePipeFd, "W", 1);
    } while (nWrite == -1 && errno == EINTR);
  
    if (nWrite != 1 && errno != EAGAIN) {
        ALOGW("Could not write wake signal: %s", strerror(errno));
    }
}

在EventHub的wake方法中,會向mWakeWritePipeFd對應的管道中寫入內容,然后就可以喚醒EventHub繼續開始工作。

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    // 省略若干行
    bool awoken = false;
    // 處理管道喚醒事件
    if (eventItem.data.fd == mWakeReadPipeFd) {
        if (eventItem.events & EPOLLIN) {
            ALOGV("awoken after wake()");
            // 標識被喚醒
            awoken = true;
            char buffer[16];
            ssize_t nRead;
            do {
                // 讀取管道中的數據
                nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
            } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
    // 省略若干行
    // 跳出循環,進入到InputReader中處理
    if (event != buffer || awoken) {
        break;
    }
    // 省略若干行
}

在上述getEvents中,當EventHub被喚醒后,就會跳出循環,緊接著會返回到getEvents的調用方,也就是InputReader中去。

void InputReader::loopOnce() {
    // 省略若干行
    { // acquire lock
        // 省略若干行
        uint32_t changes = mConfigurationChangesToRefresh;
        // 如果配置狀態發生改變
        if (changes) {
            mConfigurationChangesToRefresh = 0;
            timeoutMillis = 0;
            // 刷新配置
            refreshConfigurationLocked(changes);
        }
    } // release lock
    // 省略若干行
}

EventHub被喚醒后,就會致使InputReader繼續進入工作,使線程繼續循環。在loopOnce中,會調用refreshConfigurationLocked方法來刷新配置。

void InputReader::refreshConfigurationLocked(uint32_t changes) {
    // 省略若干行
    if (changes) {
        // 省略若干行
        // 處理reopen設備事件
        if (changes & InputReaderConfiguration::CHANGE_MUST_REOPEN) {
            mEventHub->requestReopenDevices();
        } else {
            // 遍歷所有的設備,并更新其configure
            for (auto& devicePair : mDevices) {
                std::shared_ptr<InputDevice>& device = devicePair.second;
                device->configure(now, &mConfig, changes);
            }
        }
    }
}

刷新配置方法refreshConfigurationLocked,先是判斷處理reopen的事件,然后會循環遍歷所有的device,并調用其configure方法更新配置。

void InputDevice::configure(nsecs_t when,
    const InputReaderConfiguration* config,
    uint32_t changes) {
    // 省略若干行
    if (!isIgnored()) {
        // 省略若干行
        // 遍歷所有的device以及其mapper,并更新mapper的配置
        for_each_mapper([this, when, config, changes](InputMapper& mapper) {
            mapper.configure(when, config, changes);
            mSources |= mapper.getSources();
        });
    }
    // 省略若干行
}

上述inputDevice的configure方法中,會遍歷每一個可用的device,并且遍歷device的每一個mapper,然后更新其對于的配置。這里的InputMapper是一種和device的映射關系,能夠處理一種類型的事件,例如鍵盤事件的有KeyboardInputMapper。這里是觸摸相關的配置,所以我們繼續看TouchInputMapper。

void TouchInputMapper::configure(nsecs_t when,
    const InputReaderConfiguration* config,
    uint32_t changes) {
    // 省略若干行
        if (!changes ||
        (changes &
         (InputReaderConfiguration::CHANGE_DISPLAY_INFO |
          InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT |
          InputReaderConfiguration::CHANGE_SHOW_TOUCHES |
          InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE))) {
        // Configure device sources, surface dimensions, orientation and
        // scaling factors.
        // 如果是上述這些配置發生改變,則會繼續調用configureSurface
        // 去改變顯示相關的參數,如顯示方向、縮放等
        configureSurface(when, &resetNeeded);
    }
}

上面方法中會繼續調用configureSurface去改變顯示相關的參數,包括顯示的方向、縮放的大小等等,后面的這里就不再進行展開了。下面我們繼續介紹觸摸小白點的繪制過程。

觸摸小白點的繪制

觸摸小白點兒的顯示是在觸摸時發生,所以我們繼續看touch事件的分發。從前面介紹過的InputReader中處理輸入事件,我們能夠看到,事件會經過InputReader,然后到達InputDevice,接著會到InputMapper的process方法。

觸摸小白點在觸摸事件處理中的入口

觸摸小白點是在手指觸摸屏幕時進行繪制的,所以這里我們從看TouchInputMapper的process方法開始,繼續追蹤其繪制入口。

void TouchInputMapper::process(const RawEvent* rawEvent) {
    // 省略若干行
    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
        // 調用sync繼續處理事件
        sync(rawEvent->when);
    }
}
  
void TouchInputMapper::sync(nsecs_t when) {
    // 省略若干行
    // 直接調用processRawTouches繼續處理
    processRawTouches(false /*timeout*/);
}
  
void TouchInputMapper::processRawTouches(bool timeout) {
    // 省略若干行
    // 需要處理的事件數
    const size_t N = mRawStatesPending.size();
    size_t count;
    for (count = 0; count < N; count++) {
        // 獲取下一個需要處理的state
        const RawState& next = mRawStatesPending[count];
        // 省略若干行
        mCurrentRawState.copyFrom(next);
        if (mCurrentRawState.when < mLastRawState.when) {
            mCurrentRawState.when = mLastRawState.when;
        }
        // 生成并分發事件
        cookAndDispatch(mCurrentRawState.when);
    }
    // 省略若干行
}

上述過程中最后調用到processRawTouches方法中,此方法中會遍歷每一個需要處理的state,然后計算出事件的時間,然后繼續調用cookAndDispatch生成并分發touch事件。

void TouchInputMapper::cookAndDispatch(nsecs_t when) {
    // 省略若干行
    // 上面NativieInputManager的setShowTouches方法中
    // 已經設置過mConfig.showTouches為true,所以會觸發這里的邏輯
    if (mDeviceMode == DEVICE_MODE_DIRECT && mConfig.showTouches &&
        mPointerController != nullptr) {
        // 設置繪制為spot圓點
        mPointerController->setPresentation
        (PointerControllerInterface::PRESENTATION_SPOT);
        // 設置fade類型
        mPointerController->fade
        (PointerControllerInterface::TRANSITION_GRADUAL);
        // 設置button的狀態
        mPointerController->setButtonState(mCurrentRawState.buttonState);
        // 設置spot對應的坐標、顯示id等數據進行繪制
        mPointerController->setSpots(
            mCurrentCookedState.cookedPointerData.pointerCoords,
            mCurrentCookedState.cookedPointerData.idToIndex,
            mCurrentCookedState.cookedPointerData.touchingIdBits,
            mViewport.displayId);
    }
    // 省略若干行
}

上面方法調用中會設置繪制類型、fade類型,最后會將觸摸的位置坐標以及顯示的displayId等信息傳遞到PointerController中進行繪制。

觸摸小白點的參數獲取

觸摸小白點的繪制需要屏幕顯示位置坐標、繪制的icon、顯示id等等信息,這里介紹這些參數的獲取。

void PointerController::setSpots(const PointerCoords* spotCoords,
        const uint32_t* spotIdToIndex,
        BitSet32 spotIdBits,
        int32_t displayId) {
    // 省略若干行
    // 處理手指按下或者移動的繪制
    // Add or move spots for fingers that are down.
    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
        uint32_t id = idBits.clearFirstMarkedBit();
        // 取出坐標
        const PointerCoords& c = spotCoords[spotIdToIndex[id]];
        // 取出icon
        const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0
                ? mResources.spotTouch : mResources.spotHover;
        // 取出x坐標
        float x = c.getAxisValue(AMOTION_EVENT_AXIS_X);
        // 取出y坐標
        float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
        // 獲取spot對象
        Spot* spot = getSpot(id, newSpots);
        if (!spot) {
            spot = createAndAddSpotLocked(id, newSpots);
        }
        // 根據displayId在屏幕上更新spot
        spot->updateSprite(&icon, x, y, displayId);
    }
    // 處理手指移除小圓點的繪制
    // Remove spots for fingers that went up.
    for (size_t i = 0; i < newSpots.size(); i++) {
        Spot* spot = newSpots[i];
        if (spot->id != Spot::INVALID_ID
                && !spotIdBits.hasBit(spot->id)) {
            // 漸變移除spot
            fadeOutAndReleaseSpotLocked(spot);
        }
    }
    // 省略若干行
}

上述調用過程中,會遍歷處理每一個手指的按下和釋放。首先獲取到手指觸摸的坐標、InputMapper傳過來的displayId以及spot的icon,最后調用Spot的updateSprite進行繪制;接著后面還會處理可能的手指移除時spot的改變。

觸摸小白點之Sprite的準備

通過分析發現,觸摸小白點是通過sprite來進行控制的,這里我們介紹下sprite的創建以及update過程。

void PointerController::Spot::updateSprite(const SpriteIcon* icon,
    float x, float y, int32_t displayId) {
    // 設置顯示的layer
    sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
    // 設置alpha
    sprite->setAlpha(alpha);
    // 設置transform矩陣
    sprite->setTransformationMatrix(SpriteTransformationMatrix(scale,
    0.0f, 0.0f, scale));
    // 設置x、y坐標
    sprite->setPosition(x, y);
    // 設置顯示id
    sprite->setDisplayId(displayId);
    this->x = x;
    this->y = y;
    // icon不同則更新
    if (icon != lastIcon) {
        lastIcon = icon;
        if (icon) {
            // icon有效則顯示出來
            sprite->setIcon(*icon);
            sprite->setVisible(true);
        } else {
            // icon無效則隱藏
            sprite->setVisible(false);
        }
    }
}

updateSprite方法中設置sprite的各種數據和參數,最后會通過setVisible讓其顯示出來。那么這里的sprite又是什么呢?回到上面PointerController的setSpots中,首先會通過getSpot獲取spot,如果沒有獲取到則會調用createAndAddSpotLocked方法來創建和點擊spot,那么我們繼續看這個方法。

PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id,
        std::vector<Spot*>& spots) {
    // 省略若干行
    // Obtain a sprite from the recycled pool.
    sp<Sprite> sprite;
    // recycled池不為空,則獲取一個
    if (! mLocked.recycledSprites.empty()) {
        sprite = mLocked.recycledSprites.back();
        mLocked.recycledSprites.pop_back();
    } else {
        // 否則通過SpriteController的createSprite方法創建一個
        sprite = mSpriteController->createSprite();
    }
    // 創建出一個Spot并返回
    // Return the new spot.
    Spot* spot = new Spot(id, sprite);
    spots.push_back(spot);
    return spot;
}

createAndAddSpotLocked首先嘗試從回收池中回收sprite,若無法回收,則繼續調用SpriteController的createSprite方法創建一個sprite,那么,繼續看創建過程。

sp<Sprite> SpriteController::createSprite() {
    // 直接構建一個SpriteImpl,傳入的controller是SpriteController
    return new SpriteImpl(this);
}

接下來我們繼續看上面Spot的updateSprite方法中,調用sprite的setVisible,其實就是SpriteImpl的setVisible方法。

觸摸小白點的繪制

準備好sprite之后,就到了繪制的時機了,下面將詳細介紹Sprite是如果繪制到界面上的。

void SpriteController::SpriteImpl::setVisible(bool visible) {
    AutoMutex _l(mController->mLock);
    // 只有visible改變時在刷新
    if (mLocked.state.visible != visible) {
        mLocked.state.visible = visible;
        // 刷新小圓點
        invalidateLocked(DIRTY_VISIBILITY);
    }
}
  
void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
    bool wasDirty = mLocked.state.dirty;
    mLocked.state.dirty |= dirty;
    // 有數據要顯示
    if (!wasDirty) {
        // 調用SpriteController的invalidateSpriteLocked方法刷新
        mController->invalidateSpriteLocked(this);
    }
}

上面setVisible中會調用到mController的invalidateSpriteLocked方法,這里的mController是構造是傳入的SpriteController,繼續看。

void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
    bool wasEmpty = mLocked.invalidatedSprites.isEmpty();
    mLocked.invalidatedSprites.push(sprite);
    // 更新前數組為空,說明有數據到達
    if (wasEmpty) {
        // 存在transaction的sprite
        if (mLocked.transactionNestingCount != 0) {
            mLocked.deferredSpriteUpdate = true;
        } else {
            // 通過looper發送消息觸發更新sprite
            mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
        }
    }
}
  
void SpriteController::handleMessage(const Message& message) {
    switch (message.what) {
    case MSG_UPDATE_SPRITES:
        // 更新sprite
        doUpdateSprites();
        break;
    case MSG_DISPOSE_SURFACES:
        doDisposeSurfaces();
        break;
    }
}

上面方法中首先會判斷是否存在transaction過程中的sprite,然后通過looper發送更新sprite的消息,然后再handleMessage中進行處理。

void SpriteController::doUpdateSprites() {
    Vector<SpriteUpdate> updates;
    // 1、取出需要刷新的sprites
  
    // Create missing surfaces.
    // 2、遍歷每一個需要刷新的sprite,有選擇的創建surfaceControl
  
    // Resize and/or reparent sprites if needed.
    SurfaceComposerClient::Transaction t;
    bool needApplyTransaction = false;
    for (size_t i = 0; i < numSprites; i++) {
        SpriteUpdate& update = updates.editItemAt(i);
        if (update.state.surfaceControl == nullptr) {
            continue;
        }
  
        // 3、修改surfaceControl的size
    }
    // Redraw sprites if needed.
    // 4、繪制sprite
  
    // 5、根據參數調整sprite
    // If any surfaces were changed, write back the new surface properties
    // to the sprites.
    // 6、如果surface改變,則進行更新
}

doUpdateSprites較長,其中主要完成sprite的獲取、surface的創建、surface尺寸的調整、繪制sprite、調整sprite位置等信息以及最后更新可能改變的surface這一系列過程。下面我們按照這個順序介紹各個階段。

觸摸小白點Sprite列表鎖定

列表的鎖定主要是從全局lock對象中獲取待刷新的sprite,并添加到updates列表當中。

void SpriteController::doUpdateSprites() {
    Vector<SpriteUpdate> updates;
    size_t numSprites;
    { // acquire lock
        AutoMutex _l(mLock);
        // 獲取需要刷新的Sprite數量
        numSprites = mLocked.invalidatedSprites.size();
        for (size_t i = 0; i < numSprites; i++) {
            // 遍歷取出每一個需要刷新的Sprite,并將其添加的updates數組中
            const sp<SpriteImpl>& sprite = mLocked.invalidatedSprites.itemAt(i);
  
            updates.push(SpriteUpdate(sprite, sprite->getStateLocked()));
            sprite->resetDirtyLocked();
        }
        mLocked.invalidatedSprites.clear();
    } // release lock
}

Sprite列表的鎖定過程,主要是從全局的lock對象中獲取到數據列表,然后挨個取出放到updates數組中去,這樣的操作主要是為了避免長時間持有mLock鎖,導致前面Sprite準備過程可能出現阻塞進而引起觸摸小白點顯示不及時等問題。

SurfaceControl的構建

此過程主要是遍歷每一個需要繪制的Sprite,創建可能缺失的SurfaceControl。

void SpriteController::doUpdateSprites() {
    // Create missing surfaces.
    bool surfaceChanged = false;
    // 挨個遍歷updates數組處理每一個sprite
    for (size_t i = 0; i < numSprites; i++) {
        // 取出sprite
        SpriteUpdate& update = updates.editItemAt(i);
        // 如果還不存在surfaceControl并且需要繪制
        if (update.state.surfaceControl == NULL && update.state.
        wantSurfaceVisible()) {
            // 從繪制的icon中獲取寬高
            update.state.surfaceWidth = update.state.icon.bitmap
            .getInfo().width;
            update.state.surfaceHeight = update.state.icon.bitmap
            .getInfo().height;
            update.state.surfaceDrawn = false;
            update.state.surfaceVisible = false;
            // 根據寬高構建surfaceControl
            update.state.surfaceControl = obtainSurface(
                    update.state.surfaceWidth, update.state.surfaceHeight);
            if (update.state.surfaceControl != NULL) {
                // 標識需要刷新surfaceControl
                update.surfaceChanged = surfaceChanged = true;
            }
        }
    }
}

準備surfaceControl階段,主要是遍歷每一個Sprite,然后判斷如果還不存在surfaceControl并且需要繪制的話,則構建surfaceControl,最后標識需要刷新surfaceControl。

SurfaceControl的size修改

主要是遍歷每一個需要update的Sprite,然后獲取其對應的icon中bitmap的寬和高,判斷surface的寬高小于icon的,則更新之。

void SpriteController::doUpdateSprites() {
    SurfaceComposerClient::Transaction t;
    bool needApplyTransaction = false;
    for (size_t i = 0; i < numSprites; i++) {
        // 取出Sprite
        SpriteUpdate& update = updates.editItemAt(i);
        if (update.state.surfaceControl == nullptr) {
            continue;
        }
        // 需要繪制
        if (update.state.wantSurfaceVisible()) {
            // 獲取icon寬和高
            int32_t desiredWidth = update.state.icon.bitmap.getInfo().width;
            int32_t desiredHeight = update.state.icon.bitmap.getInfo().height;
            // surface寬和高小于icon的則使用icon的寬高
            if (update.state.surfaceWidth < desiredWidth
                    || update.state.surfaceHeight < desiredHeight) {
                needApplyTransaction = true;
                // 更新寬高
                t.setSize(update.state.surfaceControl,
                        desiredWidth, desiredHeight);
                update.state.surfaceWidth = desiredWidth;
                update.state.surfaceHeight = desiredHeight;
                update.state.surfaceDrawn = false;
                update.surfaceChanged = surfaceChanged = true;
  
                if (update.state.surfaceVisible) {
                    t.hide(update.state.surfaceControl);
                    update.state.surfaceVisible = false;
                }
            }
        }
}

調整surface寬和高階段,首先會過濾掉需要刷新顯示的Sprite,然后從icon中獲取實際的寬和高,然后判斷surface的尺寸如果小于icon的,則使用icon的尺寸進行更新。

繪制Sprite

繪制Sprite采用直接使用Surface進行繪制,期間會構建出Paint以及canvas,并通過drawBitmap方法將bitmap繪制到canvas,最后通過unlockAndPost提交。

void SpriteController::doUpdateSprites() {
    for (size_t i = 0; i < numSprites; i++) {
        // 取出每一個需要刷新的Sprite
        SpriteUpdate& update = updates.editItemAt(i);
        // 省略若干行
        if (update.state.surfaceControl != NULL && !update.state.surfaceDrawn
                && update.state.wantSurfaceVisible()) {
            // 獲取surface
            sp<Surface> surface = update.state.surfaceControl->getSurface();
            ANativeWindow_Buffer outBuffer;
            // lock buffer
            status_t status = surface->lock(&outBuffer, NULL);
            if (status) {
                ALOGE("Error %d locking sprite surface before drawing.", status);
            } else {
                graphics::Paint paint;
                paint.setBlendMode(ABLEND_MODE_SRC);
                // 構建canvas
                graphics::Canvas canvas(outBuffer, (int32_t) surface->getBuffersDataSpace());
                // 繪制icon
                canvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint);
  
                const int iconWidth = update.state.icon.bitmap.getInfo().width;
                const int iconHeight = update.state.icon.bitmap.getInfo().height;
                // 省略若干行
                // unlock并提交繪制
                status = surface->unlockAndPost();
            }
        }
    }
}

繪制Sprite時,會遍歷所有的需要更新的Sprite,然后獲取到Surface并lock綁定buffer,接著構建Paint和canvas并將icon對應的bitmap繪制到canvas上,最后unlock并提交。

Sprite參數調整

這塊主要是修改繪制icon的alpha、顯示位置以及matrix和顯示的layer,最后會根據是否顯示分別調用show和hide方法來更新小白點的現實狀態。

void SpriteController::doUpdateSprites() {
    for (size_t i = 0; i < numSprites; i++) {
        // 取出需要更新的Sprite
        SpriteUpdate& update = updates.editItemAt(i);
        // 省略若干行
        // 更新alpha
        if (wantSurfaceVisibleAndDrawn
                && (becomingVisible || (update.state.dirty & DIRTY_ALPHA))) {
            t.setAlpha(update.state.surfaceControl,
                    update.state.alpha);
        }
        // 更新顯示位置
        if (wantSurfaceVisibleAndDrawn
                && (becomingVisible || (update.state.dirty & (DIRTY_POSITION
                        | DIRTY_HOTSPOT)))) {
            t.setPosition(
                    update.state.surfaceControl,
                    update.state.positionX - update.state.icon.hotSpotX,
                    update.state.positionY - update.state.icon.hotSpotY);
        }
        // 更新matrix
        if (wantSurfaceVisibleAndDrawn
                && (becomingVisible
                        || (update.state.dirty & DIRTY_TRANSFORMATION_MATRIX))) {
            t.setMatrix(
                    update.state.surfaceControl,
                    update.state.transformationMatrix.dsdx,
                    update.state.transformationMatrix.dtdx,
                    update.state.transformationMatrix.dsdy,
                    update.state.transformationMatrix.dtdy);
        }
        // 設置layer
        int32_t surfaceLayer = mOverlayLayer + update.state.layer;
        if (wantSurfaceVisibleAndDrawn
                && (becomingVisible || (update.state.dirty & DIRTY_LAYER))) {
            t.setLayer(update.state.surfaceControl, surfaceLayer);
        }
  
        if (becomingVisible) {
            t.show(update.state.surfaceControl);
            // 顯示icon
            update.state.surfaceVisible = true;
            update.surfaceChanged = surfaceChanged = true;
        } else if (becomingHidden) {
            t.hide(update.state.surfaceControl);
            // 隱藏icon
            update.state.surfaceVisible = false;
            update.surfaceChanged = surfaceChanged = true;
        }
        }
    }
}

首先遍歷每一個Sprite,然后會依次修改alpha、position、matrix以及顯示的layer,最后調用show或者hide來刷新觸摸小白點的顯示狀態。

更新SurfaceControl

觸摸小白點繪制過程中如果surface發生改變,則會做相應的處理,主要就是將改變的信息同步到全局的mLocked中去。

void SpriteController::doUpdateSprites() {
    // If any surfaces were changed, write back the new surface properties to the sprites.
    if (surfaceChanged) { // acquire lock
        AutoMutex _l(mLock);
        // 遍歷每一個刷新的Sprite
        for (size_t i = 0; i < numSprites; i++) {
            const SpriteUpdate& update = updates.itemAt(i);
            // surface發生改變
            if (update.surfaceChanged) {
                // 將surface對應的信息同步到全局中lock中去
                update.sprite->setSurfaceLocked(update.state.surfaceControl,
                        update.state.surfaceWidth, update.state.surfaceHeight,
                        update.state.surfaceDrawn, update.state.surfaceVisible);
            }
        }
    } // release lock
}
  
inline void setSurfaceLocked(const sp<SurfaceControl>& surfaceControl,
        int32_t width, int32_t height, bool drawn, bool visible) {
    mLocked.state.surfaceControl = surfaceControl;
    mLocked.state.surfaceWidth = width;
    mLocked.state.surfaceHeight = height;
    mLocked.state.surfaceDrawn = drawn;
    mLocked.state.surfaceVisible = visible;
}

這里更新的信息其實是surface的一些參數,因為上面繪制過程會改變update的數組,這里主要是將surface變更的信息同步到全局的lock對象中去。

小結

通過追蹤觸摸小白點的開關狀態改變的處理過程,最終我們了解到觸摸小白點的實現原理:其實就是在分發輸入事件時,如果是touch事件,就會去通過構建Sprite,進而創建出Surface,并將對應的icon繪制到觸摸事件發生的位置,從而在屏幕對應位置顯示出小白點的效果。

通過Systrace看觸摸小白點繪制過程中輸入事件的傳遞

下面我們結果Systrace來查看界面點擊時輸入事件的傳遞流程,以下Systrace抓取時機為:在設置觸摸小白點開關界面點擊時抓取。

Systrace抓取

Systrace的抓取方法是通過安卓sdk工具包中的systrace.py的python腳本實現的,具體抓取方式如下:

Systrace抓取

通過以上方法我們就得到了觸摸過程的Systrace文件,接著我們就可以使用Chrome瀏覽器來分析了。

Systrace的打開方式

打開Systrace我們可以使用Chrome瀏覽器,打開Chrome瀏覽器,然后再地址欄輸入chrome://tracing,接著將Systrace文件拖入即可,或者點擊左上的load按鈕選擇抓取的Systrace文件即可。

Systrace上的InputReader

通過前面的介紹,我們已經知道,輸入事件首先會在InputReader中進行處理。所以,我們去Systrace上找到InputReader,并查看其狀態:

Systrace上的InputReader

從Systrace能夠看到在InputReader線程中,調用了notifyMotion方法和interceptMotionBeforeQueueing方法。

Systrace上的InputDispatcher

在InputDispatcher中,首先我們會執行到dispatchMotionLocked,然后會執行findTouchedWindowTargetsLocked查找焦點窗口,接著會通過dispatchEventLocked方法將輸入事件朝焦點窗口分發。

Systrace上的InputDispatcher

繼續看dispatchEventLocked方法的調用過程,調用過程是如下這樣的:

dispatchEventLocked方法調用過程

① prepareDispatchCycleLocked(inputChannel=8621987 com.android.settings/com.android.settings.Settings$DevelopmentSettingsDashboardActivity (server), id=0x74b2f34)

② enqueueDispatchEntriesLocked(inputChannel=8621987 com.android.settings/com.android.settings.Settings$DevelopmentSettingsDashboardActivity (server), id=0x74b2f34)

③ enqueueDispatchEntry(inputChannel=8621987 com.android.settings/com.android.settings.Settings$DevelopmentSettingsDashboardActivity (server), dispatchMode=DISPATCH_AS_HOVER_EXIT)

④ enqueueDispatchEntry(inputChannel=8621987 com.android.settings/com.android.settings.Settings$DevelopmentSettingsDashboardActivity (server), dispatchMode=DISPATCH_AS_OUTSIDE)


enqueueDispatchEntry(inputChannel=8621987 com.android.settings/com.android.settings.Settings$DevelopmentSettingsDashboardActivity (server), dispatchMode=DISPATCH_AS_HOVER_ENTER)

⑥ enqueueDispatchEntry(inputChannel=8621987 com.android.settings/com.android.settings.Settings$DevelopmentSettingsDashboardActivity (server), dispatchMode=DISPATCH_AS_IS)

⑦ enqueueDispatchEntry(inputChannel=8621987 com.android.settings/com.android.settings.Settings$DevelopmentSettingsDashboardActivity (server), dispatchMode=DISPATCH_AS_SLIPPERY_EXIT)

⑧ enqueueDispatchEntry(inputChannel=8621987 com.android.settings/com.android.settings.Settings$DevelopmentSettingsDashboardActivity (server), dispatchMode=DISPATCH_AS_SLIPPERY_ENTER)

⑨ startDispatchCycleLocked(inputChannel=8621987 com.android.settings/com.android.settings.Settings$DevelopmentSettingsDashboardActivity (server))

通過查看上述過程,我們也能夠看到先通過prepareDispatchCycleLocked方法處理,接著會調用分別處理不同的flag對應的event,請添加到分發隊列中,最后調用startDispatchCycleLocked進行事件分發。

Systrace上的應用進程

這里的應用進程是Settings,所以這里在Systrace中找到Settings對應的進程,然后查看事件處理流程。

Systrace上的應用進程

首先我們能夠看到在InputEventReceiver中事件會到達①deliverInputEvent方法,接著依次會通過②EarlyPostImeInputStage、③NativePostImeInputStage以及④ViewPostImeInputStage,在ViewPostImeInputStage方法中會繼續將事件進行分發,最終會到達View樹上。

總結

安卓中的輸入系統,占用了很大一部分,而且牽扯的模塊也有很多,比如對各類外設的控制,以及窗口及視圖層事件的分發處理等,均離不開輸入系統的支持。本篇僅僅是冰山一角地介紹了輸入事件從native層是如何從底層獲取,然后又是如何向上層分發的過程,而且也僅僅是介紹了個大概,至于更具體的流程以及原理,還需大家仔細去研讀安卓源碼。另外,文中若存在某些方面描述有誤,還請大家多多指正,感謝!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,748評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,165評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,595評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,633評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,435評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,943評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,035評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,175評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,713評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,599評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,788評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,303評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,034評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,412評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,664評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,408評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,747評論 2 370

推薦閱讀更多精彩內容