悲剧!一个全局视频播放器引发的惨案
案件描述
列表中、全屏可播放的视频播放器,实现:列表item提供父容器add播放器videoView,全屏用DecorView直接add播放器videoView
onDetachedFromWindow里清除播放器即从列表item父容器移除播放器videoView,并且videoView置为null
播放器在列表中播放视频,移出屏幕正常
播放器设置全屏播放,crash
onDetachedFromWindow方法
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (adBean == null || ListVideoPlayMg.getInstance().isDestroy()) {
return;
}
// 非全屏清除播放器
// 全屏时实现 是通过DecorView添加播放器,此时再调用cleanVideoView(1、remove后requestLayout,2、videoView = null)
// DecorView.layoutChildren时导致child即videoView为null,导致NullPointerException
if (!ListVideoPlayMg.getInstance().isFullScreen()) {
ListVideoPlayMg.getInstance().cleanVideoView();
}
}
ListVideoPlayMg.getInstance().cleanVideoView();
/**
* 清空播放器
* Note:在item的的onDetachedFromWindow()方法中执行该方法时,要判断非全屏状态!
*/
public void cleanVideoView() {
stopFullScreen();
if (mVideo != null) {
if (mCurPlayerModel != null) {
mProgressManager.saveProgress(mCurPlayerModel.videoId, mVideo.getCurrentPosition());
}
mVideo.release();
// 系统的remove方法
Utils.removeViewFormParent(mVideo);
mVideo = null;
}
//注销控制器
if (mController != null) {
mController.reset();
mController = null;
hasDanmakuView = false;
}
mCurPlayerModel = null;
mVideoContainer = null;
mCurrentPositionInList = POSITION_UNPLAY;
}
看看VideoView进入全屏模式的代码
/**
* 进入全屏
*/
@Override
public void startFullScreen() {
if (mIsFullScreen)
return;
ViewGroup decorView = getDecorView();
if (decorView == null)
return;
mIsFullScreen = true;
//隐藏NavigationBar和StatusBar
hideSysBar(decorView);
//从当前FrameLayout中移除播放器视图
this.removeView(mPlayerContainer);
//将播放器视图添加到DecorView中即实现了全屏
decorView.addView(mPlayerContainer);
setPlayerState(PLAYER_FULL_SCREEN);
}
通过decorView直接add的playerContainer,startFullScreen方法执行removeView会触发requestLayout导致列表中item的onDetachedFromWindow回调,
而在cleanVideoView()方法中将mVideo即mPlayerContainer中的VideoView置null,
最后decorView中的FrameLayout方法layoutChildren
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
有截取,getChildCount为4,但是final View child = getChildAt(i),此child为null,导致if(child.getVisibility() != GONE)NullPointerException,实际上这个child就是VideoView,被设置成null了