0
\$\begingroup\$

Overview

My splash screen starts by display a 'loading' dialogue and then kicks off an asyncTask (The loading dialogue is derived from the standard Android View class)

Within the doInBackground method of the asyncTask, all of my quads / sprite objects are created and other values set etc.

When the code gets to onSurfaceCreated, it loads all of the textures and runs the game.

The texture loading happens in onSurfaceCreated because it has to be done on the GL Thread.

Now, on a nice fast tablet, it all works great and it's all very stable. However, when I run it on an old handset, it (intermittently) crashes all over the place with nullpointerexceptions.

Reason

What's happening is on the slower device, the asyncTask is running and (due to the asynchronous nature of it), continues to run in the background while the main code moves on to the onSurfaceCreate method of the GLRenderer, there it attempts to apply textures to objects that don't yet exist because the asyncTask is still doing it's business.

How best to proceed?

What are some methods I can implement that would allow me to guarantee that the async has done it's stuff before attempting to affect any of the objects that it creates?

Is there a way to 'halt' the GLThread until the asyncTask has finished? I've been told in the past to use a separate thread or an async for loading resources, but I'm confused as to how to handle it correctly and keep everything sync'd between it and the GL thread (if "sync'd" is the correct term in this context).

Any help would be appreciated.

Code example

Showing onCreate and doInBackground from Activity Class

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        //Request full screen
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
        WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

        super.onCreate(savedInstanceState);

        //Create a displayMetrics object to get pixel width and height
        metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        width = metrics.widthPixels;
        height = metrics.heightPixels;

        //Create splash-screen object and pass in scaled width and height
        splash = new SplashScreen(MainActivity.this, width, height);

        //Create dialog that will show splash-screen 
        loading_dialog = new Dialog(MainActivity.this,android.R.style.Theme_Black_NoTitleBar_Fullscreen);

        //Create and set View
        myView = new MyGLSurfaceView(MainActivity.this);

        //Create a copy of the Bundle
        if (savedInstanceState != null){
            newBundle = new Bundle(savedInstanceState);         
        }

        //Create splash object

        goSplash = new DisplaySplash(newBundle);
        goSplash.execute(); //Start asyncTask

        setContentView(layout);

    }

And....

    @Override
    protected Void doInBackground(Void... params) {
        createObjects();
        initialise();           
        return null;
    }

Showing onSurfaceCreated from GLRenderer Class

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {

    //Eek! Running before AsyncTask has finished on slower devices! :(
    res.loadResources();
    res.setTextures();
    res.RecycleBitmaps();

}
\$\endgroup\$
1
\$\begingroup\$

You could wait for the asyncTask to finish by waiting for it in onSurfaceCreated using the get() method. This will be a blocking call though, so you might as well do your loading directly in onSurfaceCreated and manually update the progress display after each item is loaded.

But in case you want to keep using asyncTask it will go something like this:

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {

   goSplash.get();    // waits indefinitely for asyncTask to finish 

   // at this point, the asyncTask should be finished, unless an exception
   // was thrown - which you should probably handle :)

   res.loadResources();
   res.setTextures();
   res.RecycleBitmaps();
}

You could also use the alternate get() method which allows you to set a timeout for the wait operation, but since you have to wait for it to finish it wouldn't serve much purpose.

\$\endgroup\$
  • \$\begingroup\$ Thanks @free3dom I will give this a go. I wasn't aware of the get() method. I don't want to put everything in onSurfaceCreated because if I do that means everything will be created again if the GL Context is lost which is pointless as it's only the GL stuff that's lost (ie textures). I'll give this a go thanks! Not really too sure of the point of using a separate thread though to load resources when the app could start before they've finished loading! PS what is the alternate get()? It links to the same one. Cheers!! \$\endgroup\$ – BungleBonce Jun 23 '14 at 10:46
  • \$\begingroup\$ The alternate get() allows you to specify a timeout value, after which it will resume - even if the async has not completed yet. It throws an exception to inform you of that, so you can use it to wait in intervals (and maybe do some other stuff in the meantime). As for doing the loading in onSurfaceCreated, you could use a status flag to only load fully at startup, then set the flag and on all subsequent calls you could then just reload the required stuff. \$\endgroup\$ – free3dom Jun 23 '14 at 10:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.