摘要:android APP的启动方式主要分为两种:冷启动和热启动。冷启动会把LOGO当作启动页面;热启动则会把任务快照当作启动页面。相机作为实时获取预览图像的应用,在热启动时就会存在一个问题:前一次退出的预览画面会被存储为任务快照,下一次热启动时,会先显示之前退出时被保存的任务快照,当预览准备好后,刷新预览数据,这就会有一种跳帧的现象。本文通过目前行业机的通用处理方式,将相机的任务快照进行蒙层虚化(模糊)处理,提升用户体验感。
一、准备知识
二、代码实现
目标平台:MTK android 13
方案:在获取任务快照snapshotTask中,填充buffer之前将获取到的buffer内容进行模糊处理。
java
Index: frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotController.java
===================================================================
--- frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotController.java (revision B)
+++ frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotController.java (revision A)
@@ -38,13 +38,16 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.ColorSpace;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
+import android.graphics.RenderEffect;
import android.graphics.RenderNode;
import android.hardware.HardwareBuffer;
+import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.util.ArraySet;
@@ -512,11 +515,73 @@
// Failed to acquire image. Has been logged.
return null;
}
- builder.setSnapshot(screenshotBuffer.getHardwareBuffer());
+ String targetPackage = task.getTopMostActivity().mActivityComponent.getPackageName();
+ if ("com.mediatek.camera".equals(targetPackage)) {
+ Slog.d("TaskSnapshotController", "snapshotTask::mtk cam snapshot");
+ builder.setSnapshot(applyBlurToBuffer(screenshotBuffer.getHardwareBuffer(), screenshotBuffer.getColorSpace()));
+ } else {
+ builder.setSnapshot(screenshotBuffer.getHardwareBuffer());
+ }
builder.setColorSpace(screenshotBuffer.getColorSpace());
return builder.build();
}
+ private HardwareBuffer applyBlurToBuffer(HardwareBuffer originalBuffer,
+ ColorSpace colorSpace) {
+ // Only RGBA format is supported
+ if (originalBuffer.getFormat() != HardwareBuffer.RGBA_8888 &&
+ originalBuffer.getFormat() != HardwareBuffer.RGBA_FP16) {
+ Slog.w("TaskSnapshotController", "Unsupported buffer format for blur: " + originalBuffer.getFormat());
+ return originalBuffer;
+ }
+
+ int width = originalBuffer.getWidth();
+ int height = originalBuffer.getHeight();
+ if (width <= 1 || height <= 1) return originalBuffer;
+
+ // Packaging the original HardwareBuffer as a Bitmap (without copying pixels)
+ Bitmap srcBitmap = Bitmap.wrapHardwareBuffer(originalBuffer, colorSpace);
+ if (srcBitmap == null) return originalBuffer;
+
+ try {
+ RenderNode node = RenderNode.create("BlurLayer", null);
+ node.setLeftTopRightBottom(0, 0, width, height);
+ RecordingCanvas canvas = node.start(width, height);
+
+ // Draw the original image
+ canvas.drawBitmap(srcBitmap, 0, 0, null);
+
+ // Apply Gaussian blur effect (API 31+)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ RenderEffect blurEffect = RenderEffect.createBlurEffect(
+ 25f, 25f, android.graphics.Shader.TileMode.CLAMP);
+ node.setRenderEffect(blurEffect);
+ } else {
+ // The low version cannot be blurred, and the original buffer is returned directly
+ srcBitmap.recycle();
+ return originalBuffer;
+ }
+
+ node.end(canvas);
+
+ // Generate new HardwareBuffer
+ Bitmap blurredBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
+ srcBitmap.recycle(); // Release the temporary Bitmap (without closing the original buffer)
+
+ if (blurredBitmap == null) {
+ return originalBuffer;
+ }
+
+ HardwareBuffer blurredBuffer = blurredBitmap.getHardwareBuffer();
+ blurredBitmap.recycle(); // The Bitmap wrapper is released, but its internal buffer is still referenced
+ return blurredBuffer != null ? blurredBuffer : originalBuffer;
+
+ } catch (Exception e) {
+ Slog.e("TaskSnapshotController", "Failed to apply blur to buffer", e);
+ return originalBuffer;
+ }
+ }
+
void setTaskSnapshotEnabled(boolean enabled) {
mTaskSnapshotEnabled = enabled;
}