bclymer bclymer - 4 years ago 136
Android Question

"The surface has been released" inside "surfaceCreated"

I know this is a common question, however this stack trace shows something else is wrong. You can see that even though

setDisplay(holder)
is called inside of
surfaceCreated
it still throws
IllegalArgumentException
. This isn't a rare exception either, yesterday happening ~125,000 times in ~3,000,000 clip views. I can assure you that
mCurrentPlayer
is initialized correctly as well.

surfaceCreated:

@Override
public void surfaceCreated(SurfaceHolder holder) {
mIsSurfaceCreated = true;
mCurrentPlayer.setDisplay(holder);
}


surfaceDestroy:

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mIsSurfaceCreated = false;

// Could be called after player was released in onDestroy.
if (mCurrentPlayer != null) {
mCurrentPlayer.setDisplay(null);
}
}


Stacktrace:

java.lang.IllegalArgumentException: The surface has been released
at android.media.MediaPlayer._setVideoSurface(Native Method)
at android.media.MediaPlayer.setDisplay(MediaPlayer.java:660)
at com.xxx.xxx.view.VideoPlayerView.surfaceCreated(VideoPlayerView.java:464)
at android.view.SurfaceView.updateWindow(SurfaceView.java:543)
at android.view.SurfaceView.access$000(SurfaceView.java:81)
at android.view.SurfaceView$3.onPreDraw(SurfaceView.java:169)
at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:590)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1644)
at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2505)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:4945)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
at dalvik.system.NativeStart.main(Native Method)


Any ideas on what else could be going wrong? Is
SurfaceHolder
potentially destroying the surface on a background thread and then waiting for the main thread (currently occupied by
surfaceCreated
) to finish it's block before it can call
surfaceDestroyed
on the main thread (which I don't even think locks can fix)? Something else?

Update -- After drilling down a little farther I found out what causes "The surface has been released" to be thrown:

Which references
android_view_Surface_getSurface
which can be found here:

This is where my lack of C++ knowledge hurts, it's looking like it's trying to lock onto the surface, and if it can't the surface returned will be
null
. Once it gets returned as
null
,
IllegalArgumentException
will be thrown.

Answer Source

I've just fighted a similar problem.

And my investigation shows, that there is a bug in the SurfaceView, which causes passing invalid surface to the surfaceCreated callback method.

This is the commit in the android repo that fixes it: link.

It seems that fix in android sources was introduced in 4.2 version. And, from crashes of application I see that crash caused by invalid surface occured only on 4.0 and 4.1.

So, I can assume that before 4.0 it was valid to pass invalid surface to the MediaPlayer. And there was change in logic of SurfaceView/MediaPlayer in 4.0 which caused this being valid no more. But code in SurfaceView was not updated before 4.2 (in which this problem in SurfaceView is fixed).

I have checked in git repo of android and really, version tagged android-4.0.1_r1 does not include fix and version tagged android-4.2.1_r1 includes it.

So, to fix it for platforms which doesn't contain fix for it, manual check if surface valid before setting it to the MediaPlayer is needed only for platforms 4.0 and after:

@Override public void surfaceCreated(final SurfaceHolder holder) {
    final Surface surface = holder.getSurface();

    if ( surface == null ) return;

    // on pre Ice Scream Sandwich (4.0) versions invalid surfaces seems to be accepted (or at least do not cause crash)
    final boolean invalidSurfaceAccepted = Build.VERSION.SDK_INT < Build.ICE_CREAM_SANDWICH;
    final boolean invalidSurface = ! surface.isValid();

    if ( invalidSurface && ( ! invalidSurfaceAccepted ) ) return;

    _mediaPlayer.setDisplay(holder);
}

In this way, on older platforms invalid surface will be successfully set to the media player and video will playback, on platforms 4.0-4.1 it will throw invalid surfaces away (and i think that surfaceCreated will be called again with valid surface) and on 4.2 and later surfaceCreated will just not be called with invalid surface.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download