Unexpected NullPointerException in SurfaceView-Thread on API-level 15 - android

I have programmed an App which I wanted to test on higher API-Levels for checking the compatibility. For API 10 (2.3.3) there were no problems, but as soon as I ran my app on API 15 (4.0.3) I got an NullPointerException in one of my SurfaceViews when I was quitting the Activity.
I have to say that i solved the problem, but i can't figure out why the Exception occured actually. So maybe you could tell me.
Here is the code that worked for me on API 10:
It's the common structure of the run()-method.
public void run() {
while (mThreadActive) {
c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
if(mState == 1) {
updateValues();
updateAnimation();
}
doDraw(c);
}
} finally {
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
On API 15 when quitting the activity:
The Exception accoured when the doDraw()-method tried to write on "c". I checked c and found out it was null, so no surprise I got an Exception. I also checked mThreadActive and found out that although i set it to false, the while-loop still triggers.
Here is the Code sample:
public void run() {
while (mThreadActive) {
c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
if(mState == 1) {
updateValues();
updateAnimation();
}
if(!mThreadActive) // so it really is!
Log.d("Thread", "mThreadActive is false!");
if(c == null) // so it is too!
Log.d("Thread", "c is null!");
doDraw(c); // error
}
} finally {
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
I can imagine why mThreadActive becomes false AFTER being checked by the while-statement, but I can't figure out why "c" is null after mSurfaceHolder.lockCanvas(null). It seems that the code is not running sequential.
Well, the solution would be checking c != null before drawing on it:
public void run() {
while (mThreadActive) {
c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
if(mState == 1) {
updateValues();
updateAnimation();
}
if(c != null) // prevent drawing on c if c doesnt exist.
doDraw(c);
}
} finally {
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
So why do i get an Exception on API 15 whereas it works fine on API 10?
Another funny thing is, that i have other SurfaceViews with the same structure, but in comparison to this one, they work all fine!
Why is the code not running sequential? Is it because I am testing on an emulator (which is pretty laggy)?
Thank You.

You mention that your while() loop seems to proceed despite mThreadActive being false. Is mThreadActive marked volatile? It may need to be.
Also, lockCanvas(null) smells. Since we can't see the rest of your code, it's not exactly clear what you're trying to do. What does the API say about passing a null in to lockCanvas? (And are we talking about a raw instance of SurfaceHolder or a subclass?)
Note that the API spec for SurfaceHolder.lockCanvas() indicates it can return null:
A null is returned if the surface has not been created or otherwise cannot be edited. You will usually need to implement Callback.surfaceCreated to find out when the Surface is available for use.
From reading the API, it looks like you should be implementing the SurfaceHolder.Callback interface and responding to the surfaceCreated() event, which is really the event that tells you you're ready to go in writing to the canvas.

I also get a very similar issue when calling:
public void surfaceDestroyed(SurfaceHolder holder) {
isAttached = false;
this.drawthread = null;
}
When exiting my application to stop the drawing thread - the while boolean I use isAttached is false, but the drawing code within that while loop still executes - giving a nullexception on exit. Not a show stopper but something I really want to fix. Was considering a if (canvas != null) sort of solution too but I thought there must be a better way.
Now here's the weird thing with this error - it only happens some of the time - and happens less on faster devices. The problem the way I see it is that holder.lockCanvas() is returning null - meaning the holder is being destroyed before the boolean is set to false and the while has a chance to stop executing the Thread. A thread race problem?
Found a possible solution that used Thread.join() before making the while boolean null, but with holder.lockCanvas() returning null, the holder is being destroyed before the drawing is finished, so it's kind of pointless and still caused a nullexception.
Only other solution I can think of is to override the back button method and force the while bool to null before destroying the surfaceview(if that's poss), but I think if (canvas != null) is probably cleaner. Any other ideas out there keen to hear!
edit: haven't tested elsewhere, but I get the error using API 17

Related

SurfaceHolder.lockCanvas() Returns null Even Though it Was Available

I have an android game on the market and I've been getting crash reports of NullPointers when trying to use the canvas. I can assume it's because SurfaceHolder.lockCanvas() is returning null, however it does this mid gameplay because based on where it crashes, SurfaceHolder.lockCanvas() returned a valid canvas at least once.
This is difficult to debug because I can't re-create it on my own device, which makes me wonder if it has to do with specific devices. The only hint I have is that one of the devices it occurred on was a Nexus 7.
NOTE: This is not the same problem as the similar-named question by me. The other question was due to trying to use the canvas before it was available, whereas here it was available.
Below is a sample of my code:
public class GameView extends SurfaceView implements SurfaceHolder.Callback
{
class GameThread extends Thread
{
#Override
public void run()
{
while (running)
{
Canvas c = null;
try
{
c = mSurfaceHolder.lockCanvas();
synchronized (mSurfaceHolder)
{
long start = System.currentTimeMillis();
doDraw(c);
long diff = System.currentTimeMillis() - start;
if (diff < frameRate)
Thread.sleep(frameRate - diff);
}
} catch (InterruptedException e)
{
}
finally
{
if (c != null)
{
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
public void surfaceCreated(SurfaceHolder holder)
{
if (gThread.getState() == Thread.State.TERMINATED)
{
gThread = new GameThread(getHolder(), getContext(), getHandler());
gThread.start();
}
else
{
gThread.start();
}
}
}
I also faced that problem. It happens sporadically when the surface is already destroyed (and surfaceDestroyed was already called), but the looper thread is already inside the while loop (and after checking the running variable). Then lockCanvas returns null (as the surface was destroyed). This may happen for example on screen orientation changes or activity changes. It is a "simple" race condition threading problem.
A simple fix is to add if (canvas == null) continue; after lockCanvas (or just break the loop). Then running will be checked again and the loop ends.

Surface view drawing thread - busy loop?

All examples of the use of a SurfaceView seems to use a run method that performs a busy loop. Is that a valid way to do this? All the code I can see follows this paradigm from the lunar lander sample. However, creating a busy while loop seems to be a strange way to code multi threaded apps. Shouldnt the drawing code wait on a queue of drawing commands, or something similar. I would have implemented it that way, but the amount of code that I see that does is like below makes me ask the question... What is the best semantics for a thread drawing on a SurfaceView.
public void run() {
while (mRun) {
Canvas c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
// DO DRAWING HERE
}
} finally {
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
I don't know what is best practice in this case, but I have successfully used a slightly modified version of that example in my apps. Since I respond to touch input (rather than continuously updating the canvas) I added a flag to test if drawing even needs to be done. I also added a sleep after each refresh to limit system load. This is my code inside of the try block:
if(mPanel.needsRefresh()) {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
mPanel.onDraw(c);
}
} else {
SystemClock.sleep(10);
}

Android how to properly finish an activity

I am making an android game using the SurfaceView/Thread model seen in many of the examples. I am trying to split the components into multiple activities. Right now I have a menu activity and a game activity. For some reason when I attempt to finish the game activity to return to the menu activity my app will become unresponsive. It seems to work fine on the emulator but on my device (Samsung Mesmerize) it will become unresponsive. I have a feeling this is because a thread is still busy in the game activity but I have no idea how to kill the thread. I tried breaking the game loop as well and that did not seem to help.
My SurfaceView has an inner thread class, here is the implementation of run()
public void run()
{
_isRunning = true;
Canvas c = null;
while(_isRunning)
{
try
{
c = _surfaceHolder.lockCanvas();
synchronized(_surfaceHolder)
{
if(_engine != null)
{
_engine.gameLogic();
if(c != null)
_engine.draw(c);
}
}
}
finally
{
if(c != null)
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
I found the solution to my problem after an hour or two of debugging. I had to keep track of whether the surface was alive before calling lockCanvas(). I used this same loop in both activities and calling lockCanvas() after the surface from the game activity was destroyed was causing problems.

Android LunarLander example does not seem to use 'invalidate'

I am studying the LunarLander example in the Android sample code: http://developer.android.com/resources/samples/LunarLander/index.html
I am puzzled because the comments say in several places that the code uses 'invalidate' to trigger redrawing. But I can't find it in the code.
More importantly I believe that drawing should always happen in a View's onDraw and not inline elsewhere in a thread.
Has anyone studied that example and have comments about why invalidate() is not being called?
Thanks for sharing your insights!
-- Pito
It isn't inlined in a Thread but it is called from a Thread.
#Override
public void run() {
while (mRun) {
Canvas c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
if (mMode == STATE_RUNNING) updatePhysics();
doDraw(c);
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
The drawing itself should always be called from a Thread when you do 2D graphics...

Android: Thread pausing intermittently

having some trouble with a Thread (CanvasThread) that is intermittently pausing at random points within my application. Everything else in the app continues to function as necessary, it's simply this thread that randomly blocks out for some reason and doesn't draw anything new to the screen. I noticed that Surface.lockCanvasNative() seems to be the last function called before the block, and the first one returned after. In a pattern as such:
Surface.lockCanvasNative (Landroid/graphics/Rect)Landroid/graphics/Canvas; # 26,560 msec ____
Surface.lockCanvasNative (Landroid/graphics/Rect)Landroid/graphics/Canvas; # 40,471 msec ____|
Surface.lockCanvasNative (Landroid/graphics/Rect)Landroid/graphics/Canvas; # 40,629 msec ____
Surface.lockCanvasNative (Landroid/graphics/Rect)Landroid/graphics/Canvas; # 54,516 msec ____|
This is evident with the traceview below:
I have been using the CanvasThread.run() below if it helps:
#Override
public void run() {
boolean tellRendererSurfaceChanged = true;
/*
* This is our main activity thread's loop, we go until
* asked to quit.
*/
while (!mDone) {
/*
* Update the asynchronous state (window size)
*/
int w;
int h;
synchronized (this) {
// If the user has set a runnable to run in this thread,
// execute it and record the amount of time it takes to
// run.
if (mEvent != null) {
mEvent.run();
}
if(needToWait()) {
while (needToWait()) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
if (mDone) {
break;
}
tellRendererSurfaceChanged = mSizeChanged;
w = mWidth;
h = mHeight;
mSizeChanged = false;
}
if (tellRendererSurfaceChanged) {
mRenderer.sizeChanged(w, h);
tellRendererSurfaceChanged = false;
}
if ((w > 0) && (h > 0)) {
// Get ready to draw.
// We record both lockCanvas() and unlockCanvasAndPost()
// as part of "page flip" time because either may block
// until the previous frame is complete.
Canvas canvas = mSurfaceHolder.lockCanvas();
if (canvas != null) {
// Draw a frame!
mRenderer.drawFrame(canvas);
mSurfaceHolder.unlockCanvasAndPost(canvas);
//CanvasTestActivity._isAsyncGoTime = true;
}
else{
Log.v("CanvasSurfaceView.CanvasThread", "canvas == null");
}
}
}
}
Just let me know if I can provide any other useful information. I'm simply looking for clues as to why my thread might be blocking at this point? Thanks for any help in advance!
I've since narrowed the block down to mSurfaceHolder.unlockCanvasAndPost(canvas); I inserted a log before and after this call and the one after is not logged after app is frozen; but the log before is last logged event on this thread. It's not pausing or using a null canvas either, because I threw in logs for those instances as well; which are not logged even once until app is done.
I'm not sure if this could be the reason, but under SurfaceHolder.lockCanvas(), it warns that,
If you call this repeatedly when the
Surface is not ready (before
Callback.surfaceCreated or after
Callback.surfaceDestroyed), your calls
will be throttled to a slow rate in
order to avoid consuming CPU.
If null is not returned, this function
internally holds a lock until the
corresponding
unlockCanvasAndPost(Canvas) call,
preventing SurfaceView from creating,
destroying, or modifying the surface
while it is being drawn. This can be
more convenient than accessing the
Surface directly, as you do not need
to do special synchronization with a
drawing thread in
Callback.surfaceDestroyed.
I'm not sure what the threshold is when the CPU starts throttling. How many threads are refreshing the canvas?
btw,
if(needToWait()) {
while (needToWait()) {
is redundant
I have since figured out my problem. I'm not sure why but because I had accidentally forgot to fully comment out an earlier asyncTask(), thus had two doing roughly the same tasks and obviously struggling to do so with the same variables and such. Thanks for your pointers, but simply another careless mistake on my part I guess.

Categories

Resources