背景:
vip学员在群里问到了一个分屏触摸区域设置的问题,开始以为就是和普通Activity设置区域没啥差别,都是在InputMonitor中进行的设置,但是仔细研究下来其实并不是哈。本文就带大家来手把手分析一下分屏情况下的触摸区域是怎么设置的。
dumpsys input
短信的触摸区域为上半屏幕
com.android.messaging/com.android.messaging.ui.conversationlist.ConversationListActivity
touchableRegion=[0,0][1440,1463]
电话的触摸区域为下半屏幕
com.android.dialer/com.android.dialer.main.impl.MainActivity
touchableRegion=[0,1498][1440,2960]
下面就来剖析出分屏情况下的touchableRegion是如何正确设置获取的。
原因分析追踪:
先来一个正常touchableRegion的传递流图
这里看Region设置的本质来源还是system_server发起的,dumpsys input看到的Region是SurfaceFlinger直接传递的,有了上面的知识基础后,开始分析这个分屏Region设置问题。
分析思路1–从systemserver进程设置源头出发进行打印堆栈分析
具体可以使用InputWindowHandleWrapper的setTouchableRegion进行堆栈打印,既可以很方便的追到哪里设置的touch矛,但是正常使用InputWindowHandleWrapper的setTouchableRegion发现根本没发现有分屏相关区域的任何设置。
分屏情况下的打印setTouchableRegion,发现并没有在system_server层面进行设置
分析思路2-从SurfaceFlinger角度出发分析Region的设置
从SurfaceFlinger看看给InputDispatcher的Region是如何设置的,这个其实在前面sf,input课程都有讲解,这里就不进行这块源码分析,主要还是针对Region的获取部分分析。
下面来看看buildWindowInfos方法
在fillInputInfo方法中主要看核心部分
WindowInfo Layer::fillInputInfo(const ui::Transform& displayTransform, bool displayIsSecure) {
//省略
//明显看到这个地方有对touchableRegion进行额外的设置,而不是直接用systemserver端设置的,估计就是这个地方对分屏的Region进行了重新设置
auto cropLayer = mDrawingState.touchableRegionCrop.promote();
if (info.replaceTouchableRegionWithCrop) {
std::string str;
const Rect bounds(cropLayer ? cropLayer->mScreenBounds : mScreenBounds);
info.touchableRegion = Region(displayTransform.transform(bounds));
} else if (cropLayer != nullptr) {
std::string str;
info.touchableRegion = info.touchableRegion.intersect(
displayTransform.transform(Rect{cropLayer->mScreenBounds}));
}
在fillInputInfo需要加入相关的打印来验证猜想
加入日志后代码如下,主要在 if (info.replaceTouchableRegionWithCrop)前面打印一个Region,在他逻辑后打印一个
std::string str;
info.touchableRegion.dump(str,"info.touchableRegion");
ALOGE(" %s fillInputInfo info str %s ",getName().c_str(),str.c_str());
auto cropLayer = mDrawingState.touchableRegionCrop.promote();
if (info.replaceTouchableRegionWithCrop) {
std::string str;
const Rect bounds(cropLayer ? cropLayer->mScreenBounds : mScreenBounds);
info.touchableRegion = Region(displayTransform.transform(bounds));
info.touchableRegion.dump(str,"info.touchableRegion");
ALOGE("111111111111 %s fillInputInfo info str %s cropLayer %s",getName().c_str(),str.c_str(),cropLayer!=nullptr ? cropLayer->getName().c_str():"null");
} else if (cropLayer != nullptr) {
std::string str;
info.touchableRegion = info.touchableRegion.intersect(
displayTransform.transform(Rect{cropLayer->mScreenBounds}));
info.touchableRegion.dump(str,"info.touchableRegion");
ALOGE("22222222222222 %s fillInputInfo info str %s ",getName().c_str(),str.c_str());
}
然后进入分屏,查看日志结果如下
得到结论如下:
1、分屏的相关window设置的TouchRegion是开始就是0
2、因为replaceTouchableRegionWithCrop标志设置成了true,代表要使用task的bounds覆盖TouchRegion
3、具体使用哪个task的bounds需要靠mDrawingState.touchableRegionCrop这个参数
那么这个replaceTouchableRegionWithCrop和mDrawingState.touchableRegionCrop是哪里设置的呢?
设置堆栈如下:
void populateInputWindowHandle(final InputWindowHandleWrapper inputWindowHandle,
final WindowState w) {
//省略部分
if (task != null) {
// TODO(b/165794636): Remove the special case for freeform window once drag resizing is
// handled by WM shell.
//判断task是不是属于分屏
if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
&& !task.inFreeformWindowingMode()) {
// If the window is in a TaskManaged by a TaskOrganizer then most cropping will
// be applied using the SurfaceControl hierarchy from the Organizer. This means
// we need to make sure that these changes in crop are reflected in the input
// windows, and so ensure this flag is set so that the input crop always reflects
// the surface hierarchy.
//分屏useSurfaceBoundsAsTouchRegion为true
useSurfaceBoundsAsTouchRegion = true;
if (w.mAttrs.isModal()) {
//获取的Task,这个Task就是上面sf的touchableRegionCrop
TaskFragment parent = w.getTaskFragment();
touchableRegionCrop = parent != null ? parent.getSurfaceControl() : null;
}
} else if (task.cropWindowsToRootTaskBounds() && !w.inFreeformWindowingMode()) {
touchableRegionCrop = task.getRootTask().getSurfaceControl();
}
}
inputWindowHandle.setReplaceTouchableRegionWithCrop(useSurfaceBoundsAsTouchRegion);
inputWindowHandle.setTouchableRegionCrop(touchableRegionCrop);
//省略部分
}
代码总结:
1、遍历windowstate的WindowMode是否属于非全屏非自由窗口,如果分屏模式,则需要设置setReplaceTouchableRegionWithCrop为true
2、遍历分屏窗口的父亲Task,把Task设置为TouchableRegionCrop,最后会设置到sf中
整体总结:
在framework系统是属于一个很复杂的体系,每个小分支都会有很多不同的处理方式等,所以当使用正规的思路打堆栈分析不出来时候,不应该直接放弃,更应该从逆向,或者多角度来尝试探索分析,这样才符合实际项目中遇到各种问题都可以使用学习的知识灵活应对,而不是仅仅套一下模板,一旦有一些异常变化就又不知道如何分析,教给各位粉丝的知识一定要活学活用哈,整体理解多角度分析。
更多framework实战干货,请关注下面“千里马学框架”