i am trying to find a way that the thread can be shut down and restarted without the program crashing. It is called from a menu and an activity sets the panel as its content view and i wish that when the return arrow is pressed on the android that it returns to the activity and then the thread can be restarted however currently any variation i try causes it to crash at one point or another :(
package SortItOut.sortitout;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class Level1Panel extends SurfaceView implements
SurfaceHolder.Callback {
private Level1Thread thread;
static Bitmap background;
static int x = 0;
public Level1Panel(Context context) {
super(context);
getHolder().addCallback(this);
background = BitmapFactory.decodeResource(getResources(), R.drawable.gamebackground);
thread = new Level1Thread(getHolder(), this);
setFocusable(true);
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
public void surfaceCreated(SurfaceHolder holder) {
thread.setRunning(true);
thread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
thread.stop();
}
public void render(Canvas canvas)
{
canvas.drawBitmap(background, x, 0, null);
x = x + 20;
}
}
======Thread=======
package SortItOut.sortitout;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.SurfaceHolder;
public class Level1Thread extends Thread {
private boolean running;
private SurfaceHolder surfaceHolder;
private Level1Panel gamePanel;
public void setRunning(boolean running) {
this.running = running;
}
public Level1Thread(SurfaceHolder surfaceHolder, Level1Panel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
public void run() {
Canvas canvas;
while (running) {
canvas = null;
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
this.gamePanel.render(canvas);
}
} finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
You should not use thred.stop(); You must allow the thred to stop on its own by stopping the loop within it.
Check the second example of SurfaceView aniamtion: How can I use the animation framework inside the canvas?
Here are some suggestions (without getting any details on the exception of the crash):
You should Implement Runnable instead of Extend Thread.
You should let the thread know that it should exit the loop.
You should interrupt() the thread instead of calling stop() (interrupt also takes the thread out of a blocking state).
You should handle the InterruptedException inside the run method.
You should exit gracefully when you're interrupted (i.e. finish up whatever you're doing and clean up).
I'm sure I'm missing some things, but generally this should keep you out of trouble. Again, without knowing the specific exception you're getting, I would assume that you're calling stop and the thread is not cleaning up properly at whatever state it was when running, thus you get the crashes at one point or another (depending on what's corrupted with the state).
Related
I am developing a game for android. Between stages, i want to show a part of a map with a route, and move it from a city to city (stage to stage).
First i want to do it on my phone, this a Samsung Galaxy Y, 240x320 Qvga ldpi.
So i have the map file in jpg format. This picture is 2463x602, this is a world map.
I did it, everything is done except one thing. This "animation" is slow for me.
When i start with this, i thought, it will be so fast, and i will handle the speed with a Thread.sleep(); but the matter is, it is not fast.
How can i make it more faster this?
Here is my code:
package hu.mycompany.myproject;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MapActivity extends Activity {
DrawView drawView;
SoundPool soundPool;
int soundId;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
soundId = soundPool.load(this, R.raw.airplane, 1);
drawView = new DrawView(this);
setContentView(drawView);
}
public void playSound() {
soundPool.play(soundId, 1f, 1f, 1, 0, 1f);
}
#Override
public void onResume() {
super.onResume();
playSound();
drawView.resume();
}
#Override
public void onPause() {
super.onPause();
drawView.pause();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_map, menu);
return true;
}
public class DrawView extends SurfaceView implements Runnable {
Bitmap gameMap = null;
Thread gameLoop = null;
SurfaceHolder surface;
Rect rect;
volatile boolean running;
volatile boolean moved;
public DrawView(Context context) {
super(context);
moved = false;
surface = getHolder();
gameMap = BitmapFactory.decodeResource(getResources(),
R.drawable.map);
}
public void resume() {
running = true;
gameLoop = new Thread(this);
gameLoop.start();
}
public void pause() {
running = false;
while (true) {
try {
gameLoop.join();
} catch (InterruptedException e) {
}
}
}
#Override
public void run() {
Rect canvasSize = new Rect(0, 0, 240, 320);
Paint paint = new Paint();
paint.setAntiAlias(true);
while (running) {
if (!surface.getSurface().isValid()) {
continue;
}
if (!moved) {
int i;
for (i = 80; i <= 830; i++) {
Canvas canvas = surface.lockCanvas();
rect = new Rect(i, 250, (i + 240), 570);
canvas.drawBitmap(gameMap, rect, canvasSize, paint);
surface.unlockCanvasAndPost(canvas);
}
moved = true;
}
}
}
}
}
1-try to reduce picture size by exporting it save for web in photoshop and select png as extension type
2- You should create two class one for handling thread and other for rendering and game logic. The thread will manage it by locking canvas when system is rendering. Take a look at this link
http://android-er.blogspot.com/2010/05/android-surfaceview.html
I am looking for an easy way to animate custom views in Android. I am trying to avoid using the animator object but want to work with raw threads. What I have done is created a custom view by creating a class that extends android.view.View. I then override the onDraw method and use the canvas to draw a rect. What I would like is the rect to shrink, so I keep a variable that represents the x value of the right hand side of the rectangle. I would then like the the rectangle's right edge shrink in over time. What I would have liked to do is create a new thread, start it and have it change the value of the rectangle. That all works except the view doesn't get updated until you call View.invalidate. The problem is that I can't call that from the thread that I spawned because it is not the UI thread. I read solutions on using Handlers... but I am still unsure if that is the correct solution and then how to use them.
package com.example.practicum;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;
public class TimerControl extends View implements Runnable, Handler.Callback
{
private Paint paint;
private Rect rect;
private Thread t;
private Handler h;
public TimerControl(Context context, AttributeSet attrs)
{
super(context, attrs);
// TODO Auto-generated constructor stub
paint = new Paint();
paint.setColor(Color.BLUE);
rect = new Rect(0,0,60,60);
t = new Thread(this);
t.start();
h = new Handler(this);
//h.post(this);
}
#Override
public void onDraw(Canvas canvas)
{
canvas.drawRect(rect, paint);
}
#Override
public void run()
{
rect.right = rect.right-1;
while(true)
{
rect.right = rect.right-1;
this.invalidate();
try
{
Thread.sleep(5000);
h.sendEmptyMessage(0);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
#Override
public boolean handleMessage(Message msg)
{
return false;
}
}
you can define a global Handler (in the UI thread):
Handler mHandler = new Handler();
then, from your thread, call:
mHandler.post(new Runnable() {
public void run() {
// call some method on the UI thread
}
});
I am trying to learn game development in android. First I am trying to appear and disappear an object on screen using game loop for every five second. But I did not get succeed. I have read different tutorials and forums. I applied all things as in tutorials but still object is drawing continuously. It is not disappearing. I a not getting what I am missing? Please guide me.
The complete code is here:
MainGameActivity.java
package com.example.showandhideobject;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
public class MainGameActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(new MainGamePanel(this));
}
}
MainGamePanel .java
package com.example.showandhideobject;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainGamePanel extends SurfaceView implements
SurfaceHolder.Callback {
private MainGameThread thread;
private ImageObject image;
// private long gameStartTime;
public MainGamePanel(Context context) {
super(context);
// adding the callback (this) to the surface holder to intercept events
getHolder().addCallback(this);
// create the game loop thread
thread = new MainGameThread(getHolder(), this);
Bitmap imageBitMap = BitmapFactory.decodeResource(getResources(),
R.drawable.rose);
image = new ImageObject(imageBitMap, 100, 150);
image.setAppeared(false);
image.setDisappearTime(System.currentTimeMillis());
// make the GamePanel focusable so it can handle events
setFocusable(true);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// at this point the surface is created and
// we can safely start the game loop
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
public void update() {
Log.i("Image Status::::::::::::: ",
Boolean.valueOf(image.isAppeared()).toString());
if (!image.isAppeared()
&& System.currentTimeMillis() - image.getDisappearTime() >= 5000) {
Log.i("Image Object::::::: ", "Showing");
image.setAppeared(true);
image.setAppearTime(System.currentTimeMillis());
}
if (image.isAppeared()
&& (System.currentTimeMillis() - image.getAppearTime() >= 5000)) {
Log.i("Image Object::::::: ", "Not Showing");
image.setAppeared(false);
image.setDisappearTime(System.currentTimeMillis());
}
}
public void render(Canvas canvas) {
if (image.isAppeared()) {
image.draw(canvas);
}
}
}
MainGameThread.java
package com.example.showandhideobject;
import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
public class MainGameThread extends Thread {
// Surface holder that can access the physical surface
private SurfaceHolder surfaceHolder;
// The actual view that handles inputs
// and draws to the surface
private MainGamePanel gamePanel;
// flag to hold game state
private boolean running;
public boolean isRunning() {
return running;
}
public void setRunning(boolean running) {
this.running = running;
}
public MainGameThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
#Override
public void run() {
Canvas canvas;
while (isRunning()) {
canvas = null;
// try locking the canvas for exclusive pixel editing
// in the surface
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
Log.i("With in :::::::::", "Game Loop");
// update game state
gamePanel.update();
// render state to the screen and draw the canvas on the
// panel
gamePanel.render(canvas);
// gamePanel.onDraw(canvas);
}
} finally {
// in case of an exception the surface is not left in an
// inconsistent state
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
} // end finally
}
}
}
ImageObject.java
package com.example.showandhideobject;
import android.graphics.Bitmap;
import android.graphics.Canvas;
public class ImageObject {
private Bitmap bitmap; // the actual bitmap
private int x; // the X coordinate
private int y; // the Y coordinate
private boolean isAppeared;
private long appearTime;
private long disappearTime;
// Constructor for this class
public ImageObject(Bitmap bitmap, int x, int y) {
this.bitmap = bitmap;
this.x = x;
this.y = y;
}
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public boolean isAppeared() {
return isAppeared;
}
public void setAppeared(boolean isAppeared) {
this.isAppeared = isAppeared;
}
public long getAppearTime() {
return appearTime;
}
public void setAppearTime(long appearTime) {
this.appearTime = appearTime;
}
public long getDisappearTime() {
return disappearTime;
}
public void setDisappearTime(long disappearTime) {
this.disappearTime = disappearTime;
}
/* Method to draw images on Canvas */
public void draw(Canvas canvas) {
canvas.drawBitmap(bitmap, x - (bitmap.getWidth() / 2),
y - (bitmap.getHeight() / 2), null);
}
}
in this part
if (image.isAppeared()) {
image.draw(canvas);
}
you never clear your canvas. What you are doing is actually drawing your image over and over on the same spot.
You probably need to redraw a background in cas isAppeared() is false
Edit
you can also use canvas.save() before drawing the image, and canvas.restore() when you don't want the image anymore.
Don't try to optimise too early, game rendering is usually inefficient as almost always most of the screen is expected to change.
Loop should be:
always draw the background to canvas
always draw all game objects to the canvas, let them decide if they are visible or not which will simplify the MainGamePanel class
finally always display canvas (by copying to the image as you are doing)
To expand on point 2:
/* Method to draw images on Canvas */
public void draw(Canvas canvas) {
if(!isAppeared) return; //let the object decide when it should be drawn
canvas.drawBitmap(bitmap, x - (bitmap.getWidth() / 2),
y - (bitmap.getHeight() / 2), null);
}
Change the method render in MainGamePanel.java to
if (image.isAppeared() && canvas != null) {
image.draw(canvas);
}
It's a bit cheeky - but I was wondering if anyone could tell me what's wrong below.
This is messing around trying to understand android - not "real" code.
It's a surfaceView which is laid out in the main activity layout.
It works - until the phone's "off" button is tapped (sleep) and woken up again. Upon waking up, it goes crazy and android produces a "Force Close" diaglog.
I've been trying to follow the path with LogCat, but for some reason, some messages get dropped - OR - the path I think is being followed, isn't.
eg - on putting the phone to sleep, I will get surfaceDestroyed called (seems reasonable) but on waking, I do not get a surfaceCreated().
The basic logic is: the surfaceView creates a thread which paints the system time in seconds as text. That's it.
I've got a real app I'd like to write - but until I really understand the basics, that won't happen. I've been through a fair number of tutorials too.
Any pointers most gratefully recieved :)
Cheers
Tim
package net.dionic.android.bouncingsquid;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.lang.System;
public class WidgetSeconds extends SurfaceView implements SurfaceHolder.Callback {
private class CanvasThread extends Thread {
private SurfaceHolder _surfaceHolder;
private WidgetSeconds _surfaceView;
private boolean _run = false;
public CanvasThread(SurfaceHolder surfaceHolder, WidgetSeconds surfaceView) {
Log.i("WidgetSecs.CanvasThread", "constructor");
_surfaceHolder = surfaceHolder;
_surfaceView = surfaceView;
}
public void setRunning(boolean run) {
_run = run;
}
#Override
public void run() {
Canvas c;
while (_run) {
c = null;
try {
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
_surfaceView.onDraw(c);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} 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) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
private CanvasThread canvasthread;
public void Initalise() {
Log.i("WidgetSecs", "Initialise");
}
public WidgetSeconds(Context context, AttributeSet attrs) {
super(context, attrs);
Log.i("WidgetSecs", "constructor");
this.Initalise();
getHolder().addCallback(this);
setFocusable(true);
}
#Override
public void onDraw(Canvas canvas) {
Paint textPaint;
canvas.drawColor(Color.GRAY);
textPaint = new Paint();
textPaint.setTextSize(32);
canvas.drawText(System.currentTimeMillis()/1000 + " S", 10, 50, textPaint);
canvas.restore();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
Log.i("WidgetSecs", "surfaceChanged");
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Log.i("WidgetSecs", "surfaceCreated");
Log.i("WidgetSecs.CanvasThread", "Thread create");
canvasthread = new CanvasThread(getHolder(), this);
canvasthread.setRunning(true);
canvasthread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.i("WidgetSecs", "surfaceDestroyed");
boolean retry = true;
while (retry) {
try {
Log.i("WidgetSecs", "Thread destroyed");
canvasthread.join();
canvasthread = null;
retry = false;
} catch (InterruptedException e) {
Log.i("WidgetSecs", "Thread join failed");
// we will try it again and again...
}
}
}
}
For simpleness of the question, I'm drawing an integer on a SurfaceView which increases by 1 every draw.
The increasing actually happens, as I can see on the System.out.
The text on the screen stays on '0'.
Who can tell me what I'm doing wrong?
SurfaceViewTest.java
package com.niek.surfaceviewtest;
import android.app.Activity;
import android.os.Bundle;
public class SurfaceViewTest extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
StatusView.java
package com.niek.surfaceviewtest;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class StatusView extends SurfaceView implements SurfaceHolder.Callback {
private int tmp;
private DrawThread drawThread;
public StatusView(Context context, AttributeSet attrs) {
super(context, attrs);
getHolder().addCallback(this);
setFocusable(true);
drawThread = new DrawThread(getHolder());
}
#Override
public void onDraw(Canvas c) {
c.drawColor(Color.BLACK);
Paint p = new Paint();
p.setColor(Color.RED);
c.drawText(tmp + "", 10, 10, p);
tmp++;
System.out.println(tmp);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
drawThread.setRunning(true);
drawThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// 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
boolean retry = true;
drawThread.setRunning(false);
while (retry) {
try {
drawThread.join();
retry = false;
} catch (InterruptedException e) {
// we will try it again and again...
}
}
}
protected class DrawThread extends Thread {
private SurfaceHolder surfaceHolder;
private boolean isRunning;
public DrawThread(SurfaceHolder surfaceHolder) {
this.surfaceHolder = surfaceHolder;
isRunning = false;
}
public void setRunning(boolean run) {
isRunning = run;
}
public void run() {
Canvas c;
while (isRunning) {
c = null;
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
onDraw(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) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#FFFFFF">
<com.niek.surfaceviewtest.StatusView
android:id="#+id/statusview1"
android:layout_width="fill_parent"
android:layout_height="30dip"
android:background="#000000"
/>
</LinearLayout>
My only guess is that painting isn't being done on the surface because the view isn't invalidated. You're supposed to call invalidate() in order to draw, and then let the framework call onDraw(). Maybe that's why tmp is being incremented, but the paint operation only reaches the surface the first time.
It might be worth experimenting: maybe make Canvas c a member of StatusView, and then replace
synchronized (surfaceHolder) {
onDraw(c);
}
with
synchronized (surfaceHolder) {
invalidate();
}
Does that work?
The current solution is not right. You are using the SurfaceView in order to update the content from a separate thread and not using the invalidate() method that will run onDraw() method when the system refreshes the content. The problem is that you have set a background for your StatusView, try deleting that line
android:background="#000000"
apparently, you need to control the whole information displayed in that view.
Looks like you are showing your main.xml with setContentView(R.layout.main); instead of creating the surface and displaying that. unless i am missing code somewhere I don't see that as being the case.