分析的动力
最近在做人脸识别产品,实现了一个屏幕永久旋转功能,这个功能就包括触摸坐标的转换。然后呢,又有一个客户拿了一个坐标系不正常的触摸屏,让我调整下。这个坐标系的调整呢,其实就是做了一次坐标系的映射,在驱动层我感觉挺好实现的,但是我并没有选择在驱动层实现,而是想在输入系统的 native 层实现。
为了以后能应付各种不同的需求,我决定开始对输入系统进行分析。当然,整个输入系统的东西非常复杂,我必须站在巨人的肩膀上,因此我再研究了一遍邓凡平老师的 <<深入理解Android卷3>> 中对输入系统的解析。好了,闲话少说,让我们开始正题吧。
本文分析是基于Android 11的代码,使用的类的路径如下
frameworks/base/services/java/com/android/server/SystemServer.java
frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
frameworks/native/services/inputflinger/InputManager.cpp
InputManagerService 是一个系统 Binder 服务,它在 system_server 进程中启动
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
// ..
// 1. 创建InputManagerService服务
inputManager = new InputManagerService(context);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
/* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);
// 2. 启动InputManagerService服务
inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());
inputManager.start();
// ...
}
复制代码
这里只是简单列举了 InputManagerService 创建与启动代码,其实 InputManagerService 还与 WindowManagerService 和 DisplayManagerService 有交互,但这又是另外一个很长的话题了,本系列文章并不打算分析这个交互过程。
本文将以 IMS 简称 InputManagerService。
创建IMS
首先来看下 IMS 的创建过程
public InputManagerService(Context context) {
this.mContext = context;
// 一个后台线程Handler
this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
// 读取 /vendor/etc/input-port-associations.xml 文件内容
// 这是平台特有的功能,一般是没有这个文件的
mStaticAssociations = loadStaticInputPortAssociations();
/** When true use the linux /dev/input/event subsystem to detect the switch changes
on the headphone/microphone jack. When false use the older uevent framework. */
// 默许值为 false
mUseDevInputEventForAudioJack =
context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
+ mUseDevInputEventForAudioJack);
// 初始化JNI层,mPtr 指向 JNI 层的 NativeInputManager 对象
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
// File used to enable the double touch gesture.
// 默认为空值
String doubleTouchGestureEnablePath = context.getResources().getString(
R.string.config_doubleTouchGestureEnableFile);
// null
mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
new File(doubleTouchGestureEnablePath);
// 提供system_server进程内部使用接口
LocalServices.addService(InputManagerInternal.class, new LocalService());
}
复制代码
IMS 构造函数,主要就是调用 nativeInit() 来初始化 JNI 层,然后注册本地接口,供其它服务调用。
注意,nativeInit() 有返回值,它其它返回的是一个指针,那么这个指针指向什么呢?
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
// 获取底层的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);
}
复制代码
从最后的返回值可以看出,nativeInit() 函数返回一个指针,这个指针指向了 JNI 层创建的 NativeInputManager 对象。因此,上层的 InputManagerService 的成员变量 long mPtr
其实就是指向 JNI 层的 NativeInputManager 对象。
NativeInputManager 的声明和实现都放在了 com_android_server_input_InputManagerService.cpp 中,这也提示我们 NativeInputManager 是一个 JNI 层类。
创建 NativeInputManager
现在来看下 NativeInputManager 的创建过程
NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
JNIEnv* env = jniEnv();
// 1.保存上层的InputManagerService对象
mServiceObj = env->NewGlobalRef(serviceObj);
{
AutoMutex _l(mLock);
// 2. 初始化一些参数
// mLocked 的类型是 struct Locked,这里初始化了一些参数有什么用呢?
mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
mLocked.pointerSpeed = 0;
mLocked.pointerGesturesEnabled = true;
mLocked.showTouches = false;
mLocked.pointerCapture = false;
// ADISPLAY_ID_DEFAULT值为0,表示默认屏
mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
}
mInteractive = true;
// 2.创建并注册底层服务 InputManager
mInputManager = new InputManager(this, this);
defaultServiceManager()->addService(String16("inputflinger"),
mInputManager, false);
}
复制代码
创建 NativeInputManager 的过程我分为了三步。
第一步,通过成员变量 mServiceObj 指向了上层的 InputManagerService 对象。而刚刚,已经使用InputManagerService 的成员变量 mPrt 指向了 NativeInputManager。如此一来,上层和JNI层就相互关注了,可以互相通信了。
第二步,对一个类型为 struct Locked 的对象 mLocked,进行了一部分成员变量的初始化。
这个 struct Locked 结构体的设计看起来杂乱无章的,那么它到底发挥了什么作用呢?
第三步,创建了一下底层的 Binder 服务 InputManager,并注册到了 Service Manager 中。我们注意到创建 InputManager 使用了两个 this 参数,我们来看下这两个类的关系图