Issue
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.
Solution
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.
Answered By - Reaz Murshed
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.