mapzen / android

Where you can find everything Android from Mapzen
http://mapzen.github.io/android/
Apache License 2.0
118 stars 66 forks source link

Crash: GLSurfaceView IllegalStateException: setRenderer has already been called for this instance #518

Open yanickv opened 6 years ago

yanickv commented 6 years ago

Description

GLSurfaceView IllegalStateException: setRenderer has already been called for this instance. Happens on line 211 of MapController.js (in call to view.setRenderer(this))

protected MapController(GLSurfaceView view) {
    // Set up MapView
    mapView = view;
    view.setRenderer(this);
    view.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    view.setPreserveEGLContextOnPause(true);

Here is the call stack:

06-06 22:33:19.824 16800-16800/AndroidRuntime: FATAL EXCEPTION: main java.lang.IllegalStateException: setRenderer has already been called for this instance. at android.opengl.GLSurfaceView.checkRenderThreadState(GLSurfaceView.java:1893) at android.opengl.GLSurfaceView.setRenderer(GLSurfaceView.java:348) at com.mapzen.tangram.MapController.(MapController.java:211) at com.mapzen.tangram.MapView.getMapInstance(MapView.java:49) at com.mapzen.tangram.MapView.getMap(MapView.java:41) at com.mapzen.android.graphics.MapReadyInitializer.onMapReady(MapReadyInitializer.java:29) at com.mapzen.android.graphics.MapInitializer$1.onSceneReady(MapInitializer.java:113) at com.mapzen.tangram.MapController$14.run(MapController.java:1355) at android.os.Handler.handleCallback(Handler.java:789) at android.os.Handler.dispatchMessage(Handler.java:98) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6940) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)

Steps to Reproduce

To reproduce you must rotate the phone 90 degree and rotate it back -90 degree quickly. It must be rotated back -90 degree so that your MainActivity onStop gets called before the onMapReady gets called.

Mapzen SDK & Android Version

Mapzen 1.6.1. Android 8 Oreo.

yanickv commented 6 years ago

Possibly because the onSceneReady that was posted on the UI thread gets callled after the com.mapzen.tangram.MapView disposeMap() method was called.

// Called from JNI on worker or render-thread.
void sceneReadyCallback(final int sceneId, final SceneError error) {

    final SceneLoadListener cb = sceneLoadListener;
    if (cb != null) {
        uiThreadHandler.post(new Runnable() {
            @Override
            public void run() {
                cb.onSceneReady(sceneId, error);
            }
        });
    }
}

The com.mapzen.tangram.MapView disposeMap() method sets the mapController to null but does not free the GLSurfaceView.

protected void disposeMap() {

    if (mapController != null) {
        // MapController has been initialized, so we'll dispose it now.
        mapController.dispose();
    }
    mapController = null;

}

Then when the MapView.getMap gets called the mapController is null so it creates a new MapController.

public MapController getMap(MapController.SceneLoadListener listener) {
    if (mapController != null) {
        return mapController;
    }
    mapController = getMapInstance();
    mapController.setSceneLoadListener(listener);
    mapController.init();

    return mapController;
}

protected MapController getMapInstance() {
    return new MapController(glSurfaceView);
}

And the MapController constructor calls view.setRenderer(this), but the view (which is the old undisposed GLSurfaceView) already has a renderer. Then setRenderer throws this IllegalStateException.