我们的董事长在某方面也真是舍得花钱,先后请了柳岩,于震,曾志伟,杨坤等各大艺人来为公司做宣传。
先来二段我们公司抖音直播带货的视频


进入正题
公司临时加的需求要做类似【抖音直播上下翻页滑动切换直播】效果,按排一个同事从网上找找有没有现有的轮子,同事找到了如下文章,,没多想直接就拿来集成了用的 fragment +Viewpager来实现。
提测后发现了较多问题
1,ViewPager会 缓存1个(首次) 或2个(滑动1个后)Fragment,每个Fragment中都有一个播放器和IM的实例,当上下翻页后,播放器和IM实例并没有即时销毁问题,只有当滑过到第三个Fragment后,第一个Fragment中的播放器和IM实例才销毁。 1,导致IM 统计实时在线人数不准确问题,,2,导致用户还没有进入下个直播间时,主播用户会收到 xxx来到直播间的提示。3,导致用户数的礼物数统计有问题。
2,页面重后台进入前台时,缓存的Fragment 都会执行 onResume方法,由于每个Fragment中都维护了一个播放器,而播放器和IM实例的生命周期状态委托给fragent生命周期, 导致onResume方法执行后,页面播放器都会重新开始播放,IM也会重新调用进入某某直播间。(解决方法是在fragment中的onUserVisible中增加falg,onResume时判断falg是否是显示状态,如果是重新走生命周期方法,如果不是return ;)
3,没有及时销毁问题
4,容易导入OOM的发生
5,管理维护起来繁琐,IM 问题(通知消息混乱,退出聊天室不即时,)
二期就已这样的方式上线了。
三期加了送礼物功能,,针对二期这个ViewPager优化问题,我提供了一个思路和Demo交由另外的一个同事来做了。大致思路是 复用ViewPager的 上下滑动事件,问题动态的往ViewPage里添加和移除 播放器和IM组件。
这样做的好处,思路清楚,管理 维护方便,,不需要处理多缓存实例的情况。
从github 找到了如下Demo 供同事参考:
仿映客viewPager上下滑动切换直播 InkeVerticalViewPagerLive
一星期过后,问同事优化的成果,同事说集成的有问题,【播放器在上下滑动过程中会出现 黑屏】如下图 ,并问我有没有遇到过SufaceView 黑屏的问题,,我说没有,他给我演示了一下运行的效果后,我说把你开发的分支名发给我,我看一下 。

开始研究测试,
1,第一步猜测是mVerticalVP.setPageTransforme中的代码导致的,注释后运行程序发现不行有黑屏的问题。
mVerticalVP.setPageTransformer(false) { page, position ->
if (!mScrollInit && (position != 0.0f && position != 1.0f)) {
mVideoPlayer?.let {
it.snapShot()
// it.visibility = View.GONE
}
mScrollInit = true
} else if (position == 1.0f || position == 0.0f) {
mVideoPlayer?.let {
// it.visibility = View.VISIBLE
}
mReplaceImg.visibility = View.GONE
}
val viewGroup = page as ViewGroup
if (position < 0 && viewGroup.id != mCurrentItem) {
val mRoomContainer = viewGroup.findViewById<View>(R.id.room_container)
if (mRoomContainer != null && mRoomContainer.parent != null && mRoomContainer.parent is ViewGroup) {
(mRoomContainer as ViewGroup).removeView(mRoomContainer)
}
}
if (position == 0.0f && viewGroup.id == mCurrentItem && mCurrentItem != mCurrentRoomId) {
mScrollState = if (mCurrentItem > mCurrentRoomId) 2 else 1
mRoomContainer?.let {
if (it.parent != null && it.parent is ViewGroup) {
(it.parent as ViewGroup).removeView(it)
mReplaceImg.visibility = View.GONE
}
}
releaseResource(mVideoPlayer)
loadLiveVideo(viewGroup, mCurrentItem)
}
}
2, 从程序的现象来看,由于ViewPager中每个Fragment只是一个直播的背景图,而上下滑动时 播放器的内容会导致黑屏,,所以直接看的 阿里播放器AliyunVodPlayerView的源码,,首先找 这个类中 和View 添加和 移除,挂载的方法,,并没有发现异常,偶然间看到了SurfaceView 这个类,这个类的 getHolder 方法可以添加相应的回调,感觉是这个出的问题,,经过打断点调试,发现,上下滑动ViewPager时,这个SurfaceHolder.Callback会 频繁的调用 surfaceCreated ,surfaceChanged,surfaceDestroyed 方法,,,上下滑动ViewPager为什么会导致SurfaceHolder.Callback 调用呢?
此时有两个解决方法,1 是 SurfaceHolder.Callback 回调中不处理直接return ,但是没有找到判断条件,应该何时return , 2,第二个方法就是 不让SurfaceHolder.Callback回调,,第一种办法,没想到思路 ,,就开始研究第二种, , ViewPager 上下滑动为什么会导致SurfaceHolder.Callback回调? 而采用之前的 ViewPager +Fragment 的方式之前就没有出现过这个问题呢? 带着疑问开始看ViewPager的源码,
项目中使用的是 fr.castorflex.android.verticalviewpager.VerticalViewPager 类,查看该类的代码后,
首页想到的是在 这个类的 onInterceptTouchEvent 、onTouchEvent,方法的 MotionEvent.ACTION_MOVE 中添加断点, onLayout 中添加断点, 然后开始滑动界面,观察现象,,发现 当放行一个断点后,如果界面 黑屏,说明,,确实调用了某些方法,导致 SurfaceHolder.Callback 回调了,而我现在要做的就是找到那行代码(不调用那行代码),,,然后通过不断的打断点,用排除法缩小范围。由于项目是通过gradle 引用 的是第三VerticalViewPager,所以直接把第三方的代码得copy到了项目里,这样也方法我打断点和 注释代码。
通过这种打断点的方法,最终发现了那个方法 ,如下代码
VerticalViewPager类
这个方法的作用是设置 ViewPager中的子View 是否开启硬件加速功能。 当开启后会有副作用。
private void enableLayers(boolean enable) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final int layerType = enable ?
ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;
ViewCompat.setLayerType(getChildAt(i), layerType, null);
}
}
ViewCompat类
@Deprecated
public static void setLayerType(View view, int layerType, Paint paint) {
view.setLayerType(layerType, paint);
}
view.setLayerType(layerType, paint); 方法是用来设置View 硬件加速的,
注释该行代码即可:如下图:

昨天测试的时候,由于只开了一个直播,,
当有多个直播时,上下滑动后,由于播放器只是调用了stop和从父布局增加和移除,当进入下个直播后,播放器会显示上个直播的画面一段时间,,当请求视频地址接口返回来并开始播放后,播放器界面才会显示当前直播间的画面。
为了解决这个问题,
第一种思路 是 stop后,清空SufaceView中的内容,并设置内容为透明,,但尝试后发现行不通 【SurfaceView清空画布】
第二种思路是 当调用 播放器的stop后, 设置播放器为 it.visibility = View.INVISIBLE,
playerView?.let {
if (it.parent != null && it.parent is ViewGroup) {
(it.parent as ViewGroup).removeView(it)
it.onStop()
it.visibility = View.INVISIBLE
}
}
当 播放器开始播放第一帧后, 设置播放器为 it.visibility = View.VISIBLE ,就可以解决这个问题,
mVideoPlayer?.let {
it.setVideoScalingMode(IPlayer.ScaleMode.SCALE_ASPECT_FILL)
it.setControlBarCanShow(false)
it.setOnPreparedListener {}
it.setOnFirstFrameStartListener(object:IPlayer.OnRenderingStartListener{
override fun onRenderingStart() {
it.visibility = View.VISIBLE
}
})
it.setOnErrorListener {
IPlayer.OnErrorListener {
TODO("最后添加")
}
}
}
注意 在 播放器的 setOnPreparedListener 方法中设置是有问题的,因为 setOnPreparedListener 是即将开始播放的方法,这个方法执行时, 播放器还未开始播放呢, 还是会有上述问题。
最终效果如下图:

参考
硬件加速
【转】观看视频时启用硬件加速有什么用?如果关闭硬件加速又有什么区别呢?
观看视频时启用硬件加速有什么用?如果关闭硬件加速又有什么区别呢?...
硬件加速是用显卡的GPU解码视频,几乎不占用CPU。在播放高清视频时CPU不给力就会卡,不卡也会占用率很高。开启硬件加速是让显卡分担了CPU的解码工作,所以你可以再开别的程序也不会卡。
硬件加速是用来降低视频解码时候CPU负载的,如果CPU性能不够强,看高清视频没有硬件加速的时候就卡了
硬件加速的优点与缺点
硬件加速能使用GPU来加速2D图像的渲染速度,但是硬件加速并不能完全支持所有的渲染操作,
针对自定义的View,硬件加速可能导致渲染出现错误。
如果有自定义的View,需要在硬件加速的设备上进行测试,如果出现渲染的问题,需要关闭硬件加速
网友评论