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);
}
Related
In online tutorials or in the Android SDK samples of 2D games, there is usually a drawing function inside the game thread's run() method, which is responsible for drawing on the canvas. For instance, in the classical Lunar Lander code, we have the following code in class LunarThread:
#Override
public void run() {
while (mRun) {
Canvas c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
if (mMode == STATE_RUNNING) updatePhysics();
doDraw(c);
}
} finally {
// ...
}
}
}
private void doDraw(Canvas canvas) {
canvas.drawBitmap(mBackgroundImage, 0, 0, null);
// ...
}
The Android development guide, however, state very clearly that:
Do not access the Android UI toolkit from outside the UI thread.
I have two questions.
I am puzzled by the meaning of 'Android UI toolkit'. Since the canvas is locked by the SurfaceHolder, and the SurfaceHolder is holding a surface that can accept user inputs (such as touch events), is the canvas considered an 'Android UI toolkit'?
Are those tutorials or code samples violating the above rule? If so, why is such violation acceptable? If not, why not?
I am making a board game. The board doesn't ever move, but pieces on top of it sometimes do depending on user interaction. There are also UI elements which may update periodically.
Right now the way I set it up is by overwriting the onDraw() method of a SurfaceView subclass. I have a drawing thread that constantly calls postInvalidate() in a while loop:
class PanelThread extends Thread
{
//...
long sleepTime = 0;
long nextGameTick = System.currentTimeMillis();
#Override
public void run()
{
Canvas c;
while (_run)
{ // When setRunning(false) occurs, _run is
c = null; // set to false and loop ends, stopping thread
try
{
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder)
{
// Insert methods to modify positions of items in onDraw()
_panel.postInvalidate();
}
} finally
{
if (c != null)
{
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
nextGameTick += MILLISECONDS_PER_FRAME;
sleepTime = nextGameTick - System.currentTimeMillis();
if(sleepTime >= 0)
{
try
{
sleep(sleepTime, 0);
} catch (InterruptedException e)
{
continue;
}
}
else
{
//we're behind, oh well.
System.out.println("behind!");
nextGameTick = System.currentTimeMillis();
}
}
}
This is not efficient and is taking a lot of CPU. Is there a easy way to get android to only update when something changes?
You have the right idea, but it needs a bit of refinement.
You definitely do not want to loop as fast as the CPU can handle it though.
You should be sleeping your Thread in every loop for a little while. You most certainly do not need to do everything in your loop every millisecond.
I found this guide to FPS control to be incredible helpful in designing a game loop.
This Android-specific game loop guide also provides a lot of great sample code and an in-depth explanation.
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
I'm just picking up android development to make a game. Touched it before, but only picked up the basics. I'm a bit confused how to set up a main loop. I've been into XNA (C#) and I love the separated update/draw loop.
I was wondering how a typical android gameloop works? I've searched online and came across 2 methods:
public void run() {
while (running) {
//Method 1: update is called here
view.update();
Canvas c = null;
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
//Method 2: update is called inside view.onDraw
view.onDraw(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
}
}
Let's take the updating of game entities as an example for the 2 methods:
//METHOD1
public void onDraw(Canvas canvas)
{
for (GameEntity entity : entities)
{
entity.update();
entity.draw(canvas);
}
}
//END METHOD 1
//METHOD 2
public void update()
{
for (GameEntity entity : entities)
{
entity.update();
}
}
public void draw(Canvas canvas)
{
for (GameEntity entity : entities)
{
entity.draw(canvas);
}
}
//END METHOD 2
Now I have no experience with threads whatsoever, so I have no idea how XNA does the update/draw loops behind the screens in xna.
But using method 1, I would have to loop through all the entities twice, once for updating and another time for seperate drawing. I'm afraid this will kill the performance, but I háve seen this in samples online.
Am I missing something or am I right and is method 2 the best performance wise?
It matters not how many times you loop since it only matters how many actions you do. And the amount of actions done are basicly the same. since the second "for" only adds an one more supposed "if" for each entety. So its not much.
But it gives you the ability to do only one of the actions and not forced to do both.
for example: if I want the game to update 60 times per sec but only draw 40 fps, I can only do that in method 2. This allows you to have a more fluid game with less calculations, but only if you use it right.
If you have the darw and update happen at the same rate, then it is stupid to split them
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...