一、背景
Android智能座舱,视频播放场景,通过多指滑屏退回桌面,会出现闪屏问题。
二、问题定位以及修改方案
通过录屏,然后一帧一帧播放的方式,发现闪屏是因为,多指的情况下,不仅触发了退回桌面的动作,还触发了调节视频app的亮度,以及视频的快进和快退的动作,几种UI刷新叠加起来,造成了闪屏的现象。
除了调整手势识别的方法解决闪屏的问题,还有一种方式,就是退出页面的时候截屏,卡住UI的刷新,也能解决闪屏的问题。
1. 步骤一
通过如下函数takeScreenshot,截取主屏的bitmap,但是通过SurfaceControl.captureDisplay获取到的bitmap对应的buffer是SurfaceControl.ScreenshotHardwareBuffer,属于hadware bitmaps,无法通过software rendering,会报如下错误:
Software rendering doesn't support hardware bitmaps
必须通过如下方式,将hardware bitmap转成soft bitmap:
Bitmap softwareBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, true);
java
private Bitmap takeScreenshot(int displayId) {
if (displayId != 0) {
return null;
}
// Take the screenshot
final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
final SurfaceControl.DisplayCaptureArgs captureArgs =
new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
.build();
final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
SurfaceControl.captureDisplay(captureArgs);
Bitmap screenShot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
if (screenShot == null) {
Log.e(TAG, "Failed to take fullscreen screenshot");
return null;
}
// Optimization
screenShot.setHasAlpha(false);
Bitmap softwareBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, true);
if (screenShot != null) {
screenShot.recycle();
screenShot = null;
}
return softwareBitmap;
}
2.步骤二
通过函数createScreenshotLayer,将takeScreenshot 获取到的software bitmap,生成对应的SurfaceControl ,也就是底层的Layer数据。
java
private SurfaceControl createScreenshotLayer(Bitmap bitmap) {
Log.w(TAG, "launchHomeFromFivePointerCatch: createScreenshotLayer bitmap : " + bitmap + ", w = " + bitmap.getWidth()
+ " ,h = " + bitmap.getHeight());
// 创建 SurfaceControl 图层
SurfaceControl surfaceControl = new SurfaceControl.Builder()
.setName("ScreenshotLayer")
.setBufferSize(bitmap.getWidth(), bitmap.getHeight())
.setFormat(PixelFormat.TRANSLUCENT)
.build();
Surface surface = new Surface(surfaceControl);
Canvas canvas = surface.lockCanvas(null);
canvas.drawBitmap(bitmap, 0, 0, null);
surface.unlockCanvasAndPost(canvas);
return surfaceControl;
}
3. 步骤三
通过playExitAnimation实现冻屏或者刷帧的动作,最后测试发现,冻屏比较适合,也相对简单。刷帧的方式,因为不能很好的掌握UI的调整和显示的时间,无法正确确定刷帧的起始和结束的时机。
java
private void playExitAnimation(SurfaceControl surfaceControl) {
Log.w(TAG, "launchHomeFromFivePointerCatch: playExitAnimation surfaceControl : " + surfaceControl);
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
方案1:冻屏
/*
// 设置动画属性
/transaction.setMatrix(surfaceControl, 0.5f, 0, 0, 0.5f); // 缩放到 50%
/transaction.setAlpha(surfaceControl, 0.0f); // 设置透明度为 0
transaction.setLayer(surfaceControl, Integer.MAX_VALUE);
transaction.show(surfaceControl);
transaction.apply();
// 延时销毁图层
mHandler.postDelayed(() -> {
transaction.remove(surfaceControl).apply();
}, 500); // 动画持续 500ms
*/
float startScale = 1.0f;
float endScale = 0.5f;
float startAlpha = 1.0f;
float endAlpha = 0.0f;
int duration = 500;
long startTime = System.currentTimeMillis();
方案2:通过handler 刷帧
/*mHandler.post(new Runnable() {
@Override
public void run() {
if (!surfaceControl.isValid()) {
return;
}
long elapsedTime = System.currentTimeMillis() - startTime;
if (elapsedTime > duration) return;
float progress = (float) elapsedTime / duration;
float currentScale = startScale + progress * (endScale - startScale);
float currentAlpha = startAlpha + progress * (endAlpha - startAlpha);
Log.w(TAG, "launchHomeFromFivePointerCatch: playExitAnimation progress : -----------" + progress
+ ", currentScale = " + currentScale + ", currentAlpha = " + currentAlpha);
//SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
transaction.setMatrix(surfaceControl, currentScale, 0, 0, currentScale); // 设置缩放
transaction.setAlpha(surfaceControl, currentAlpha); // 设置透明度
transaction.setLayer(surfaceControl, Integer.MAX_VALUE);
transaction.show(surfaceControl);
transaction.apply();
mHandler.post(this);
}
});*/
方案3:通过Choreographer刷帧
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
long startTime = System.nanoTime();
@Override
public void doFrame(long frameTimeNanos) {
if (!surfaceControl.isValid()) {
return;
}
float progress = (frameTimeNanos - startTime) / (float) (duration * 1_000_000); // 计算进度
if (progress > 1.0f) return;
float currentScale = startScale + progress * (endScale - startScale);
float currentAlpha = startAlpha + progress * (endAlpha - startAlpha);
Log.w(TAG, "launchHomeFromFivePointerCatch: playExitAnimation progress : -----------" + progress
+ ", currentScale = " + currentScale + ", currentAlpha = " + currentAlpha);
//transaction.setMatrix(surfaceControl, currentScale, 0, 0, currentScale); // 设置缩放
transaction.setAlpha(surfaceControl, currentAlpha); // 设置透明度
transaction.setLayer(surfaceControl, Integer.MAX_VALUE);
transaction.show(surfaceControl);
transaction.apply();
Choreographer.getInstance().postFrameCallback(this); // 下一帧
}
});
mHandler.postDelayed(() -> {
//SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
transaction.hide(surfaceControl); // 隐藏图层
transaction.remove(surfaceControl); // 移除图层
transaction.apply();
surfaceControl.release(); // 释放资源
}, duration);
}