I am new to Android. I'm trying to develop a game app using the surface view. The app runs properly but at the end when I exit it shows the null pointer exception on onDraw() method and the app gets crashed. Please help me out and thanks in advance.
Here is my code:-
public class Gameloopthread extends Thread {
private Gameview view;
static final long fps=30;
boolean running;
public Gameloopthread(Gameview view)
{
this.view=view;
}
// we have to tell thread to shut down & wait for it to finish, or else
// it might touch the Surface after we return and explode
public void setRunning(Boolean run)
{
running=true;
}
#Override
public void run() {
long tickps=3000/fps;
long startTime=0;
long sleepTime;
while(running)
{
Canvas c=null;
try{
c=view.getHolder().lockCanvas();
//Log.d("Canvas==>",""+c);
synchronized(view.getHolder()){
view.onDraw(c);
}
}finally{
if(c!=null){
view.getHolder().unlockCanvasAndPost(c);
}
}
sleepTime=tickps-(System.currentTimeMillis()-startTime);
try{
if(sleepTime>0)
{
sleep(sleepTime);
}else{
sleep(30);
}
}catch(Exception e){
}
}
super.run();
}
//
}
You should take a look at the lockCanvas call, the surface has probably been destroyed and you have passed a null canvas reference to view.onDraw(). There's some recommendations in the API on how to deal with surface state.
http://developer.android.com/reference/android/view/SurfaceHolder.html#lockCanvas()
Related
Have an activity with SurfaceView and a thread to paint on it (from this example). Here is GameSurface's finalization code:
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
while(retry) {
try {
this.gameThread.setRunning(false);
this.gameThread.join();
}catch(InterruptedException e) {
e.printStackTrace();
}
retry = true;
}
}
(don't really understand the logic of having retry always true, layed a bit with it but got no positive effect)
Here is the game thread:
package org.mineprogramming.laserminigolf;
import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
import java.util.logging.Logger;
public class GameThread extends Thread {
private boolean running;
private GameSurface gameSurface;
private SurfaceHolder surfaceHolder;
public GameThread(GameSurface gameSurface, SurfaceHolder surfaceHolder) {
this.gameSurface = gameSurface;
this.surfaceHolder = surfaceHolder;
}
#Override
public void run() {
long startTime = System.nanoTime();
while(running) {
Canvas canvas= null;
try {
// Get Canvas from Holder and lock it.
canvas = this.surfaceHolder.lockCanvas();
// Synchronized
synchronized (canvas) {
this.gameSurface.update();
this.gameSurface.draw(canvas);
}
}catch(Exception e) {
// Do nothing.
} finally {
if(canvas!= null) {
// Unlock Canvas.
this.surfaceHolder.unlockCanvasAndPost(canvas);
}
}
long now = System.nanoTime() ;
// Interval to redraw game
// (Change nanoseconds to milliseconds)
long waitTime = (now - startTime)/1000000;
if(waitTime < 10) {
waitTime= 10; // Millisecond.
}
System.out.print(" Wait Time="+ waitTime);
try {
// Sleep.
this.sleep(waitTime);
} catch(InterruptedException ignored) { }
startTime = System.nanoTime();
System.out.print(".");
}
}
public void setRunning(boolean running) {
this.running= running;
}
}
Now I start some other activity:
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
And this activity is not responding (it is shown, however).
Tried also to start it for result.
The same thing when returning back via finish() from my activity with SurfaceView to another one.
After debugging for a little while, I see that initialization of the activity is successful, it freezes somewhere later
Ok, that was it. Retry was always true. This variant of finalization helped me:
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
while(retry) {
try {
this.gameThread.setRunning(false);
this.gameThread.join();
retry = false;
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
Not sure it is the best one, but it works for me.
I am trying to create a simple render of a background with an image on top to update roughly every 30seconds. I am using a canvas to get this done but when I try and print to the canvas it throws a NPE. As far as I can tell the canvas has been initialised. The activity starts the view which starts/stops the thread that runs the draw method. It is in the draw method that the NPE is thrown. This is the activity that starts it all:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view = new GameView(this);
setContentView(view);
view.onCreate();
}
public void onPause() {
super.onPause();
view.onPause();
}
public void onResume() {
super.onResume();
view.onResume();
}
These are the main functions of my Surface View:
public GameView(Context context) {
super(context);
getHolder().addCallback(this);
setFocusable(true);
}
public void onCreate() {
_drawThread = new DrawThread(getHolder(), this);
}
synchronized public void draw(Canvas canvas) {
canvas.getHeight(); //returns NPE
}
public void onResume() {
if (!_drawThread.isAlive()) {
_drawThread.setRunning(true);
_drawThread.start();
} else {
_drawThread.setRunning(true);
}
}
public void onPause() {
boolean retry = true;
_drawThread.setRunning(false);
while (retry) {
try {
_drawThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
And my draw thread looks like:
public void run() {
Canvas c;
while (_running) {
c = null;
try {
c = _surfaceHolder.lockCanvas();
synchronized (_surfaceHolder) {
_game.draw(c);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} finally {
if (c != null) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
From what I can tell the canvas somehow doesn't get initialised before the draw thread is created which causes the NPE but I can't see what is wrong with the code. A little help or advise would be great.
You need to register your own SurfaceHolder.Callback or SurfaceHolder.Callback2 with the SurfaceHolder and ensure the surface has been created before attempting to draw into it. If you attempt to lock the canvas before the surface has been created, it will not be able to give you a Canvas object. You usually don't need to override SurfaceView, but just use it as a mechanism to get access to the backing surface in which you wish to draw.
I've got a surfaceview and a gameThread class.
The gameThread keeps updating and painting on the SurfaceView class.
Now when I exit the app (By pressing the home or back button) I get a message that the app force closed. That's because the GameThread still tries to draw on the erased surfaceview...
So how do i properly end the app without getting this force close notification? I would like the GameThread class to stop when the back button is pressed. It should pause when pressing on home and running in the background. When re-entering the still running game it should resume....
Any ideas?
This is my GameThread class:
public class GameThread extends Thread{
private GameView view;
public boolean isRunning = false;
public GameThread(GameView view) {
this.view = view;
}
public void setRunning(boolean setRunning) {
isRunning = setRunning;
}
public void run() {
while(isRunning) {
Canvas c = null;
view.update();
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.draw(c);
}
}finally {
if(c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
}
}
It keeps updating my GameView class:
public class GameView extends SurfaceView{
private GameThread gameThread;
public GameView(Context context, Activity activity) {
super(context);
gameThread = new GameThread(this);
init();
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
gameThread.setRunning(true);
gameThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
});
}
public void init() {
}
public void update() {
}
public void draw(Canvas canvas) {
super.draw(canvas);
}
}
When pressing home my logcat shows this up:
02-24 18:24:59.336: E/SurfaceHolder(839): Exception locking surface
02-24 18:24:59.336: E/SurfaceHolder(839): java.lang.IllegalStateException: Surface has already been released.
02-24 18:24:59.336: E/SurfaceHolder(839): at android.view.Surface.checkNotReleasedLocked(Surface.java:437)
02-24 18:24:59.336: E/SurfaceHolder(839): at android.view.Surface.lockCanvas(Surface.java:245)
02-24 18:24:59.336: E/SurfaceHolder(839): at android.view.SurfaceView$4.internalLockCanvas(SurfaceView.java:872)
Main activity:
public class MainActivity extends Activity {
private RelativeLayout relativeLayout;
private GameView gameView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
gameView = new GameView(this, this);
setContentView(R.layout.activity_main);
relativeLayout = (RelativeLayout)findViewById(R.id.mainView);
relativeLayout.addView(gameView);
}
#Override
public void onBackPressed() {
// TODO Auto-generated method stub
super.onBackPressed();
gameView.onBackPressed();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Well you can make changes to your code like this.
In GameView
#Override
public void surfaceCreated(SurfaceHolder holder) {
MainActivity obj=new MainActivity();
stop=obj.getBoolean(); //receive status of boolean from main activity
//stop is boolean set if backPressed in main activity
if(!stop){
gameThread.setRunning(true);
gameThread.start();
}
else
gameThread.setRunning(false);
}
In MainActivity
public Boolean getBoolean(){
return stop;
}
public void setBoolean(Boolean bools){
stop=bools;
}
#Override
public void onBackPressed() {
// TODO Auto-generated method stub
super.onBackPressed();
stop=true;
setBoolean(stop);
try{
Thread.sleep(200); // Be safeside and wait for Game thread to finish
}
catch(Exception e){
e.printStackTrace();
}
MainActivity.finish();
}
Call setBoolean once before backPress else it will give error. Make amendments to code as per your needs.
Hope it helps. Cheers. :)
The Surface underlying the SurfaceView gets destroyed between onPause() and onDestroy() in the app lifecycle. Send a message to your renderer thread in onPause(), and wait for the thread to shut down.
For an example, see the "Hardware scaler exerciser" activity in Grafika. It starts the render thread in the SurfaceHolder's surfaceCreated() callback.
Update: it felt weird to start the thread in surfaceCreated() and stop it in onPause(), so after a bit of reshuffling HardwareScalerActivity now stops the thread in surfaceDestroyed(). (The SurfaceView documentation is a bit ambiguous -- it says the Surface is valid "between surfaceCreated() and surfaceDestroyed()" -- but looking at the SurfaceView implementation this appears to be the expected usage.)
Update 2: I updated the code some more after finding cases it didn't handle well. There's now a 60-line megacomment in the sources, which can also been seen in my answer to a similar question.
Update 3: The mega-comment turned into an architecture doc appendix. Grafika's "hardware scaler exerciser" activity demonstrates approach #2, while "texture from camera" demonstrates approach #1.
I want to make stopwatch. And i create stopwatch class like this. And when i call onPause in another Activity its freeze application.
public class StopWatch implements Runnable {
private Object mPauseLock;
private boolean mPaused;
private boolean mFinished;
private ArrayList<TextView> textFields;
private Handler mHandler = new Handler();
public StopWatch( ArrayList<TextView> textFields) {
mPauseLock = new Object();
mPaused = false;
mFinished = false;
this.textFields =textFields;
}
public void run() {
textFields.get(1).setText("progressing...");
if (!mPaused) {
mHandler.postDelayed(this, 1000);
}
synchronized (mPauseLock) {
while (mPaused) {
try {
mPauseLock.wait();
} catch (InterruptedException e) {
}
}
}
}
public void onPause() {
synchronized (mPauseLock) {
mPaused = true;
}
}
public void onResume() {
synchronized (mPauseLock) {
mPaused = false;
mPauseLock.notifyAll();
}
}
}
and i create instance of class in another View like. Can somebody exmplain me where is problem?
stopky = new StopWatch(textFields);
stopky.run();
// do another stuff and register buttons with onClickListener and call
stopky.onPause(); // freeze application
stopky.onResume();
You can't Object.wait() in a run method called from a Handler, which is probably running on the main/UI Thread.
The whole Android app is coordinated via short methods which register with the main/UI Thread. You're probably registering your stopwatch there, too. It's not possible to perform a while loop there and at the same time process events from the user interface..
A quick solution would be to re-schedule your run method and check the status the next time it gets called. Basically like so:
public void run() {
textFields.get(1).setText("progressing...");
if (!mPaused) {
// do what has to be done when stopwatch is running
mHandler.postDelayed(this, 1000);
} else {
// just re-schedule with a shorter delay
mHandler.postDelayed(this, 10);
}
}
An even better way would be to go for a fully event-driven design and avoid calling the stopwatch at all while it is stopped. In this case, you would simply re-start it from the Button's event handler.
I've searched over and over in this forum & google about this issue, maybe it has to do something with my thread or the way how the game is starting.
This is the skeleton of the game application, please tell me what I'm doing wrong.
This is what I'm doing in thread;
public class GameThread extends Thread
{
static final long FPS = 30;
private GameView view;
private boolean running = false;
private SurfaceHolder surfaceHolder;
public boolean isSurfaceCreated = false;
private Object mPauseLock;
private boolean mPaused;
public GameThread(SurfaceHolder surfaceHolder, Handler handler, GameView view)
{
super();
mPauseLock = new Object();
mPaused = false;
this.surfaceHolder = surfaceHolder;
this.view = view;
}
public void setRunning(boolean run)
{
running = run;
}
/**
* pause thread.
*/
public void onPause() {
synchronized (mPauseLock) {
mPaused = true;
}
}
/**
* resume thread.
*/
public void onResume() {
synchronized (mPauseLock) {
mPaused = false;
mPauseLock.notifyAll();
}
}
#Override
public void run()
{
// run our thread on high priority to keep from getting slowdown
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
// wait for surface to become available # start
while (!isSurfaceCreated && running)
{
try {
sleep(50);
} catch (InterruptedException e) {}
}
long ticksPS = 1000 / FPS;
long startTime;
long sleepTime;
while (running){
Canvas c = null;
startTime = System.currentTimeMillis();
//long msSinceLastTick = System.currentTimeMillis() - view.lastTickMs;
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder)
{
/*
* stop updating when pause is pressed
*/
if(mPaused == false)
{
view.update();
}
view.onDraw(c);
}
} finally {
if (c != null)
{
surfaceHolder.unlockCanvasAndPost(c);
}
}
sleepTime = ticksPS - (System.currentTimeMillis() - startTime);
try {
if (sleepTime > 0)
sleep(sleepTime);
else
sleep(10);
} catch (Exception e) {}
}
}
This is the start activity code;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
public class test_game extends Activity {
/** Called when the activity is first created. */
private GameView Game;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Game = new GameView(this);
setContentView(Game);
}
#Override
public void onBackPressed()
{
super.onBackPressed();
//KILL ALL
finish();
System.runFinalizersOnExit(true);
System.exit(0);
}
// ===========================================================
// onPause
// ===========================================================
protected void onPause() {
super.onPause();
}
// ===========================================================
// onStop
// ===========================================================
protected void onStop() {
super.onStop();
}
// ===========================================================
// onDestroy
// ===========================================================
#Override
protected void onDestroy() {
super.onDestroy();
}
}
Added extra lines in AndroidManifest.xml
android:launchMode="singleTask"
android:alwaysRetainTaskState="true"
GameView looks like this:
public class GameView extends SurfaceView {
private SurfaceHolder holder;
private GameThread Thread = null;
private int testVar = 0;
private boolean isPaused = false;
public GameView(Context context)
{
super(context);
loadGameFiles();
holder = getHolder();
Thread = new GameThread(holder, new Handler(), this);
holder.addCallback(new SurfaceHolder.Callback()
{
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
boolean retry = true;
Thread.setRunning(false);
while (retry)
{
try {
Thread.join();
retry = false;
} catch (InterruptedException e)
{
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder)
{
Thread.isSurfaceCreated = true;
if(isPaused == true)
{
onResume();
}
Thread.setRunning(true);
Thread.start();
GameMenu();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
});
setFocusable(true);
}
public boolean isPaused()
{
return this.isPaused;
}
/**
* Pauses the physics update & animation.
*/
public void onPause() {
synchronized (holder)
{
if(isPaused == false)
{
isPaused = true;
Thread.onPause();
}
}
}
public void onResume()
{
synchronized (holder)
{
if(isPaused == true)
{
isPaused = false;
Thread.onResume();
}
}
}
etc..
Thanks for the replies, the forum commentbox doesn't let me post this; anyway, here it is:
Hi Frankenstein, I've tried what you suggested here, still doesn't work. My resume code in the GameView looks like this;
public void onResume(){ synchronized (holder)
{ if(isPaused == true){
isPaused = false; //resume notification to the gameview
Thread.onResume(); //resume the thread
}
}
}
This is on the Activity class;
#Override
protected void onPause() {
super.onPause();
Game.onPause();
}
#Override
protected void onResume() {
super.onResume();
Game.onResume();
}
I have not used logcat, not sure how to use it, I can sure call it in each steps in java but where does it displays the error/logs? thanks in advance guys!
When I resume it shows following error in LogCat;
Added this line to avoid the previous problem;
Thread.isSurfaceCreated = true;
Thread.setRunning(true);
Thread.start();
loadGame();
UPDATE:
The application returns to black screen, but it's not frozen.
Errors (updated) in logcat;
12-14 16:54:52.176: ERROR/MediaPlayerService(3937): create PVPlayer
12-14 16:55:03.172: ERROR/dalvikvm(4619): Failed to write stack traces to /data/anr/traces.txt (1093 of 3526): Unknown error: 0
12-14 16:55:03.910: ERROR/dalvikvm(25464): Failed to write stack traces to /data/anr/traces.txt (2401 of 3332): Math result not representable
12-14 16:55:03.918: ERROR/dalvikvm(25279): Failed to write stack traces to /data/anr/traces.txt (-1 of 2354): Math result not representable
12-14 16:55:32.394: ERROR/imdg81(19379): IsShutDownStarted()
12-14 16:55:32.590: ERROR/imdg81(19379): IsShutDownStarted()
12-14 16:55:37.902: ERROR/MediaPlayer(19379): error (100, 0)
12-14 16:55:37.930: ERROR/SoundPool(5264): Unable to load sample: (null)
12-14 16:55:39.281: ERROR/SoundBooster(5328): readSBTable: file open error!
12-14 16:55:39.281: ERROR/AcousticEQ(5328): [AEQ] aeqcoe.txt: file open error!
12-14 16:55:39.500: ERROR/AudioService(19379): Media server died.
12-14 16:55:39.500: ERROR/AudioService(19379): Media server started.
12-14 16:55:39.996: ERROR/MediaPlayerService(5328): create PVPlayer
In your onSurfaceCreated method you have this block of code:
if(isPaused == true){
onResume();
}
Thread.setRunning(true);
Thread.start();
your call to Thread.start() is called no matter what (even if isPaused is true). Since your thread is already started in this case, you're trying to start it twice and thus getting an illegal state exception.
Thanks guys!
I've fixed this issue, the black screen was showing because the thread isn't created again but being manipulated when I entered 2nd time to the screen. Therefore, it showed an empty/null thread being manipulated as black screen. I've added following line to OnResume()
if(!Thread.isAlive())
{
Thread = new GameThread(holder, new Handler(), this);
}
This is the fix for my issue, but now I've to implement onSaveInstanceState & onRestoreInstanceState to avoid the from re-starting from beginnig.