Android 画中画避坑指北

一、背景

之前项目中遇到了一个需求,需要使用画中画功能,原本以为将是一个比较小的功能点,但是真正实际走下来发现这样一个小功能实际上有很多坑点,所以总结一下。

二、避坑指北

1、声明支持画中画

在 AndroidManifest.xml 中声明 Activity 支持 PiP:

xml 复制代码
<activity android:name=".YourActivity"
    android:supportsPictureInPicture="true" />

2、Android画中画仅支持8以上的机型

小于安卓8的手机就需要隐藏画中画的按钮

3、进入画中画模式:enterPictureInPictureMode

enterPictureInPictureMode 是 Android 中用于将当前 Activity 切换到画中画(PiP)模式的方法。

参数: params: PictureInPictureParams 对象,包含画中画窗口的配置参数

其中我们可以在PictureInPictureParams对象中设置按钮图标。

4、是否是画中画模式:isInPictureInPictureMode()

调用 Activity#isInPictureInPictureMode()可以判断当前是否为画中画模式。

5、画中画状态切换监听

java 复制代码
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
    super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
}

重写 onPictureInPictureModeChanged 用于监听应用进入或退出画中画(Picture-in-Picture, PiP)模式的状态变化。

isInPictureInPictureMode

  • true 表示当前进入 PiP 模式
  • false 表示退出 PiP 模式

6、画中画权限问题

画中画的功能权限默认都是赋予的,但是也可以通过画中画里面设置按钮,去把这个权限给关闭,这个时候就需要判断此时是否有这个权限,否则就无法开启。

判断权限是否开启的方法:

java 复制代码
public static boolean hasPipPermission(){
    AppOpsManager appOpsManager = (AppOpsManager) App.getContext().getSystemService(Context.APP_OPS_SERVICE);
    return appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_PICTURE_IN_PICTURE, android.os.Process.myUid(), App.getContext().getPackageName()) == AppOpsManager.MODE_ALLOWED;
}

跳转到系统画中画权限设置页面的代码:

java 复制代码
activity.startActivity(new Intent("android.settings.PICTURE_IN_PICTURE_SETTINGS", Uri.parse("package:" + activity.getPackageName())));

这里需要注意,必须要使用activity来启动Activity,如果用Application Context就无法跳转

7、画中画模式下,功能按钮的设置

第一种可以使用MediaSessionCompat来进行设置,不过这种方式无法替换icon,局限性比较大。就不再介绍了。

第二种可以使用setActions进行设置,也是官方推荐的方式。

这里就有几个细分的问题:

icon的点击事件如何处理

创建Action会传入一个PendingIntent,然后点击之后会发送广播,在广播接收器中添加事件处理。

java 复制代码
private RemoteAction createRemoteAction(int iconResId, String action) {
    return new RemoteAction(
            Icon.createWithResource(this, iconResId),
            "",
            "",
            PendingIntent.getBroadcast(
                    this,
                    0,
                    new Intent(action),
                    PendingIntent.FLAG_IMMUTABLE
            )
    );
}

广播接收器

java 复制代码
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent == null) {
            return;
        }
        LogWriter.writeLog(TAG, "BroadcastReceiver onReceive() action:" + intent.getAction());
        if (ACTION_START.equals(intent.getAction())) {
            //
        } else if (ACTION_PAUSE.equals(intent.getAction())) {
            //
        } else if (ACTION_PRE.equals(intent.getAction())) {
            //
        } else if (ACTION_NEXT.equals(intent.getAction())) {
            //
        }
    }
};

这里还需要注意不同的action都需要注册,否则无法识别这些按钮

java 复制代码
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_START);
intentFilter.addAction(ACTION_PAUSE);
intentFilter.addAction(ACTION_PRE);
intentFilter.addAction(ACTION_NEXT);
registerReceiver(mBroadcastReceiver, intentFilter);

icon如何动态切换

在需要切换icon的时候,需要调用,来重新刷新配置,来替换icon

scss 复制代码
setPictureInPictureParams(pictureInPictureParams);

广播接收器的问题

因为一般要在广播接收器中去调用setPictureInPictureParams来切换icon。所以必须要注意,需要在Activity的onDestroy()中去反注册广播接收器。 前面的页面内关闭,在新页面也会收到之前页面的广播,在调用setPictureInPictureParams方法时,会爆出token异常的问题。而且也会造成内存泄漏。

8、vivo s7手机打开画中画模式会延迟3500ms左右

如果theme设置了windowIsTranslucent,就会出现这个问题

xml 复制代码
<item name="android:windowIsTranslucent">true</item>

解决办法:就是把这个参数去掉。

9、部分机型会出现进入画中画时会有白色闪一下的问题(比如折叠屏手机)

解决办法:

在启动画中画时,调用setSourceRectHint()方法,配置视频源的位置。视频源位置,如果直接能获取到,那就直接拿来用。一般情况下只能通过屏幕的位置,以及视频的宽高来进行计算,下面就是计算的方法。

java 复制代码
private Rect getSourceRect(Rational rational){
    Rect screenRect = new Rect();
    getWebview().getGlobalVisibleRect(screenRect);
    float screenRation = (float)screenRect.width() / (float)screenRect.height();
    float videoRation = rational.floatValue();
    if(videoRation >= screenRation){
        int height = (int) (screenRect.width() /  videoRation);
        int top = (screenRect.height() - height) / 2;
        int bottom = top + height;
        Rect launchBounds = new Rect(0, top, screenRect.right, bottom);
        return launchBounds;
    }else{
        int width = (int) (videoRation * screenRect.height());
        int top = 0;
        int left = (screenRect.width() - width) / 2;
        int right = left + width;
        int bottom = screenRect.bottom;
        Rect launchBounds = new Rect(left, top, right, bottom);
        return launchBounds;
    }
}

10、画中画启动后,其他页面启动,可能会在画中画内部打开

如果画中画以默认形式打开,背后的页面以无activity的模式来打开页面,那么新的页面就会在画中画中被打开。

解决方案:

方案一,背后的页面不能以无activity的模式来打开页面,必须传入activity来打开。这样改动量非常大,而且有些地方只能以无activity的模式来打开页面。这个方案局限性就很大。

方案二,将画中画的Activity设置为新的task

xml 复制代码
android:taskAffinity=".webForVideo" 
android:excludeFromRecents="true"

一般来说,都会搭配excludeFromRecents=true来使用,防止在后台任务页中,出现两个。

11、画中画宽高比的问题

指定宽高比

如果进入画中画模式不指定宽高比的话,就会按照默认的大小来打开画中画,那么竖屏的视频也会按照横屏的形式来播放。可以调用来指定宽高比。

java 复制代码
setAspectRatio()

宽高比限制问题

根据系统提示,画中画宽高比是有大小限制的,最大是239:100 最小是100:239,如果设置的宽高比超过了这个限制,那么就会出现报错。

所以就需要添加限制,超过最大值,就以最大值进行展示。

Java 复制代码
Rational aspectRatio = new Rational(mVideoUrlBean.getWidth(), mVideoUrlBean.getHeight());
//限定最大值与最小值 1:2.39 2.39:1 否则会crash
Rational maxAspectRatio = new Rational(239, 100);
Rational minAspectRatio = new Rational(100, 239);
if (aspectRatio.floatValue() > maxAspectRatio.floatValue()) {
    aspectRatio = maxAspectRatio;
} else if (aspectRatio.floatValue() < minAspectRatio.floatValue()) {
    aspectRatio = minAspectRatio;
}
builder.setAspectRatio(aspectRatio);

12、直接进入画中画不够丝滑

如果使用ARoute的方式来启动需要画中画的页面,那么在页面跳转、关闭的时候,会有动画。所以一般的情况下,这些动画都不需要,所以可以采用默认的startActivity(new Intent())的方式来启动。

13、画中画,大小切换时,大部分机型会出现闪烁

大概原因定位到是,大小切换时,会走onConfigChange(),在onConfigChange时,WebView会重新布局,从而导致了闪烁,但是不好修复。查看了官方的demo也有这个问题,所以还需要再看看有没有其他办法解决。

14、直接以画中画模式打开,会先出现黑影

目前直接以画中画模式打开,是先打开主体页面,然后再开启画中画。因为主体页面是黑色的,所有会出现黑影闪过。看后面是否可以先将主体页面设置为透明色。不过目前页面设置为透明色还有很多问题。官方demo直接以画中画形式打开也会存在黑影的情况。

三、总结

以上大致总结了一下,在开发以及测试过程中遇到的一些坑点,虽然比较细碎,但是都是实际的项目经验,希望能够读者带来一些思考。

相关推荐
kk爱闹23 分钟前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空2 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭2 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日3 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安3 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑4 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟8 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡9 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi009 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil11 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android