
原以为显示 Tap 位置是
ViewRootImpl里依据 Touch 位置显示的PopupWindow,实际不是、而且要复杂得多。
开发者选项画面里的 “Show taps” 选项,开发者一定不陌生。开启之后,截屏或录屏里可以直观地展示点击过的位置,非常方便。
类似的选项还有显示 Touch 参数的 “Pointer location”,原理差不多。本次我们聚焦 “Show taps” 的功能,查阅 Android 12 的源码,将开启和显示流程分析清楚。
借此也窥探一下 Android 最重要的 Input 系统。
1. Settings 写入设置
首先是 Settings App 提供的开发者选项画面响应点击,将 “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;
}
...
}
2. IMS 监听和反映设置
负责管理输入的系统服务 InputManagerService 在启动之际,会注册监听 SHOW_TOUCHES Key 的观察者,在设置产生变化的时候调用 JNI 开始反映设置。
// 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);
nativeSetShowTouches(mPtr, setting != 0);
}
...
}
JNI 端的 NativeInputManager 持有 InputFlinger ,向其中负责读取事件的 InputReader 发出更新配置的请求,配置变更的 Type 为 CHANGE_SHOW_TOUCHES。
在此之前需要先更新管理配置信息的 mLocked 结构体中的 showTouches 成员,InputFlinger 在刷新配置的时候需要验证。
// frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
static void nativeSetShowTouches(JNIEnv* /* env */,
jclass /* clazz */, jlong ptr, jboolean enabled) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
im->setShowTouches(enabled);
}
void NativeInputManager::setShowTouches(bool enabled) {
{ // acquire lock
...
mLocked.showTouches = enabled;
} // release lock
mInputManager->getReader()->requestRefreshConfiguration(
InputReaderConfiguration::CHANGE_SHOW_TOUCHES);
}
3. 通过 InputReader 请求刷新配置
InputReader 接收到配置变化的 Type 之后,会根据记录待刷新配置的变量 mConfigurationChangesToRefresh 判断当前是否已经在刷新过程中。
如果尚未处于刷新中,则标记需要 wake 事件源头 EventHub,之后会将该变化添加该变量到中,最后就是通知 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();
}
}
}
4. EventHub 唤醒 InputReader 线程
IMS 过来的刷新请求最终需要 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);
...
}
...
}
5. InputReader 线程刷新配置
EventHub 唤醒后处于等待状态的 getEvents() 会结束,之后 InputReader 线程会进入下次循环即 loopOnce()。
其首先将检查是否存在待刷新的配置变化 changes,存在的话调用 refreshConfigurationLocked() 让 InputDevice 去重新配置这项变化。
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 进一步处理之前需要先从 JNI 获取配置(getReaderConfiguration())的变化放入 mConfig 中。
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

本文深入探讨了Android系统中Showtaps功能的工作原理,从Settings写入设置开始,详细分析了IMS如何监听和反映设置变化,再到InputReader线程如何处理配置刷新,最终揭示了PointerController如何创建、初始化以及如何显示tap。通过这一过程,我们可以了解到Android Input系统的关键组件和交互机制。
最低0.47元/天 解锁文章
384





