1

I am have a little trouble understanding the AsyncTask and whether or not i can actually do what i am trying to do.

Within my onCreate method of my activity I am doing a few things, one of which is to create a GLSurfaceView and set my renderer. I want this process to be done by an AsyncTask in the background.

Here is my code

public class ActivityTest extends BaseGameActivity
{
/** Hold a reference to our GLSurfaceView */
private MYGLSurfaceView mGLSurfaceView;
public MYGamePlay GamePlay;
public GoogleApiClient mGoogleApiClient = null;
private AdView adView;
private static final String AD_UNIT_ID = "*******************************";
private Button RotateButton;
private CreateRenderer mCreateRenderer;
private TextView Score;
private RelativeLayout layout;

@Override
public void onCreate(Bundle savedInstanceState)
{       
    super.onCreate(savedInstanceState);
     Dialog loader_dialog = new Dialog(this,android.R.style.Theme_Black_NoTitleBar_Fullscreen);
        loader_dialog.setContentView(R.layout.loading_screen);
        loader_dialog.show();


    // Create an ad.
    adView = new AdView(this);
    adView.setAdSize(AdSize.SMART_BANNER);
    adView.setAdUnitId(AD_UNIT_ID);

    // create the layout that holds the combined game layout
    layout = new RelativeLayout(this);
    layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,    LayoutParams.MATCH_PARENT));

    // Create an ad request. Check logcat output for the hashed device ID to
    // get test ads on a physical device.
    AdRequest adRequest = new AdRequest.Builder()
    .addTestDevice("***********************").build();
    // Start loading the ad in the background.
    adView.loadAd(adRequest);


    Score = new TextView(this);
    Score.setText(" 0");
    RelativeLayout.LayoutParams scoreParams = new 
    RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 
    Score.setLayoutParams(scoreParams);

    Typeface tf = Typeface.createFromAsset(getAssets(),"Fonts/D3Euronism_b.ttf");
    Score.setTextSize(getResources().getDimension(R.dimen.textsize));
    Score.setTypeface(tf);
    Score.setTextColor(Color.parseColor("#FDAA03"));

    LayoutInflater inflater = (LayoutInflater) this.getSystemService(this.LAYOUT_INFLATER_SERVICE);
    View userInterface = inflater.inflate(R.layout.user_interface, null);

    RotateButton = (Button) userInterface.findViewById(R.id.rotbutton);

    RotateButton.setOnTouchListener(new OnTouchListener()
    {
        @Override
        public boolean onTouch(View v, MotionEvent event)
        {
           //irrelivant
        }
    });

    RelativeLayout.LayoutParams adParams = 
            new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, 
                RelativeLayout.LayoutParams.WRAP_CONTENT);
        adParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
        adParams.addRule(RelativeLayout.CENTER_HORIZONTAL);



    mCreateRenderer = new CreateRenderer(this, Score, mGLSurfaceView);
    mCreateRenderer.execute();

    layout.addView(userInterface);
    layout.addView(Score);
    layout.addView(adView, adParams);
    setContentView(layout); 

}   


@Override
protected void onResume() 
{
    // The activity must call the GL surface view's onResume() on activity onResume().
    super.onResume();
    //mGLSurfaceView.onResume();
}

@Override
protected void onPause()
{
    // The activity must call the GL surface view's onPause() on activity onPause().
    super.onPause();
    //mGLSurfaceView.onPause();
}

private class CreateRenderer extends AsyncTask<Void, Void, GLSurfaceView>
{

    Context baseClassContext;
    RBGLSurfaceView myGLSurfaceView;
    TextView GameScore;

    public CreateRenderer(Context mContext, TextView mScore, RBGLSurfaceView rGLSurfaceView )
    {
        baseClassContext = mContext;
        GameScore = mScore;
        myGLSurfaceView = rGLSurfaceView;
    }
    @Override
    protected GLSurfaceView doInBackground(Void... params) 
    {

        final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
        final boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000;

        if (supportsEs2)
        {               
            // Request an OpenGL ES 2.0 compatible context.
    /*line 226*/        myGLSurfaceView = new MYGLSurfaceView(baseClassContext); new GLSurfaceView(baseClassContext);
            myGLSurfaceView.setEGLContextClientVersion(2);          
            final DisplayMetrics displayMetrics = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);          
            // Set the renderer to our demo renderer, defined below.
            mGoogleApiClient = getApiClient();

            layout.addView(myGLSurfaceView);

            myGLSurfaceView.setRenderer(new MYRenderer(baseClassContext, GameScore,  mGoogleApiClient),displayMetrics);

        }
        else
        {
            // This is where you could create an OpenGL ES 1.x compatible
            // renderer if you wanted to support both ES 1 and ES 2.

        }
        return myGLSurfaceView;
    }

    @Override
    protected void onPostExecute(GLSurfaceView result) 
    {

    }

    @Override
    protected void onPreExecute() {}

    @Override
    protected void onProgressUpdate(Void... values) {}
}

}

I have been reading conflicting things about AsyncTask like whether i can create a contstructor and pass things to it.

Any way it fails on line 226 which i have marked in the code above.

Here is the logCat

09-05 09:29:29.554: E/AndroidRuntime(7585): FATAL EXCEPTION: AsyncTask #2
09-05 09:29:29.554: E/AndroidRuntime(7585): java.lang.RuntimeException: An error occured   while executing doInBackground()
09-05 09:29:29.554: E/AndroidRuntime(7585):     at  android.os.AsyncTask$3.done(AsyncTask.java:299)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:352)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at java.util.concurrent.FutureTask.setException(FutureTask.java:219)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at java.util.concurrent.FutureTask.run(FutureTask.java:239)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at java.lang.Thread.run(Thread.java:856)
09-05 09:29:29.554: E/AndroidRuntime(7585): Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
09-05 09:29:29.554: E/AndroidRuntime(7585):     at android.os.Handler.<init>(Handler.java:197)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at android.os.Handler.<init>(Handler.java:111)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at android.view.SurfaceView$1.<init>(SurfaceView.java:122)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at android.view.SurfaceView.<init>(SurfaceView.java:122)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at android.opengl.GLSurfaceView.<init>(GLSurfaceView.java:213)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at com.game.cuberush.MYGLSurfaceView.<init>(MYGLSurfaceView.java:34)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at com.game.test.ActivityTest$CreateRenderer.doInBackground(ActivityTest.java:226)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at com.game.test.ActivityTest$CreateRenderer.doInBackground(ActivityTest.java:1)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at  android.os.AsyncTask$2.call(AsyncTask.java:287)
09-05 09:29:29.554: E/AndroidRuntime(7585):     at java.util.concurrent.FutureTask.run(FutureTask.java:234)
09-05 09:29:29.554: E/AndroidRuntime(7585):     ... 4 more

this is the first time i have tried to use AsyncTask so i realise i am probably making a mess with it. So any help with this would be great

EDIT method using annotations still produces the same result the code a put the @Background annotation doesnt seem to be running on a seperate thread as the oncreate is still waiting for it, unless i am doing it wrong

called un onCreate

CreateRender(mGLSurfaceView, this, Score, loader_dialog, mGoogleApiClient, displayMetrics);

method CreateRender

@Background
protected void CreateRender(MYGLSurfaceView myGLSurfaceView, Context mContext, TextView   mScore, Dialog mDialog, GoogleApiClient gAPI, DisplayMetrics mDispMet  )
{
    myGLSurfaceView.setRenderer(new MYRenderer(mContext, mScore, mDialog, gAPI),mDispMet);
    mGLSurfaceView = returnResult(myGLSurfaceView);
}
@UiThread
MYGLSurfaceView returnResult(MYGLSurfaceView res)
{
    return res;
}         

this must be wrong

Rob85
  • 1,719
  • 1
  • 23
  • 46

3 Answers3

4

If you get crazy using AsyncTask i prefer to try AndroidAnnotations. Instead of writing a complete class to handle your background tasks, you just use a @Background annotation over your method. This concept saves a lot of code and it works!

This is an example from the android annotations wiki at github:

@EActivity(R.layout.translate) // Sets content view to R.layout.translate
public class TranslateActivity extends Activity {

    @ViewById // Injects R.id.textInput
    EditText textInput;

    @ViewById(R.id.myTextView) // Injects R.id.myTextView
    TextView result;

    @AnimationRes // Injects android.R.anim.fade_in
    Animation fadeIn;

    @Click // When R.id.doTranslate button is clicked 
    void doTranslate() {
         translateInBackground(textInput.getText().toString());
    }

    @Background // Executed in a background thread
    void translateInBackground(String textToTranslate) {
         String translatedText = callGoogleTranslate(textToTranslate);
         showResult(translatedText);
    }

    @UiThread // Executed in the ui thread
    void showResult(String translatedText) {
         result.setText(translatedText);
         result.startAnimation(fadeIn);
    }

    // [...]
}
Bobbelinio
  • 134
  • 1
  • 8
  • can you give a quick example of this – Rob85 Sep 05 '14 at 09:27
  • is there an import for all the above annotations? – Rob85 Sep 05 '14 at 11:13
  • 1
    Ok I have managed to add androidannotations and will try this method out now, sounds like it will help me out though, i will get back to you once i have tested Cheers – Rob85 Sep 05 '14 at 12:24
  • i have tried to implement as you said but i must be doing something wrong as is the same as before as if its not running on a seperate thread, i have updated with the code i did can you have a look – Rob85 Sep 05 '14 at 14:03
  • you include the lib and the project complied, right? did you got an exception? if so, please show us this one and also show us your method(s). – Bobbelinio Sep 08 '14 at 06:03
  • I have since been told that only Background task and process can be run using @Background so trying to create a surfaceView and renderer is a big no no as it is UI related, i have actually fixed my problem, i will do a short write up soon. Thanks – Rob85 Sep 08 '14 at 07:35
1

Ive been wrestling with AsyncTask the last couple of days myself. It seems to have some kind of an aversion to surfaces.

I would suggest a change of approach -- create the view in onCreate/onResume ( depending on your needs ) and avoid tying in nonexistent surfaces with AsyncTask.

This approach worked for me, hope it does for you as well!

Sipty
  • 1,159
  • 1
  • 10
  • 18
  • the reason i want to avoid this is that creating the surface and setting the renderer takes about 3 to 4 seconds in the onCreate, in the above code you can see i create a dialog at the top which is like my little loading screen (just an image) but this dialog doesnt get displayed until oncreate has finished doing something with the SurfaceView ( get a black screen for a few seconds) if i remove my surface view creation the dialog displays immediately, hence why i want this moved to to background task – Rob85 Sep 05 '14 at 09:11
  • Why not handle the loading screen on create and launch the AsyncTask on resume? If this is the very beginning of the app, a couple of seconds of delay is tolerable -- as long as the device doesn't complain that you're taking up the UI thread. You've probably thought of this already, but what about tracing down the stacktrace? I saw a couple of errors there, which point to a Handler, which I couldn't see -- maybe that's the real culprit? – Sipty Sep 05 '14 at 10:27
1

Your basic problem is that you try to change the UI from a non-ui thread. From the documentation:

Additionally, the Andoid UI toolkit is not thread-safe. So, you must not manipulate your UI from a worker thread—you must do all manipulation to your user interface from the UI thread.

Asynctask is actually a convience for that. It does some background work off the main thread and then runs the onPostExecute() on the ui-thread. That means 2 things: first you can change the ui in onPostExecute() and everything done here will be executed serial to the ui (so it could be blocked)

There is the usual quickfix for that with

activity.runOnUiThread(new Runnable() {
  public void run() {
    //this will be executed on the ui thread
  }
});

which will not help you since it will then block the ui again. Im actually not sure if you can do what you want, since some things just can be done in the background (e.g. inflating a real complex xml in onCreateView()). You could try to create your own looper like:

Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.

class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare();

        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };
        Looper.loop();
    }
}

and handle your surfaceview there, but Im not sure that works.

Further resouces:

Community
  • 1
  • 1
Patrick
  • 33,984
  • 10
  • 106
  • 126
  • Thanks for your answer, seems very indepth, what about if i come at this from a different angle, i personally dont want to run the surfaceview on a seperate thread, leaving it on the UI thread works great for me, so can i run the dialog on a seperate thread, the whole point of me doing this is to hide the 3-4 seconds of black screen while the setrender method runs. So, as soon as i enter oncreate if i create a background thread that runs the dialog will this work? will the dialog display immediately? – Rob85 Sep 05 '14 at 15:20