1

I created a file called JoyStickView which extends SurfaceView. In my JoyStickView.java file, I have the following function which calls the AsyncTask to draw two joysticks:

private void drawJoystick(float hatX, float hatY) {
    // Only draw the joystick when SurfaceView has been created
    if (getHolder().getSurface().isValid()) {
        new MultiplyTask().execute();
    }
}

And the inner class AsyncTask is shown below:

 public class MultiplyTask extends AsyncTask<Void, Bitmap, Bitmap[]> {

        @Override
        protected void onPreExecute() {
            //super.onPreExecute();
        }

        @Override
        protected Bitmap[] doInBackground(Void... progress_data) {

            Bitmap[] bitmapArray = new Bitmap[2];
            bitmapArray[0] = BitmapFactory.decodeResource(getResources(), R.drawable.joystick_base);
            bitmapArray[1] = BitmapFactory.decodeResource(getResources(), R.drawable.joystick_hat);
            //publishProgress(bitmapArray);
            return bitmapArray;
        }

        @Override
        protected void onProgressUpdate(Bitmap... bitmapArray) {
            //super.onProgressUpdate(values);\
        }

        @Override
        protected void onPostExecute(Bitmap[] bitmapArray) {
            // super.onPostExecute(s);
            myCanvas = getHolder().lockCanvas();
            myCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            switch (getId()) {
                case R.id.joystickRight:
                    // draw_joystick_base(myCanvas, R.drawable.joystick_base);

                    float c = bitmapArray[0].getWidth() / 2;
                    float d = bitmapArray[0].getHeight() / 2;
                    myCanvas.drawBitmap(bitmapArray[0], centerX - c, centerY - d, new Paint());
                    break;
                case R.id.joystickLeft:
                    // draw_joystick_base(myCanvas, R.drawable.joystick_base);

                    float c1 = bitmapArray[0].getWidth() / 2;
                    float d1 = bitmapArray[0].getHeight() / 2;
                    myCanvas.drawBitmap(bitmapArray[0], centerX - c1, centerY - d1, new Paint());
                    break;

            }

            float a = bitmapArray[1].getWidth() / 2;
            float b = bitmapArray[1].getHeight() / 2;
            myCanvas.drawBitmap(bitmapArray[1], hatX_tmp - a, hatY_tmp - b, new Paint());
            getHolder().unlockCanvasAndPost(myCanvas);
            // Do things like update the progress bar
        }

        void stopTask() {
            MultiplyTask a = new MultiplyTask();
            a.cancel(true);
        }
    }

Everything works great until I hit the back button, and the app just crashes and gave me the following null pointer error.

  06-03 15:06:15.505 4478-4478/com.example.android.toybot E/AndroidRuntime: FATAL EXCEPTION: main
        Process: com.example.android.toybot, PID: 4478
        java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Canvas.drawColor(int, android.graphics.PorterDuff$Mode)' on a null object reference
            at com.example.android.toybot.JoyStickView$MultiplyTask.onPostExecute(JoyStickView.java:304)
            at com.example.android.toybot.JoyStickView$MultiplyTask.onPostExecute(JoyStickView.java:274)
            at android.os.AsyncTask.finish(AsyncTask.java:661)
            at android.os.AsyncTask.-wrap1(AsyncTask.java)
            at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:678)
            at android.os.Handler.dispatchMessage(Handler.java:111)
            at android.os.Looper.loop(Looper.java:207)
            at android.app.ActivityThread.main(ActivityThread.java:5688)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:888)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:749)

JoyStickView.java:304 is this line: myCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); I thought of cancelling my AsyncTask during onstop() but this does not seem to work. Anyone have any advice on what I can do?

Update: Following this post, I was able to follow the LunarLander example and implement a new Runnable() to achieve what I needed.

Community
  • 1
  • 1
frodyteen
  • 19
  • 6
  • What does `getHolder().lockCanvas()` do? Can you show it? Your problem is there – OneCricketeer Jun 03 '18 at 22:29
  • it provides an area so that I can draw on it. I didn't implement the function, it came from SurfaceView. I found this link to be helpful for me to understand it better https://stackoverflow.com/questions/3322144/what-does-lockcanvas-mean-elaborate @cricket_007 – frodyteen Jun 04 '18 at 04:00

1 Answers1

0

You are accessing a UI element from a separate thread which is not recommended. The NullPointerException is pretty straightforward. myCanvas = getHolder().lockCanvas(); is setting a null reference to the myCanvas attribute when you are finishing the activity using the back button press.

If the code was inside your UI thread (i.e. inside any function which is in your activity life-cycle), this could have handled by the activity life-cycle functions like onStop or onDestroy. However, you are trying to change a UI element (myCanvas in your case) from a separate thread other than UI thread which is out of the Activity resource management scope. Hence the getHolder().lockCanvas() is getting a null reference for the canvas and you are getting the NullPointerException on drawing.

This can be easily avoided by implementing an interface as a listener to your AsyncTask, so that the listener function resides in your Activity and handles the resources properly. For example, you might consider creating an interface like this.

public interface MultiplyResponseReceiver {
    void onMultiplyResponseReceived(Bitmap[] bitmapArray);
}

Now take an attribute of this interface in your AsyncTask like this.

public class MultiplyTask extends AsyncTask<Void, Bitmap, Bitmap[]> {

    public MultiplyResponseReceiver listener;

    @Override
    protected void onPreExecute() {
        //super.onPreExecute();

    }

    @Override
    protected Bitmap[] doInBackground(Void... progress_data) {

        Bitmap[] bitmapArray = new Bitmap[2];
        bitmapArray[0] = BitmapFactory.decodeResource(getResources(), R.drawable.joystick_base);
        bitmapArray[1] = BitmapFactory.decodeResource(getResources(), R.drawable.joystick_hat);
        //publishProgress(bitmapArray);
        return bitmapArray;
    }

    @Override
    protected void onProgressUpdate(Bitmap... bitmapArray) {
        //super.onProgressUpdate(values);\


    }

    @Override
    protected void onPostExecute(Bitmap[] bitmapArray) {
        // super.onPostExecute(s);
        listener.onMultiplyResponseReceived(bitmapArray);
    }
}

Now implement the interface in your Activity like this.

public class YourActivity extends AppCompatActivity implements MultiplyResponseReceiver {

    @Override
    public void onMultiplyResponseReceived() {

        myCanvas = getHolder().lockCanvas();
        myCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        switch (getId()) {
            case R.id.joystickRight:
                // draw_joystick_base(myCanvas, R.drawable.joystick_base);

                float c = bitmapArray[0].getWidth() / 2;
                float d = bitmapArray[0].getHeight() / 2;
                myCanvas.drawBitmap(bitmapArray[0], centerX - c, centerY - d, new Paint());
                break;
            case R.id.joystickLeft:
                // draw_joystick_base(myCanvas, R.drawable.joystick_base);

                float c1 = bitmapArray[0].getWidth() / 2;
                float d1 = bitmapArray[0].getHeight() / 2;
                myCanvas.drawBitmap(bitmapArray[0], centerX - c1, centerY - d1, new Paint());
                break;
        }

        float a = bitmapArray[1].getWidth() / 2;
        float b = bitmapArray[1].getHeight() / 2;
        myCanvas.drawBitmap(bitmapArray[1], hatX_tmp - a, hatY_tmp - b, new Paint());
        getHolder().unlockCanvasAndPost(myCanvas);
        // Do things like update the progress bar
    }
}

And while launching the AsyncTask in your activity, do not forget to assign the listener to it.

MultiplyTask task = new MultiplyTask();
task.listener = this; 
task.execute();

The code is not tested and might contain errors. Please modify as per your necessity. I think you have got the idea already.

Reaz Murshed
  • 23,691
  • 13
  • 78
  • 98
  • thank you for your help, your explanation make things a lot more clearer. I am trying to implement onMultiplyResponseReceived() in my JoyStickActivity.java, I am unable to use getHolder in JoystickActivity.java. I tried to pass the variable down from JoyStickView.java, but it gave me a null pointer. Do you have any suggestions? – frodyteen Jun 04 '18 at 04:27
  • I think `getHolder` method should be called like `surfaceView.getHolder()` – Reaz Murshed Jun 04 '18 at 04:35
  • thank you, would I still keep my drawJoystick() function in my JoyStickView.java? – frodyteen Jun 04 '18 at 05:02
  • Sorry, I am still debugging the code, it seems to still have null pointer error. Can you tell me the difference between `new MultiplyTask().execute();` in my `drawJoystick() ` and the `task.execute();`? Thank you. – frodyteen Jun 04 '18 at 21:48
  • I wasn't able to implement an interface in the asynctask, but I was able to use a runnable thread to do the same thing that I wanted to do. but your answer definitely help me understand the problem I was having. – frodyteen Jun 05 '18 at 20:04
  • Great to know you have solved the problem and I could help you. My pleasure. :) – Reaz Murshed Jun 06 '18 at 05:09