从"Show tabs"了解Android Input系统

作者:Aloys_Code 时间:2023-12-21 05:20:49 

Input源码解读——从"Show tabs"开始

本文基于Android T版本源码,梳理当用户在开发者选项中开启Show tabs功能后显示第点按操作的视觉反馈的原理,来进一步了解Android Input系统

从"Show tabs"了解Android Input系统

Settings 写入设置

首先是设置应用(Settings)提供的开发者选项画面响应点击,将Show taps选项对应的设置Key SHOW_TOUCHES的 ON 值通过android.provder.Settings接口写入到保存系统设置数据的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监听设置

负责管理输入的系统服务InputManagerService在启动之际,会监听设置中的 SHOW_TOUCHES字段的变化,在设置产生变化的时候调用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);
}

这里的mInputManagerInputManagerInterface对象实例,InputManagerInputManagerInterface和子类,所以通过mInputManager可以连接NativeInputManagerInputReader

这里向负责读取事件的InputReader发出更新配置的请求,配置变更的Type为 CHANGE_SHOW_TOUCHES

通过 InputReader 请求刷新配置

InputReader接收到配置变化的Type之后,会根据记录待刷新配置的变量 mConfigurationChangesToRefresh判断当前是否已经在刷新过程中。
如果尚未处于刷新中,则更新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中读取事件和没有事件时便调用epoll_wait进入等待状态的循环当中。
所以为了让其立即处理配置变化,需要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唤醒后处于等待状态的getEvents会结束,之后InputReader线程会进入下次循环即loopOnce
其首先将检查是否存在待刷新的配置变化changes,存在的话调用refreshConfigurationLockedInputDevice去重新适配变化。

// 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在调用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配置变化

InputDeviceconfigure需要处理很多配置变化,比如键盘布局、麦克风等。对于Show taps的变化关注调用 InputMappercongfigure即可。

// 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 进一步处理

众多输入事件的物理数据需要对应的InputMapper来转化为上层能识别的事件类型。比如识别键盘输入的 KeyboardInputMapper、识别震动的VibratorInputMapper等等。

现在的触摸屏都支持多点触控,所以是MultiTouchInputMapper来处理的。可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会依据changes的类型进行对应处理,对于SHOW_TOUCHES的变化需要调用configureInputDevice进一步处理。

创建和初始化 PointerController

configureInputDevice进行多个参数的测量和配置,其中和Show taps相关的是PointerController的创建,该类是 Mouse、Taps、Pointer location 等系统 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();
   }
   ...
}

这里调用InputReaderContext#getPointerControllerInputReader::ContextImplInputReaderContext的子类,所以会回调到InputReader开启PointerController的创建和初始化。

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

这里调用InputReaderPolicyInterface#obtainPointerController,而NativeInputManagerInputReaderPolicyInterface的子类。

// 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执行完之后调用updatePointerDisplayLocked执行PointerController的初始化。

初始化 PointerController

调用PointerControllersetDisplayViewport传入显示用的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相关的参数,并执行两个重要步骤: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);
   ...
}

省略诸多细节,loadPointerResources将通过InputManagerServiceJNI端以及PointerIconJNI端创建PointerIcon实例,并读取显示的资源。

getSystemIcon则是负责的函数,其将读取系统资源里名为PointerStyle,并读取指针对应的资源 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中。

SpriteIconupdatePointerLocked阶段会被存放到SpriteController中,等待显示的调度。

// 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会产生事件,InputReader#loopOnce会调用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
   ...
}

之后调用InputMapper开始加工事件,并在TouchInputMapper#cookAndDispatch的时候调用updateTouchSpots更新 PointerController的一些参数。

// 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创建Spot实例向其发送updateSprite请求。最后回调 SpriteController调用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经过 Handler 回调doUpdateSprites,将取出封装在SpriteUpdate中的SpriteIcon并执行 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描画到SurfaceCanvas上去。

// 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 tabs"了解Android Input系统

可以看到,简简单单的 Show taps 功能,从设置、配置、刷新再到显示,经历了多个进程、多个模块的协力。

涉及的Input核心逻辑框图

从"Show tabs"了解Android Input系统

来源:https://www.cnblogs.com/jiangxinnju/p/17035554.html

标签:Android,Input系统
0
投稿

猜你喜欢

  • Springboot 整合RabbitMq(用心看完这一篇就够了)

    2023-11-23 05:27:17
  • 安卓(Android)中如何实现滑动导航

    2023-10-17 10:51:18
  • Java 动态数组的实现示例

    2022-02-27 07:05:25
  • .net使用Aspose.Words进行Word替换操作的实现代码

    2023-03-23 21:28:59
  • Android库项目中的资源ID冲突的解决方法

    2023-11-04 05:32:53
  • 浅谈C# 抽象类与开闭原则

    2023-11-07 11:00:08
  • Springboot整合PageOffice 实现word在线编辑保存功能

    2022-12-03 22:38:18
  • Eclipse配置maven环境的图文教程

    2021-11-15 19:06:02
  • Android自定义EditText实现登录界面

    2022-07-03 11:59:25
  • Java与C++实现相同的MD5加密算法简单实例

    2023-08-31 09:43:02
  • Redisson公平锁的源码解读分享

    2022-12-13 01:20:40
  • 一文搞懂Java设计模式之责任链模式

    2021-12-24 22:19:40
  • 详解JDK自带javap命令反编译class文件和Jad反编译class文件(推荐使用jad)

    2021-12-24 00:29:29
  • 解决Java中socket使用getInputStream()阻塞问题

    2022-06-16 14:33:14
  • 解决SpringBoot 测试类无法自动注入@Autowired的问题

    2023-11-02 04:34:14
  • Spring实战之XML与JavaConfig的混合配置详解

    2023-07-11 18:07:20
  • 关于C# dynamic装箱问题

    2021-09-13 19:22:40
  • Winform窗体效果实例分析

    2023-12-12 04:44:08
  • C#实现飞行棋(Winform)

    2023-08-07 07:08:55
  • java实现的新浪微博分享代码实例

    2023-07-06 08:33:13
  • asp之家 软件编程 m.aspxhome.com