背景:
在研究系统自带模拟屏幕相关源码时候,发现Overlays这个模拟屏幕有如下自动适配现象:
大家可以看到手机屏幕自身是竖屏,但是模拟屏幕是横屏情况,这里看到会有一个自动的竖屏数据放到横屏中间,然后两边啥也不显示。
但是横屏情况下明显模拟屏幕Overlay是全部分覆盖的
疑问点:
横屏情况比较好理解,因为只需要对录屏的数据进行等比例缩放既可以,但是竖屏数据就明显需要额外处理,让竖屏居中显示,不然直接等比例缩放,肯定会产生形变。所以核心问题只有一个那就是分析追踪出如何实现的竖屏自适配显示在模拟屏幕的上。
问题线索探索猜想1:
必须知道背景知识:
模拟屏幕其实本质是一个窗口,也是有view展示的,具体如下:
frameworks/base/services/core/java/com/android/server/display/OverlayDisplayWindow.java
private void createWindow() {
LayoutInflater inflater = LayoutInflater.from(mContext);
mWindowContent = inflater.inflate(
com.android.internal.R.layout.overlay_display_window, null);
mWindowContent.setOnTouchListener(mOnTouchListener);
mTextureView = (TextureView)mWindowContent.findViewById(
com.android.internal.R.id.overlay_display_window_texture);
mTextureView.setPivotX(0);
mTextureView.setPivotY(0);
mTextureView.getLayoutParams().width = mWidth;
mTextureView.getLayoutParams().height = mHeight;
mTextureView.setOpaque(false);
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
mTitleTextView = (TextView)mWindowContent.findViewById(
com.android.internal.R.id.overlay_display_window_title);
mTitleTextView.setText(mTitle);
mWindowParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY);
mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
if (mSecure) {
mWindowParams.flags |= WindowManager.LayoutParams.FLAG_SECURE;
}
if (DISABLE_MOVE_AND_RESIZE) {
mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
}
mWindowParams.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
mWindowParams.alpha = WINDOW_ALPHA;
mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
mWindowParams.setTitle(mTitle);
//省略部分
}
可以看到模拟器屏幕显示是靠overlay_display_window展示:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000">
<TextureView android:id="@+id/overlay_display_window_texture"
android:layout_width="0px"
android:layout_height="0px" />
<TextView android:id="@+id/overlay_display_window_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal" />
</FrameLayout>
可以看到就是个FrameLayout布局包含如下两个:
一个TextureView显示在底层
这个主要就是来提供对应的Surface来显示真实的屏幕数据,类似SurfaceView一样,大小一般就是整个模拟屏幕大小
一个TextView显示在上层用来显示模拟屏幕的名字
有了上面知识背景后猜想:
有没有可能模拟屏幕窗口显示的textureview会根据情况自身调整自己大小位置?
即是不是会识别当前显示屏幕数据是竖屏还是横屏,来调整TextureView大小
验证猜想,其实验证是不是这种还是比较方便,只需要开启一下全局布局边界既可以,当然也可以代码中调试。
从上面就可以看出,这种猜想错误
问题线索探索猜想2:
必备背景:
可以考虑通过dumpsys SurfaceFlinger来看看各个图层展示情况,哪怕是模拟屏幕也是需要在sf中有相关的图层的
执行命令:
adb shell dumpsys SurfaceFlinger
正常屏幕的layer
- Output Layer 0x78310d6980(com.android.settings/com.android.settings.SubSettings#2021)
Region visibleRegion (this=0x78310d6998, count=1)
[ 0, 0, 1080, 1920]
Region visibleNonTransparentRegion (this=0x78310d6a00, count=1)
[ 0, 0, 1080, 1920]
Region coveredRegion (this=0x78310d6a68, count=3)
[ 0, 0, 1080, 74]
[134, 215, 854, 695]
[ 0, 1794, 1080, 1920]
Region output visibleRegion (this=0x78310d6b40, count=1)
[ 0, 0, 1080, 1920]
Region shadowRegion (this=0x78310d6ba8, count=1)
[ 0, 0, 0, 0]
Region outputSpaceBlockingRegionHint (this=0x78310d6c40, count=1)
[ 0, 0, 0, 0]
forceClientComposition=false clearClientTarget=true displayFrame=[0 0 1080 1920] sourceCrop=[0.000000 0.000000 1080.000000 1920.000000] bufferTransform=0 (0) dataspace=V0_SRGB (142671872) whitePointNits=-1.000000 dimmingRatio=1.000000 override buffer=0x0 override acquire fence=0x0 override display frame=[0 0 -1 -1] override dataspace=UNKNOWN (0) override display space=ProjectionSpace{bounds=Rect(0, 0, -1, -1), content=Rect(0, 0, -1, -1), orientation=ROTATION_0} override damage region= Region (this=0x78310d6cf0, count=1)
[ 0, 0, -1, -1]
override visible region= Region (this=0x78310d6d58, count=1)
[ 0, 0, 0, 0]
override peekThroughLayer=0x0 override disableBackgroundBlur=false
hwc: layer=0x088f composition=DEVICE (2)
模拟屏幕的对应layer
- Output Layer 0x78310d7660(com.android.settings/com.android.settings.SubSettings#2021 (Mirror)#2074)
Region visibleRegion (this=0x78310d7678, count=1)
[225, 0, 495, 480]
Region visibleNonTransparentRegion (this=0x78310d76e0, count=1)
[225, 0, 495, 480]
Region coveredRegion (this=0x78310d7748, count=3)
[225, 0, 495, 19]
[259, 54, 439, 174]
[225, 449, 495, 480]
Region output visibleRegion (this=0x78310d7820, count=1)
[225, 0, 495, 480]
Region shadowRegion (this=0x78310d7888, count=1)
[ 0, 0, 0, 0]
Region outputSpaceBlockingRegionHint (this=0x78310d7920, count=1)
[ 0, 0, 0, 0]
forceClientComposition=false clearClientTarget=false displayFrame=[225 0 495 480] sourceCrop=[0.000000 0.000000 1080.000000 1920.000000] bufferTransform=0 (0) dataspace=V0_SRGB (142671872) whitePointNits=-1.000000 dimmingRatio=1.000000 override buffer=0x0 override acquire fence=0x0 override display frame=[0 0 -1 -1] override dataspace=UNKNOWN (0) override display space=ProjectionSpace{bounds=Rect(0, 0, -1, -1), content=Rect(0, 0, -1, -1), orientation=ROTATION_0} override damage region= Region (this=0x78310d79d0, count=1)
[ 0, 0, -1, -1]
override visible region= Region (this=0x78310d7a38, count=1)
[ 0, 0, 0, 0]
override peekThroughLayer=0x0 override disableBackgroundBlur=false
可以看出这里Activity Layer的visibleRegion就有较大区别
原始屏幕是
[ 0, 0, 1080, 1920]
模拟屏幕是
[225, 0, 495, 480]
这里就可以看出在模拟屏幕上其实Layer就是已经适配了模拟屏幕情况的,那么这个Layer这个visibleRegion谁导致呢?
可以继续sf的dump中查看到如下结果:
也就是这个geomLayerTransform导致的差异,那么这个geomLayerTransform又是谁决定呢?
这个就需要对Layer代码进行打印追踪,最后确定是如下:
有这里的mRequestedTransform决定,那么mRequestedTransform又是谁影响它的数据呢?
一般都是setMatrix和setPosition方法
bool Layer::setMatrix(const layer_state_t::matrix22_t& matrix) {
if (mRequestedTransform.dsdx() == matrix.dsdx && mRequestedTransform.dtdy() == matrix.dtdy &&
mRequestedTransform.dtdx() == matrix.dtdx && mRequestedTransform.dsdy() == matrix.dsdy) {
return false;
}
mRequestedTransform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
mDrawingState.sequence++;
mDrawingState.modified = true;
setTransactionFlags(eTransactionNeeded);
return true;
}
bool Layer::setPosition(float x, float y) {
if (mRequestedTransform.tx() == x && mRequestedTransform.ty() == y) {
return false;
}
mRequestedTransform.set(x, y);
mDrawingState.sequence++;
mDrawingState.modified = true;
setTransactionFlags(eTransactionNeeded);
return true;
}
这里setMatrix和setPosition一般都是客户端进程的Transaction相关对SurfaceControl进行的设置,所以这里可以考虑去对应的SurfaceControl加入对应追踪,这个课程已经讲解很多遍了,就不需要多讲解。
这里最后上结论:
从这个堆栈找到对应代码如下:
哈哈,最后是不是看到了核心代码,就是竖屏高度对横屏高度进行等比例缩放,而后平移到了横屏屏幕的中间。
本文章更多详细代码和资料需要购买课程获取
hal+perfetto+surfaceflinger
https://mp.weixin.qq.com/s/LbVLnu1udqExHVKxd74ILg
私聊作者+v(androidframework007)
其他课程七件套专题:
点击这里
https://mp.weixin.qq.com/s/Qv8zjgQ0CkalKmvi8tMGaw