问题
使用mediaPlay+TextureView播放和显示视频,在红米note4x上视频播放时切后台返回时视频区域黑屏,提示音频可以继续播放,在其他手机上播放时没有这种问题。
代码
class VideoView(context: Context, attrs: AttributeSet? = null) : TextureView(context, attrs), TextureView.SurfaceTextureListener, LifecycleObserver {
private var mediaPlayer: MediaPlayer? = null
private var surface: Surface? = null
private var resId: Int = 0
init {
if (context is FragmentActivity) {
context.lifecycle.addObserver(this)
}
surfaceTextureListener = this
}
fun setVideoId(resId: Int) {
this.resId = resId
mediaPlayer = MediaPlayer.create(context, resId)
}
fun start() {
mediaPlayer?.start()
}
fun release() {
mediaPlayer?.apply {
this.stop()
this.release()
}
surfaceTexture?.release()
mediaPlayer = null
}
private fun initSurface() {
surface = Surface(surfaceTexture)
mediaPlayer?.setSurface(surface)
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun destroy() {
release()
if (context is FragmentActivity) {
(context as FragmentActivity).lifecycle.removeObserver(this)
}
}
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
initSurface()
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
release()
return false
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
}
问题分析
在红米note 4x手机上切后台时会调用onSurfaceTextureDestroyed,由于在onSurfaceTextureDestroyed中会调用release()在release()中会进行播放器和资源的release,所以返回前台时调用onSurfaceTextureAvailable后由于播放器是空,所以展示黑屏。
1、红米note 4x调用栈
红米note切后台
如下图
调用栈断点打印出来的堆栈信息
onSurfaceTextureDestroyed:91, MyVideoView (com.bytedance.minddance.android.devicecheck.view)
destroySurface:231, TextureView (android.view)
destroyHardwareResources:364, TextureView (android.view)
destroyResources:518, ThreadedRenderer (android.view)
destroyResources:525, ThreadedRenderer (android.view)
destroyResources:525, ThreadedRenderer (android.view)
destroyResources:525, ThreadedRenderer (android.view)
destroyResources:525, ThreadedRenderer (android.view)
destroyResources:525, ThreadedRenderer (android.view)
destroyResources:525, ThreadedRenderer (android.view)
destroyResources:525, ThreadedRenderer (android.view)
destroyResources:525, ThreadedRenderer (android.view)
destroyResources:525, ThreadedRenderer (android.view)
destroyResources:525, ThreadedRenderer (android.view)
destroyResources:525, ThreadedRenderer (android.view)
destroyResources:525, ThreadedRenderer (android.view)
destroyHardwareResources:513, ThreadedRenderer (android.view)
setWindowStopped:1186, ViewRootImpl (android.view)
setStoppedState:639, WindowManagerGlobal (android.view)
performStop:7023, Activity (android.app)
performStopActivityInner:3965, ActivityThread (android.app)
handleStopActivity:4023, ActivityThread (android.app)
-wrap25:-1, ActivityThread (android.app)
handleMessage:1577, ActivityThread$H (android.app)
dispatchMessage:102, Handler (android.os)
loop:165, Looper (android.os)
main:6375, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:883, ZygoteInit$MethodAndArgsCaller (com.android.internal.os)
main:773, ZygoteInit (com.android.internal.os)
根据红米Android系统版本查找源码android-7.0.0_r6:TextureView.java
private void destroySurface() {
if (mLayer != null) {
mLayer.detachSurfaceTexture();
boolean shouldRelease = true;
if (mListener != null) {
shouldRelease = mListener.onSurfaceTextureDestroyed(mSurface);
}
synchronized (mNativeWindowLock) {
nDestroyNativeWindow();
}
mLayer.destroy();
if (shouldRelease) mSurface.release();
mSurface = null;
mLayer = null;
// Make sure if/when new layer gets re-created, transform matrix will
// be re-applied.
mMatrixChanged = true;
mHadSurface = true;
}
}
mListener.onSurfaceTextureDestroyed(mSurface)方法是在destroySurface() 中调用的。
在note 4x中切后台调用stopActivity后会调用destorySurface方法,导致mListener.onSurfaceTextureDestroyed(mSurface)调用并且释放了mediaPlay相关资源,切回前台后才会展示黑屏。
注意:同时查看了其他版本的TextureView没有找到destroySurface方法。也就是说只有在这个版本上才会有这个问题。
2、其他手机调用栈
华为手机只有在退出当前activity时才会调用onSurfaceTextureDestroyed方法,
下面是退出时的调用栈
onSurfaceTextureDestroyed:91, MyVideoView (com.bytedance.minddance.android.devicecheck.view)
releaseSurfaceTexture:261, TextureView (android.view)
onDetachedFromWindowInternal:232, TextureView (android.view)
dispatchDetachedFromWindow:19819, View (android.view)
dispatchDetachedFromWindow:4010, ViewGroup (android.view)
dispatchDetachedFromWindow:4010, ViewGroup (android.view)
dispatchDetachedFromWindow:4010, ViewGroup (android.view)
dispatchDetachedFromWindow:4010, ViewGroup (android.view)
removeViewInternal:5653, ViewGroup (android.view)
removeViewInternal:5624, ViewGroup (android.view)
removeView:5555, ViewGroup (android.view)
moveToState:1248, FragmentManager (androidx.fragment.app)
moveToState:1354, FragmentManager (androidx.fragment.app)
moveFragmentToExpectedState:1432, FragmentManager (androidx.fragment.app)
moveToState:1495, FragmentManager (androidx.fragment.app)
dispatchStateChange:2617, FragmentManager (androidx.fragment.app)
dispatchDestroy:2601, FragmentManager (androidx.fragment.app)
dispatchDestroy:330, FragmentController (androidx.fragment.app)
onDestroy:365, FragmentActivity (androidx.fragment.app)
onDestroy:242, AppCompatActivity (androidx.appcompat.app)
onDestroy:435, BaseActivity (com.bytedance.minddance.android.ui.base)
onDestroy:309, BaseSlideBackActivity (com.bytedance.minddance.android.ui.base)
performDestroy:8349, Activity (android.app)
callActivityOnDestroy:1355, Instrumentation (android.app)
performDestroyActivity:5670, ActivityThread (android.app)
handleDestroyActivity:5715, ActivityThread (android.app)
execute:44, DestroyActivityItem (android.app.servertransaction)
executeLifecycleState:190, TransactionExecutor (android.app.servertransaction)
execute:105, TransactionExecutor (android.app.servertransaction)
handleMessage:2473, ActivityThread$H (android.app)
dispatchMessage:110, Handler (android.os)
loop:219, Looper (android.os)
main:8349, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:513, RuntimeInit$MethodAndArgsCaller (com.android.internal.os)
main:1055, ZygoteInit (com.android.internal.os)
华为手机上在切后台时不会调用到onSurfaceTextureDestroyed方法。
4、解决方法
上面的问题是由于切后台时调用onSurfaceTextureDestroyed方法,并且在onSurfaceTextureDestroyed中stopPlayback中对mediaPlay进行了释放,所以切回前台时导致黑屏,解决方法是去掉onSurfaceTextureDestroyed中的release()方法.
总结
Android系统版本android-7.0.0_r6这个版本的切后台都会有这种问题。
参考
1、https://stackoverflow.com/questions/6347924/black-screen-when-returning-to-video-playback-activity-in-android
2、https://stackoverflow.com/questions/33386039/applying-textureview-to-mediaplayer-after-mediaplayer-prepare-causes-black-scree
3、https://developer.android.com/reference/android/media/MediaPlayer
4、https://developer.android.com/guide/topics/media/mediaplayer
5、https://developer.android.com/reference/android/view/TextureView?hl=en
6、https://blog.mindorks.com/using-mediaplayer-to-play-an-audio-file-in-android