我们开发了一个Android的3D应用,界面部分使用Java,渲染部分使用了C++,但发布后,应用在前后台切换时总是再渲染功能上出现错误导致程序崩溃,经过几天的奋战,终于整清楚了。

Android在进行前后台切换时,如果程序中不显示调用View的onPause和onResume,那么系统不会自动调用其onPause和onResume,在我们的程序中,在activity的onPause和onResume分别调用view相应的函数。

查看GLSurfaceView的源码发现Android在进行前后台切换时,自动重新创建EGLSurface和EGLContext,即在suspend(onPause)时删除现有的对象,然后在resume时再创建新的对象,虽然有一个属性setPreserveEGLContextOnPause,但不能保证百分百好用,与具体手机有关,根据我们测试的情况是,如果setPreserveEGLContextOnPause(true),那么程序不会调用Renderer的onSurfaceCreated,但会发生EGL_CONTEXT_LOST,


从而导致EGLSurface和EGLContext被重建(虽然重建了,但仍然不会调用onSurfaceCreated),因此为了能满足所有情况,


还是setPreserveEGLContextOnPause(false)。



根据GLSurfaceView中的文档描述,渲染环境重建时,会自动删除OpenGL的相关资源,因此不需要我们手动调用释放资源的动作。



我们的应用要适应现有的情况,则必须:


1.在渲染环境删除前保存一些运行时资源,


2.然后在渲染环境重建后,第一时间恢复这些运行时资源,并把在前一个渲染环境中创建的OpenGL对象置为初始状态(只需要置状态,不需要调用OpenGL的删除资源函数,

注意:一定不能调用删除函数,否则后续渲染就会出现未知的效果错误,比如花屏,贴图缺失等奇异错误)



对于恢复的时机比较容易找到,那就是onSurfaceCreated,但保存运行资源的位置却没有明显的时机,首先调用保存动作,必须在渲染环境环境删除前,而且必须是在渲染线程中执行,我们初步确定了几个位置onPause,EGLWindowSurfaceFactory.destroySurface,EGLContextFactory.destroyContext,我们都试了,但居然不好使,始终提示设备环境无效,经过一番努力终于搞定,在onPause中调用,具体代码如下:


就是必须在GLSurfaceView的onPause之前调用自己的代码,在activity中也是类似,在super.onPause()前调用view的onPause, activity的onResume正好相反,在super.onResume之后调用view的onResume.

@Override 

 public void onPause() { 

        queueEvent(new Runnable() { 

                @Override 

                public void run() { 

                    //保存运行时资源 

 ... 

                } 

            }); 

     super.onPause(); 

    
 }



发布程序,终于好用了!



有个小插曲,根据我的经验一般重建渲染环境,需要重建窗口,但android在没有重建窗口的情况下居然也能重建渲染环境,让我感到很诧异,后来查看ndk的程序


ANativeActivity的callbacks有一个回调onNativeWindowCreated,在程序从后台切换到前台时会调用onNativeWindowCreated,说明符合之前的经验,


而我们在程序中保存了ANativeWindow对象的指针,因此在程序从后台切换到前台时,也需要重新获取ANativeWindow指针,否则会出错:


在我们程序中唯一使用ANativeWindow的地方就是取得窗口尺寸:


h = ANativeWindow_getHeight(win_handle_); 

w = ANativeWindow_getWidth(win_handle_);




如果不重新获取这个指针,w和h得到的都是负数,我们的程序前端接的GLSurfaceView,从GLSurfaceView中无法获得ANativeWindow指针,必须先在JAVA程序中


取得Surface对象:


Surface surf = GLSurfaceView的对象.getHolder().getSurface();


然后在C++中从Surface获取ANativeWindow指针:


ANativeWindow* window = ANativeWindow_fromSurface(env, surface);

这个程序切到前台时也必须刷新ANativeWindow指针,才能取到正确的窗口尺寸。