一、背景
之前项目中遇到了一个需求,需要使用画中画功能,原本以为将是一个比较小的功能点,但是真正实际走下来发现这样一个小功能实际上有很多坑点,所以总结一下。
二、避坑指北
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直接以画中画形式打开也会存在黑影的情况。
三、总结
以上大致总结了一下,在开发以及测试过程中遇到的一些坑点,虽然比较细碎,但是都是实际的项目经验,希望能够读者带来一些思考。