返回

浅谈Android 原生 Picture in Picture 画中画功能的“坑”与规避之道

Android

Android 画中画:潜藏的陷阱与化解之道

画中画(Picture in Picture,PiP)是一项强大的 Android 功能,它允许应用程序在其他任务运行时以小型悬浮窗口的形式显示。该功能于 Android 8.0 中引入,旨在提升多任务处理效率。然而,原生画中画功能却存在诸多限制和问题,导致开发者在使用时不得不小心应对。

原生画中画的限制

1. 任务栈问题

原生画中画功能创建新的任务栈,可能引发意外行为。当用户在画中画模式下执行任务时,系统会将该任务放入新任务栈。此时,如果用户按下 Home 键,系统将返回到前一个应用程序,而不是退出画中画模式。

2. 兼容性问题

并非所有设备都支持原生画中画功能。即使在支持的设备上,也可能出现兼容性问题,如画中画窗口闪烁或冻结。

3. 性能问题

原生画中画功能可能导致性能问题,尤其当画中画窗口包含大量动画或复杂元素。这是因为系统需要不断后台渲染画中画窗口,消耗大量系统资源。

化解陷阱之法

1. 慎重考虑是否使用原生画中画功能

在使用原生画中画功能之前,开发者需要认真思考是否有必要使用该功能。如果仅仅是为了实现简单的悬浮窗口效果,可以考虑使用自定义浮窗,避免原生画中画功能的诸多问题。

2. 正确处理任务栈

若决定使用原生画中画功能,务必正确处理任务栈问题。可以使用以下代码检查当前是否处于画中画模式:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
  if (isInPictureInPictureMode()) {
    // 处于画中画模式
  } else {
    // 不处于画中画模式
  }
}

如果处于画中画模式,用户按下 Home 键时,需要将画中画窗口移至前台。可以使用以下代码实现:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
  if (isInPictureInPictureMode()) {
    moveTaskToFront(taskId, 0);
  }
}

3. 优化性能

若画中画窗口包含大量动画或复杂元素,需要优化性能。可以采取以下措施:

  • 减少动画使用
  • 减少复杂元素使用
  • 使用硬件加速
  • 使用高效渲染技术

自定义浮窗实现画中画功能

若原生画中画功能无法满足特定需求或避免其问题,开发者可以使用自定义浮窗实现画中画功能。自定义浮窗的实现步骤如下:

  1. 创建悬浮窗口布局文件
  2. 创建悬浮窗口服务类
  3. 在服务类中启动悬浮窗口
  4. 在悬浮窗口布局文件中添加控件
  5. 在悬浮窗口服务类中处理控件事件

结语

原生 Android 画中画功能虽然强大,但存在局限性和问题。开发者在使用时应格外注意。如果可以接受应用程序有两个任务栈,可以使用原生画中画功能;否则,建议使用自定义浮窗实现画中画功能。

常见问题解答

1. 如何检查设备是否支持画中画功能?

可以使用以下代码检查设备是否支持画中画功能:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
  boolean supportsPictureInPicture = getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
}

2. 如何在自定义浮窗中实现画中画功能?

可以遵循本文中提到的步骤,使用自定义浮窗实现画中画功能。

3. 如何在画中画窗口中播放视频?

使用原生画中画功能播放视频,可以参考以下代码:

PictureInPictureParams params = new PictureInPictureParams.Builder()
    .setAspectRatio(AspectRatio.of(16, 9))
    .build();

enterPictureInPictureMode(params);

使用自定义浮窗播放视频,可以参考以下代码:

// 创建 MediaPlayer
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.video);

// 设置 MediaPlayer 的位置和大小
mediaPlayer.setDisplay(holder.getSurfaceHolder());
mediaPlayer.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT);

// 开始播放视频
mediaPlayer.start();

4. 如何在画中画窗口中显示 Web 内容?

可以使用 WebView 来在画中画窗口中显示 Web 内容。可以参考以下代码:

// 创建 WebView
WebView webView = new WebView(context);
webView.loadUrl("https://www.example.com");

// 将 WebView 添加到画中画窗口的布局中
ViewGroup layout = (ViewGroup) findViewById(R.id.pip_layout);
layout.addView(webView);

5. 如何在画中画窗口中使用摄像头预览?

可以使用 Camera2 API 在画中画窗口中使用摄像头预览。可以参考以下代码:

// 创建 CameraManager
CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);

// 获取后置摄像头 ID
String cameraId = cameraManager.getCameraIdList()[0];

// 创建 CameraCaptureSession
CameraCaptureSession cameraCaptureSession;
cameraManager.openCamera(cameraId, new CameraDevice.StateCallback() {
  @Override
  public void onOpened(CameraDevice cameraDevice) {
    SurfaceTexture surfaceTexture = new SurfaceTexture(0);
    Surface surface = new Surface(surfaceTexture);

    // 创建 CameraCaptureRequest
    CameraCaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    requestBuilder.addTarget(surface);

    // 创建 CameraCaptureSession
    cameraCaptureSession = cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
      @Override
      public void onConfigured(CameraCaptureSession cameraCaptureSession) {
        cameraCaptureSession.setRepeatingRequest(requestBuilder.build(), null, null);
      }
    }, null);
  }
}, null);

// 设置预览 SurfaceTexture
surfaceTexture.setDefaultBufferSize(1920, 1080);
SurfaceTextureListener surfaceTextureListener = new SurfaceTextureListener() {
  @Override
  public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
    // 将 SurfaceTexture 设置为画中画窗口的预览窗口
    ViewGroup layout = (ViewGroup) findViewById(R.id.pip_layout);
    TextureView textureView = new TextureView(context);
    textureView.setSurfaceTexture(surfaceTexture);
    layout.addView(textureView);
  }
};
surfaceTexture.setOnFrameAvailableListener(surfaceTextureListener, null);