從"Show?tabs"了解Android?Input系統(tǒng)
Input源碼解讀——從"Show tabs"開始
本文基于Android T版本源碼,梳理當用戶在開發(fā)者選項中開啟Show tabs功能后顯示第點按操作的視覺反饋的原理,來進一步了解Android Input系統(tǒng)
Settings 寫入設置
首先是設置應用(Settings
)提供的開發(fā)者選項畫面響應點擊,將Show taps
選項對應的設置Key SHOW_TOUCHES
的 ON 值通過android.provder.Settings
接口寫入到保存系統(tǒng)設置數(shù)據(jù)的SettingsProvier
中。
// packages/apps/Settings/src/com/android/settings/development/ShowTapsPreferenceController.java public class ShowTapsPreferenceController extends DeveloperOptionsPreferenceController ... { ... @Override public boolean onPreferenceChange(Preference preference, Object newValue) { final boolean isEnabled = (Boolean) newValue; Settings.System.putInt(mContext.getContentResolver(), Settings.System.SHOW_TOUCHES, isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF); return true; } ... }
InputManagerService監(jiān)聽設置
負責管理輸入的系統(tǒng)服務InputManagerService
在啟動之際,會監(jiān)聽設置中的 SHOW_TOUCHES
字段的變化,在設置產(chǎn)生變化的時候調(diào)用native側的代碼進行處理。
// frameworks/base/services/core/java/com/android/server/input/InputManagerService.java public class InputManagerService extends IInputManager.Stub... { ... public void start() { ... registerShowTouchesSettingObserver(); ... } private void registerShowTouchesSettingObserver() { mContext.getContentResolver().registerContentObserver( Settings.System.getUriFor(Settings.System.SHOW_TOUCHES), true, new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { updateShowTouchesFromSettings(); } }, UserHandle.USER_ALL); } private void updateShowTouchesFromSettings() { int setting = getShowTouchesSetting(0); mNative.setShowTouches(setting != 0); } ... // frameworks/base/services/core/java/com/android/server/input/NativeInputManagerService.java public interface NativeInputManagerService { ... void setShowTouches(boolean enabled); ... }
// frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp class NativeInputManager : public virtual RefBase, ...{ ... void setShowTouches(bool enabled); ... } 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 mInputManager->getReader().requestRefreshConfiguration( InputReaderConfiguration::CHANGE_SHOW_TOUCHES); }
這里的mInputManager
是InputManagerInterface
對象實例,InputManager
是InputManagerInterface
和子類,所以通過mInputManager
可以連接NativeInputManager
和InputReader
。
這里向負責讀取事件的InputReader
發(fā)出更新配置的請求,配置變更的Type
為 CHANGE_SHOW_TOUCHES
。
通過 InputReader 請求刷新配置
InputReader
接收到配置變化的Type
之后,會根據(jù)記錄待刷新配置的變量 mConfigurationChangesToRefresh
判斷當前是否已經(jīng)在刷新過程中。
如果尚未處于刷新中,則更新mConfigurationChangesToRefresh
的值,并喚醒EventHub
進行配置刷新。
// frameworks/native/services/inputflinger/reader/InputReader.cpp void InputReader::requestRefreshConfiguration(uint32_t changes) { std::scoped_lock _l(mLock); if (changes) { bool needWake = !mConfigurationChangesToRefresh; mConfigurationChangesToRefresh |= changes; if (needWake) { mEventHub->wake(); } } }
EventHub 喚醒 InputReader 線程
InputManagerService
過來的刷新請求最終需要InputReader
線程來處理。
可是 InputReader 線程處在從EventHub
中讀取事件和沒有事件時便調(diào)用epoll_wait
進入等待狀態(tài)的循環(huán)當中。
所以為了讓其立即處理配置變化,需要EventHub
的手動喚醒。
// frameworks/native/services/inputflinger/reader/EventHub.cpp void EventHub::wake() { ALOGV("wake() called"); ssize_t nWrite; do { nWrite = write(mWakeWritePipeFd, "W", 1); } while (nWrite == -1 && errno == EINTR); if (nWrite != 1 && errno != EAGAIN) { ALOGW("Could not write wake signal: %s", strerror(errno)); } } size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) { ... for (;;) { ... int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis); ... } ... }
InputReader線程刷新配置
EventHub
喚醒后處于等待狀態(tài)的getEvents
會結束,之后InputReader
線程會進入下次循環(huán)即loopOnce
。
其首先將檢查是否存在待刷新的配置變化changes,存在的話調(diào)用refreshConfigurationLocked
讓InputDevice
去重新適配變化。
// frameworks/native/services/inputflinger/reader/InputReader.cpp void InputReader::loopOnce() { ... std::vector<InputDeviceInfo> inputDevices; { // acquire lock ... uint32_t changes = mConfigurationChangesToRefresh; if (changes) { mConfigurationChangesToRefresh = 0; timeoutMillis = 0; refreshConfigurationLocked(changes); } else if (mNextTimeout != LLONG_MAX) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout); } } // release lock size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); ... }
需要留意,refreshConfigurationLocked
在調(diào)用InputDevice
進一步處理之前需要先獲取配置的變化放入mConfig
中。
// frameworks/native/services/inputflinger/reader/InputReader.cpp void InputReader::refreshConfigurationLocked(uint32_t changes) { mPolicy->getReaderConfiguration(&mConfig); ... if (changes & InputReaderConfiguration::CHANGE_MUST_REOPEN) { mEventHub->requestReopenDevices(); } else { for (auto& devicePair : mDevices) { std::shared_ptr<InputDevice>& device = devicePair.second; device->configure(now, &mConfig, changes); } } ... }
InputDevice配置變化
InputDevice
的configure
需要處理很多配置變化,比如鍵盤布局、麥克風等。對于Show taps
的變化關注調(diào)用 InputMapper
的congfigure
即可。
// frameworks/native/services/inputflinger/reader/InputDevice.cpp void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) { ... if (!isIgnored()) { ... for_each_mapper([this, when, config, changes](InputMapper& mapper) { mapper.configure(when, config, changes); mSources |= mapper.getSources(); }); ... } }
TouchInputMapper 進一步處理
眾多輸入事件的物理數(shù)據(jù)需要對應的InputMapper
來轉化為上層能識別的事件類型。比如識別鍵盤輸入的 KeyboardInputMapper
、識別震動的VibratorInputMapper
等等。
現(xiàn)在的觸摸屏都支持多點觸控,所以是MultiTouchInputMapper
來處理的???code>MultiTouchInputMapper沒有復寫 configure(),而是沿用由父類TouchInputMapper
的共通處理。
// frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp void TouchInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) { ... bool resetNeeded = false; if (!changes || (changes & (InputReaderConfiguration::CHANGE_DISPLAY_INFO | InputReaderConfiguration::CHANGE_POINTER_CAPTURE | InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT | InputReaderConfiguration::CHANGE_SHOW_TOUCHES | InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE))) { // Configure device sources, display dimensions, orientation and // scaling factors. configureInputDevice(when, &resetNeeded); } ... }
TouchInputMapper
會依據(jù)changes
的類型進行對應處理,對于SHOW_TOUCHES
的變化需要調(diào)用configureInputDevice
進一步處理。
創(chuàng)建和初始化 PointerController
configureInputDevice
進行多個參數(shù)的測量和配置,其中和Show taps
相關的是PointerController
的創(chuàng)建,該類是 Mouse、Taps、Pointer location 等系統(tǒng) Touch 顯示的專用類。
// frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) { ... // Create pointer controller if needed, and keep it around if Pointer Capture is enabled to // preserve the cursor position. if (mDeviceMode == DeviceMode::POINTER || (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) || (mParameters.deviceType == Parameters::DeviceType::POINTER && mConfig.pointerCaptureRequest.enable)) { if (mPointerController == nullptr) { mPointerController = getContext()->getPointerController(getDeviceId()); } if (mConfig.pointerCaptureRequest.enable) { mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); } } else { mPointerController.reset(); } ... }
這里調(diào)用InputReaderContext#getPointerController
,InputReader::ContextImpl
是InputReaderContext
的子類,所以會回調(diào)到InputReader
開啟PointerController
的創(chuàng)建和初始化。
// frameworks/native/services/inputflinger/reader/InputReader.cpp std::shared_ptr<PointerControllerInterface> InputReader::ContextImpl::getPointerController( int32_t deviceId) { // lock is already held by the input loop return mReader->getPointerControllerLocked(deviceId); } std::shared_ptr<PointerControllerInterface> InputReader::getPointerControllerLocked( int32_t deviceId) { std::shared_ptr<PointerControllerInterface> controller = mPointerController.lock(); if (controller == nullptr) { controller = mPolicy->obtainPointerController(deviceId); mPointerController = controller; updatePointerDisplayLocked(); } return controller; }
這里調(diào)用InputReaderPolicyInterface#obtainPointerController
,而NativeInputManager
是InputReaderPolicyInterface
的子類。
// frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerController( int32_t /* deviceId */) { ... std::shared_ptr<PointerController> controller = mLocked.pointerController.lock(); if (controller == nullptr) { ensureSpriteControllerLocked(); controller = PointerController::create(this, mLooper, mLocked.spriteController); mLocked.pointerController = controller; updateInactivityTimeoutLocked(); } return controller; }
PointerController 構建的同時需要構建持有的 MouseCursorController。
// frameworks/base/libs/input/PointerController.cpp std::shared_ptr<PointerController> PointerController::create( ... ) { std::shared_ptr<PointerController> controller = std::shared_ptr<PointerController>( new PointerController(policy, looper, spriteController)); ... return controller; } PointerController::PointerController( ... ) : mContext(policy, looper, spriteController, *this), mCursorController(mContext) { std::scoped_lock lock(mLock); mLocked.presentation = Presentation::SPOT; ... }
obtainPointerController
執(zhí)行完之后調(diào)用updatePointerDisplayLocked
執(zhí)行PointerController
的初始化。
初始化 PointerController
調(diào)用PointerController
的setDisplayViewport
傳入顯示用的DisplayViewPort
。
// frameworks/native/services/inputflinger/reader/InputReader.cpp void InputReader::updatePointerDisplayLocked() { ... std::optional<DisplayViewport> viewport = mConfig.getDisplayViewportById(mConfig.defaultPointerDisplayId); if (!viewport) { ... viewport = mConfig.getDisplayViewportById(ADISPLAY_ID_DEFAULT); } ... controller->setDisplayViewport(*viewport); }
setDisplayViewport
需要持有的MouseCursorController
進一步初始化。
// frameworks/base/libs/input/PointerController.cpp void PointerController::setDisplayViewport(const DisplayViewport& viewport) { ... mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources); }
MouseCursorController
需要獲取Display相關的參數(shù),并執(zhí)行兩個重要步驟:loadResourcesLocked
/updatePointerLocked
// frameworks/base/libs/input/MouseCursorController.cpp void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources) { ... // Reset cursor position to center if size or display changed. if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth || oldDisplayHeight != newDisplayHeight) { float minX, minY, maxX, maxY; if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) { mLocked.pointerX = (minX + maxX) * 0.5f; mLocked.pointerY = (minY + maxY) * 0.5f; // Reload icon resources for density may be changed. loadResourcesLocked(getAdditionalMouseResources); ... } } else if (oldViewport.orientation != viewport.orientation) { ... } updatePointerLocked(); }
加載 Pointer 相關資源
// frameworks/base/libs/input/MouseCursorController.cpp void MouseCursorController::loadResourcesLocked(bool getAdditionalMouseResources) REQUIRES(mLock) { ... policy->loadPointerResources(&mResources, mLocked.viewport.displayId); policy->loadPointerIcon(&mLocked.pointerIcon, mLocked.viewport.displayId); ... }
省略諸多細節(jié),loadPointerResources
將通過InputManagerService
的JNI
端以及PointerIcon
的JNI
端創(chuàng)建PointerIcon
實例,并讀取顯示的資源。
getSystemIcon
則是負責的函數(shù),其將讀取系統(tǒng)資源里名為Pointer
的Style
,并讀取指針對應的資源 ID。
// frameworks/base/core/java/android/view/PointerIcon.java public static PointerIcon getSystemIcon(@NonNull Context context, int type) { ... int typeIndex = getSystemIconTypeIndex(type); if (typeIndex == 0) { typeIndex = getSystemIconTypeIndex(TYPE_DEFAULT); } int defStyle = sUseLargeIcons ? com.android.internal.R.style.LargePointer : com.android.internal.R.style.Pointer; TypedArray a = context.obtainStyledAttributes(null, com.android.internal.R.styleable.Pointer, 0, defStyle); int resourceId = a.getResourceId(typeIndex, -1); ... icon = new PointerIcon(type); if ((resourceId & 0xff000000) == 0x01000000) { icon.mSystemIconResourceId = resourceId; } else { icon.loadResource(context, context.getResources(), resourceId); } systemIcons.append(type, icon); return icon; } private static int getSystemIconTypeIndex(int type) { switch (type) { ... case TYPE_SPOT_TOUCH: return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch; ... default: return 0; } }
資源 ID 為 pointer_spot_touch_icon。
<!-- frameworks/base/core/res/res/drawable/pointer_spot_touch_icon.xml --> <?xml version="1.0" encoding="utf-8"?> <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" android:bitmap="@drawable/pointer_spot_touch" android:hotSpotX="16dp" android:hotSpotY="16dp" />
其指向的圖片就是如下熟悉的 Spot png:pointer_spot_touch.png
。之后的loadPointerIcon
階段會將該圖片解析成 Bitmap 并被管理在SpriteIcon
中。
而SpriteIcon
在updatePointerLocked
階段會被存放到SpriteController
中,等待顯示的調(diào)度。
// frameworks/base/libs/input/MouseCursorController.cpp void MouseCursorController::updatePointerLocked() REQUIRES(mLock) { if (!mLocked.viewport.isValid()) { return; } sp<SpriteController> spriteController = mContext.getSpriteController(); spriteController->openTransaction(); ... if (mLocked.updatePointerIcon) { if (mLocked.requestedPointerType == mContext.getPolicy()->getDefaultPointerIconId()) { mLocked.pointerSprite->setIcon(mLocked.pointerIcon); ... } mLocked.updatePointerIcon = false; } spriteController->closeTransaction(); }
顯示Tap
點擊的時候EventHub#getEvents
會產(chǎn)生事件,InputReader#loopOnce
會調(diào)用processEventsLocked
處理事件。
// frameworks/native/services/inputflinger/reader/InputReader.cpp void InputReader::loopOnce() { ... size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); { // acquire lock ... if (count) { processEventsLocked(mEventBuffer, count); } .... } // release lock ... }
之后調(diào)用InputMapper
開始加工事件,并在TouchInputMapper#cookAndDispatch
的時候調(diào)用updateTouchSpots
更新 PointerController
的一些參數(shù)。
// frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp void TouchInputMapper::updateTouchSpots() { ... mPointerController->setPresentation(PointerControllerInterface::Presentation::SPOT); mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); mPointerController->setButtonState(mCurrentRawState.buttonState); setTouchSpots(mCurrentCookedState.cookedPointerData.pointerCoords, mCurrentCookedState.cookedPointerData.idToIndex, mCurrentCookedState.cookedPointerData.touchingIdBits, mViewport.displayId); }
其中比較關鍵的setTouchSpots
是顯示Taps的關鍵步驟,準備 x、y 坐標和壓力值。
在 Reader 而不是 Dispatch、更不是 ViewRootImpl 的時候處理的原因在于:Read 到事件即顯示可以更早地響,同時不用占用 App 進程。
// frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp void TouchInputMapper::setTouchSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits, int32_t displayId) { std::array<PointerCoords, MAX_POINTERS> outSpotCoords{}; for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) { const uint32_t index = spotIdToIndex[idBits.clearFirstMarkedBit()]; float x = spotCoords[index].getX(); float y = spotCoords[index].getY(); float pressure = spotCoords[index].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE); ... } mPointerController->setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits, displayId); }
其后PointerController
會通過TouchSpotController
創(chuàng)建Spot
實例向其發(fā)送updateSprite
請求。最后回調(diào) SpriteController
調(diào)用setIcon
處理。
// frameworks/base/libs/input/TouchSpotController.cpp void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId) { sprite->setLayer(Sprite::BASE_LAYER_SPOT + id); ... if (icon != mLastIcon) { mLastIcon = icon; if (icon) { sprite->setIcon(*icon); sprite->setVisible(true); } else { sprite->setVisible(false); } } }
// frameworks/base/libs/input/SpriteController.cpp void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) { AutoMutex _l(mController->mLock); ... invalidateLocked(dirty); } void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) { ... if (!wasDirty) { mController->invalidateSpriteLocked(this); } } void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) { bool wasEmpty = mLocked.invalidatedSprites.isEmpty(); mLocked.invalidatedSprites.push(sprite); if (wasEmpty) { if (mLocked.transactionNestingCount != 0) { mLocked.deferredSpriteUpdate = true; } else { mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES)); } } }
MSG_UPDATE_SPRITES
經(jīng)過 Handler 回調(diào)doUpdateSprites
,將取出封裝在SpriteUpdate
中的SpriteIcon
并執(zhí)行 draw。
// frameworks/base/libs/input/SpriteController.cpp void SpriteController::doUpdateSprites() { ... for (size_t i = 0; i < numSprites; i++) { SpriteUpdate& update = updates.editItemAt(i); if ((update.state.dirty & DIRTY_BITMAP) && update.state.surfaceDrawn) { update.state.surfaceDrawn = false; update.surfaceChanged = surfaceChanged = true; } if (update.state.surfaceControl != NULL && !update.state.surfaceDrawn && update.state.wantSurfaceVisible()) { sp<Surface> surface = update.state.surfaceControl->getSurface(); if (update.state.icon.draw(surface)) { update.state.surfaceDrawn = true; update.surfaceChanged = surfaceChanged = true; } } } ... updates.clear(); }
最后,SpriteIcon
將取出Bitmap
描畫到Surface
的Canvas
上去。
// frameworks/base/libs/input/SpriteIcon.cpp bool SpriteIcon::draw(sp<Surface> surface) const { ... graphics::Paint paint; paint.setBlendMode(ABLEND_MODE_SRC); graphics::Canvas canvas(outBuffer, (int32_t)surface->getBuffersDataSpace()); canvas.drawBitmap(bitmap, 0, 0, &paint); ... status = surface->unlockAndPost(); if (status) { ALOGE("Error %d unlocking and posting sprite surface after drawing.", status); } return !status; }
總體流程
通過一個框圖簡單回顧一下整個流程。
可以看到,簡簡單單的 Show taps 功能,從設置、配置、刷新再到顯示,經(jīng)歷了多個進程、多個模塊的協(xié)力。
涉及的Input核心邏輯框圖
到此這篇關于從"Show tabs"了解Android Input系統(tǒng)的文章就介紹到這了,更多相關Android Input系統(tǒng) Show tabs內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Android RecycleView 實現(xiàn)左滑上下分層示例代碼(自定義功能)
這篇文章主要介紹了Android RecycleView 實現(xiàn)左滑上下分層示例代碼(自定義功能),具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-03-03Android手機端小米推送Demo解析和實現(xiàn)方法
本篇文章主要是介紹了Android端小米推送Demo解析和實現(xiàn)方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2016-10-10Android Studio 3.0 Gradle 配置變更
這篇文章主要介紹了Android Studio 3.0 Gradle 配置變更的相關知識,即多渠道打包變更和更改打包命名及路徑的代碼,感興趣的朋友跟隨腳本之家小編一起看看吧2018-03-03android教程使用webview訪問https的url處理sslerror示例
這篇文章主要介紹了android教程使用webview訪問https的url處理sslerror示例,大家參考使用吧2014-01-01Android獲取assets文件夾中的數(shù)據(jù)并寫入SD卡示例
這篇文章主要介紹了Android獲取assets文件夾中的數(shù)據(jù)并寫入SD卡示例,對初學Android開發(fā)的朋友來說是一個很實用的功能,需要的朋友可以參考下2014-07-07Android添加ButterKnife時報錯Error:(2, 0) Cannot add extension wit
今天小編就為大家分享一篇關于Android添加ButterKnife時報錯Error:(2, 0) Cannot add extension with name 'android'的解決辦法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12Android FTP 多線程斷點續(xù)傳下載\上傳的實例
本篇文章主要介紹了Android FTP 多線程斷點續(xù)傳下載\上傳的實例,具有一定的參考價值,有興趣的可以了解一下2017-08-08Android 實現(xiàn)密碼輸入框動態(tài)明文/密文切換顯示效果
在項目中遇到需要提供給用戶一個密碼輸入框明文/密文切換顯示的需求,今天小編借腳本之家平臺給大家分享下Android 實現(xiàn)密碼輸入框動態(tài)明文/密文切換顯示效果,需要的朋友參考下2017-01-01