I know that there are a lot of questions about this topic, however, I am still not completely satisfied with the answers provided.
The situation:
I implemented SurfaceView with drawing in another thread using SurfaceHolder like it is suggested in the developer's guide: http://developer.android.com/guide/topics/graphics/2d-graphics.html
The problem:
Sometimes I get java.lang.IllegalStateException: Surface has already been released:
when calling SurfaceHolder.lockCanvas()
or when calling SufraceHolder.unlockCanvasAndPost()
This means that my surface is sometimes released before I lock the canvas, and sometimes - between locking and unlocking it.
My solution:
I perform surface check and locking/unlocking canvas within synchronized blocks, so I know for sure that the surface can't get destroyed in between those actions. However, I have never worked with synchronized blocks and I wanted to ask if there is anything wrong with it. So far the code worked fine, but you never know when synchronization issues may show themselves, so I'm not completely convinced that this is the best way.
private class DrawingThread extends Thread{
#Override
public void run() {
super.run();
while (!isInterrupted() && holder != null) {
Canvas drawingCanvas = null;
synchronized (this) {
if (holder.getSurface().isValid()) {
drawingCanvas = holder.lockCanvas();
}
}
if (drawingCanvas != null && drawingCanvas.getWidth() > 0) {
drawThisView(drawingCanvas);
synchronized (this) {
if(holder.getSurface().isValid()) {
holder.unlockCanvasAndPost(drawingCanvas);
}
}
}
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
if(drawingThread != null){
drawingThread.interrupt();
}
drawingThread = new DrawingThread();
drawingThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if(drawingThread.isInterrupted()){
drawingThread = new DrawingThread();
drawingThread.start();
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
drawingThread.interrupt();
}
The synchronized statement is used for mutually-exclusive access between multiple threads. If you synchronized (this) in thread #1, then any other thread attempting to do the same will block until thread #1 exits the synchronized block.
If you only ever use it in one thread, it does nothing useful. Of course, if you have multiple threads attempting to lock and unlock the Surface's Canvas, you should address the problem by not doing that in the first place, rather than trying to enforce exclusive access after the fact.
Your use of interrupt() and isThreadInterrupted() doesn't make a lot of sense. If you want to raise a flag that tells a thread that it's time to stop running, a volatile boolean works just fine. The only advantage of interrupt() is that it will wake the thread if it's waiting for an object to signal. Further, consider the docs for the isInterrupted() call:
Returns a boolean indicating whether the receiver has a pending interrupt request (true) or not ( false)
(Emphasis mine.) Your code allows for situations like, "if the thread is alive, but an interrupt has been raised for it, go ahead and create a new thread while the old one is still running".
Some information about working with SurfaceView can be found in this appendix. That links to an example in Grafika that starts and stops the thread based on the Surface callbacks, which you can find here.
After some hours of trials and errors I think I have figured it out. After all, all I had to do is to read the android reference. Here are some important things that I got wrong:
SurfaceHolder.getSurface.isValid() only checks if the surface can
be locked, so this check can (and does) fail for seeing is the canvas
can be unlocked.
To check if you can call unlockCanvasAndPost(),
you just check if the canvas returned by lockCanvas() is not
null.
If the canvas is null, you should not unlock it (if you
try it will throw an exception).
If it's not null, you have to unlock it, otherwise your app will freeze if you activity tries to stop (after onPause() the SurfaceHolder tries to destroy the Surface, but since the Canvas is locked, it couldn't, so you get into a deadlock).
Hope this can help other "noobs". Finally, this is my final code for SurfaceView(the try/catch block is not necessary any more):
DrawingThread drawingThread;
private class DrawingThread extends Thread{
public volatile boolean canDraw = true;
#Override
public void run() {
try {
while (canDraw) {
Canvas drawingCanvas = null;
if (canDraw && holder.getSurface().isValid()) {
drawingCanvas = holder.lockCanvas();
if (drawingCanvas != null) {
drawThisView(drawingCanvas);
holder.unlockCanvasAndPost(drawingCanvas);
}
}
}
}catch(IllegalStateException e){
e.printStackTrace();
canDraw = false;
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
if(drawingThread != null){
drawingThread.canDraw = false;
}
drawingThread = new DrawingThread();
drawingThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if(!drawingThread.canDraw){
drawingThread = new DrawingThread();
drawingThread.start();
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
if(drawingThread != null) {
drawingThread.canDraw = false;
}
}
And thanks fadden for clarifying some points about drawing in another thread.
I had same issue - drawing in SurfaceView using background thread and, sometimes, application hanged up when activity closed. Tracing revealed that surface in this case was destroyed between lockCanvas and unlockCanvasAndPost.
It seems the cause of such behavior was that I misinterpret docs a bit. Callback.surfaceDestroyed has to be used to hold surface from destroying (by not returning from it) until background thread finishes with surface.
DrawingThread drawingThread;
ReentrantLock paintLock = new ReentrantLock();
private class DrawingThread extends Thread{
public volatile boolean canDraw = true;
#Override
public void run() {
try {
while (canDraw) {
Canvas drawingCanvas = null;
paintLock.lock();
if (canDraw)) {
drawingCanvas = holder.lockCanvas();
if (drawingCanvas != null && drawingCanvas.getWidth() > 0) {
drawThisView(drawingCanvas);
holder.unlockCanvasAndPost(drawingCanvas);
}
}
paintLock.unlock();
}
}catch(IllegalStateException e){
e.printStackTrace();
canDraw = false;
}
}
}
...
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
paintLock.lock();
if(drawingThread != null) {
drawingThread.canDraw = false;
}
paintLock.unlock();
}
Placing Lock, Draw and Unlock within a single synchronised statement fixed the Exception.
I came to know about the synchronised statement after reading this thread, correct me if this is a bad idea.
private class DrawingThread extends Thread{
#Override
public void run() {
super.run();
while (!isInterrupted() && holder != null) {
Canvas drawingCanvas = null;
synchronized (this) {
if (holder.getSurface().isValid()) {
drawingCanvas = holder.lockCanvas();
}
if (drawingCanvas != null && drawingCanvas.getWidth() > 0) {
drawThisView(drawingCanvas);
}
if(holder.getSurface().isValid()) {
holder.unlockCanvasAndPost(drawingCanvas);
}
} // Lock -> Draw -> Unlock in a single synchronised statement
}
}
}
Related
I am developing a simple game that is running in its own thread. When I launch the app, everything works great. The thread is initialized, and the update() and render() functions are executed from the thread, which is great.
If I exit the app, the thread is paused. When I launch the app again, the thread resumes fine. (The update and render functions are executed). Again, this is great!
THE PROBLEM IS, I hit the power button to sleep the device. When I resume the app from this state, the render() function appears to work fine; it is drawing all of the elements (background, sprites, etc) exactly where they were before I hit the button. BUT: The sprites are no longer animated! After a bit of tracing and troubleshooting, I found out that although the "running" flag is set to TRUE, it seems like the "RUN" function is no longer running! (On an another note, I am confused why the RENDER function works, but the UPDATE function does not, unless the app is rendering the objects from some other method? Irregardless...)
I am working this problem from two possible angles; I am either missing something in the way Android manipulates my thread, OR would this have something to do with the fact that the member variables inside the objects have been reset...
Can someone please review the basic code I have posted below, and advise any suggestions on how to improve thread manipulation? I am an intelligent person, and I have read so many articles from different sources on the subject, and am more confused now more than ever! Am I on the right track here, or is there a more intelligent way of doing this? Do I really need a separate game thread, or can I run it using the device's main thread instead?
Thanks for your feedback!
public class GameThread extends Thread {
private boolean running=false;
private SurfaceHolder surfaceHolder;
private GameView gameView;
public GameThread(SurfaceHolder surfaceHolder, GameView gameView){
super();
this.surfaceHolder = surfaceHolder;
this.gameView = gameView;
}
#Override
public void run(){
Canvas canvas;
// THIS ONLY RUNS ONCE!
// WHEN I TURN THE POWER OFF AND BACK ON, THE RUN() METHOD DOES NOT RUN!
// ODDLY ENOUGH, IF I WERE TO EXIT THE APP, AND GO BACK IN, IT WORKS FINE!
// THE running FLAG SEEMS TO CHANGE PROPERLY WHEN PAUSING OR RESUMING (SEE gameView, BELOW).
while (running){
canvas = null;
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder){
gameView.update();
gameView.render(canvas);
}
} finally {
if (canvas!=null)
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
public void setRunning(boolean running){
this.running = running;
}
}
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
private GameThread thread;
private GameScreen gameScreen;
public GameView(Context context) {
super(context);
gameScreen = new GameScreen(context);
setFocusable(true);
thread = new GameThread(getHolder(), this);
getHolder().addCallback(this);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread = new GameThread(getHolder(), this);
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
while (retry){
try {
thread.join();
retry = false;
} catch (InterruptedException e){
// Do nothing; continue trying
}
}
}
public void render(Canvas canvas){
gameScreen.draw(canvas);
}
public void update(){
gameScreen.update();
}
#Override
public boolean onTouchEvent(MotionEvent event){
gameScreen.touch(event);
}
public void resume(){
// TODO: Resume events go here
thread.setRunning(true);
}
public void pause(){
// TODO: Pause events go here
thread.setRunning(false);
}
}
public class GameScreen {
public GameScreen(Context context){
// Initialize private variables here
}
public void draw(Canvas canvas){
// Draw events go here
}
public void update(){
// Update events go here
}
public void touch(MotionEvent event){
// Touch events go here
}
}
The update() method needs to be executed BEFORE trying to post the canvas. Still unsure why this is, but it works as intended now.
Canvas canvas = null;
while (running){
gameView.update();
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder){
gameView.render(canvas);
}
} finally {
if (canvas!=null)
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
I switched a thread from extending Thread to implementing Runnable (implementing Runnable is better practice, right?) so when I called surfaceCreated I went from:
public void surfaceCreated(SurfaceHolder holder) {
createSprites();
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
to:
public void surfaceCreated(SurfaceHolder holder) {
createSprites();
gameLoopThread.setRunning(true);
gameLoopThread.run();
}
as the start() method is not included in the Runnable interface. However, once I switched this, my sprites no longer displayed. This is the run() method:
public void run() {
Canvas c;
while (running) {
c = null;
try {
c = view.getHolder().lockCanvas();
synchronized (mPauseLock) {
view.onDraw(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
synchronized (mPauseLock) {
while (mPaused) {
try {
mPauseLock.wait();
} catch (InterruptedException e) {
}
}
}
}
}
It gets called during both scenarios, but sprites only display when I call start() instead of directly calling run(). Could someone explain why this is?
You can't start a Thread by calling gameLoopThread.run() for a Runnable (or even by calling run() for a Thread).
You have to pass your Runnable instance to a Thread instance and call start() for that Thread.
For example :
public void surfaceCreated(SurfaceHolder holder) {
createSprites();
gameLoopThread.setRunning(true);
Thread t = new Thread(gameLoopThread);
t.start();
}
I have a thread that is used to spin an imageview as other things happen in the background. It works great for spinning, but I've noticed as I turn it on and off (based on the spinningMain variable) the button starts to spin faster and faster and faster.
I'm unsure what the cause of this is as the thread is told to sleep every 100ms.
If it helps I also have another thread (runnable thread) which runs the main code in between, which I was wondering whether it was disrupting the thread?
The imageView update thread is:
final Thread t = new Thread() {
#Override
public void run() {
try {
while (!isInterrupted()) {
Thread.sleep(100);
runOnUiThread(new Runnable() {
#Override
public void run() {
if (spinningMain == true) {
// update TextView here!
if (spinningAngleMain >= 360) {
spinningAngleMain = 0;
} else {
spinningAngleMain += 5;
}
imageButton.setRotation(spinningAngleMain);
}
}
});
}
} catch (InterruptedException e) {
}
}
};
t.start();
And the Second thread is pretty much made as follows
new Thread(new Runnable() {
#Override
public void run() {
if (searchStateButton == true) {
//do all my stuff
}
My boolean variables are set to volatile if that helps as well.
So I have this really weird problem that I simply cannot understand why it doesn't work. I am building a strobe light as part of my app and have created a separate Strobelight class. When I call the turnOn method or update method, the intervals never are changed. I guess it would make it easier to explain using some code:
public class Strobelight{
private int delayOn, delayOff;
public void turnStrobeOn(){...}
public void update(int a_delayOn, int a_delayOff){
delayOn = a_delayOn;
delayOff = a_delayOff;
}
public void turnOn(int a_delayOn, int a_delayOff){
delayOn = a_delayOn;
delayOff = a_delayOff;
this.turnStrobeOn();
}
Depending on whether the strobe light is already on or not, one of these methods are called to change turn on the strobe light with the specified intervals or simply to change the intervals.
Instead of changing the intervals to something custom, the application just uses the smallest possible intervals when calling Thread.sleep() for the flashlight being on or off
EDIT: this is the thread code, and code that turns the flashlight on
public void turnStrobeOn(){
for ( int i = 0; i < 3; i++){
isInCycle = true;
cam = Camera.open();
Parameters p = cam.getParameters();
p.setFlashMode(Parameters.FLASH_MODE_TORCH);
cam.setParameters(p);
cam.startPreview(); // the flashlight is now on
lightIsOn = true;
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(delayOn);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
cam.stopPreview();
cam.release();
lightIsOn = false;
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(delayOff);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
} //end of for loop
isInCycle = false;
} // end of turnStrobeOn
You might want to get someone else's advice on this buy my best guess is that you can't be so specific on sleep(). Also don't forget to use miliseconds for that. I'd suggest using the android handler and doing postDelayed for this instead of sleeping.
How to pause / sleep thread or process in Android?
i'm tried many suggestions but nothing works! When i call Thread.sleep() in background thread, main thread also freezes for this time (Animation frame drop) :(
Version 1:
public void UpdateChannels(final ArrayList<Channel> channels)
{
new AsyncTask<ArrayList<Channel>, Object[], Void>()
{
#Override
protected Void doInBackground(ArrayList<Channel>... arrayLists)
{
for(Channel channel : arrayLists[0])
{
Integer chid = new Integer(channel.arfcn);
ChannelRect channelRect = Channels.get(chid);
publishProgress(new Object[] {channelRect, channel.dBm});
try
{
Thread.sleep(1000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(Object[]... values)
{
ChannelRect channelRect = (ChannelRect) values[0][0];
int value = (Integer)values[0][1];
channelRect.setValue(value);
channelRect.Animate();
}
}.execute(channels);
}
Version 2:
public void UpdateChannels(final ArrayList<Channel> channels)
{
new Thread(new Runnable()
{
#Override
public void run()
{
for(final Channel channel : channels)
{
int chid = new Integer(channel.arfcn);
final ChannelRect channelRect = Channels.get(chid);
if(channelRect != null)
{
CurrentActivity.runOnUiThread(new Runnable()
{
#Override
public void run()
{
channelRect.setValue(channel.dBm);
channelRect.Animate();
}
});
try
{
Thread.sleep(500);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}).start();
}
And i tried same thing with post and Handler. Same thing :( When Thread.sleep(500) arrives animation freezes :(
What i'm doing wrong?
UPDATE 1
Found heavy place that slows UI thread:
I have horizontal LinearLayout that stores 175 ChannelRect views. When i call animate (that starts AlphaAnimation all okay, but when i setValue() to ChannelRect it must change his size:
public void setValue(int value)
{
_value = value;
setOpacity((byte)255);
CurrentLayoutParams.height = (int)((-110 - value) * ycoeff * -1);
requestLayout();
}
This is slow downs main UI thread. Why? setValue affects only one view in this LinearLayout not all...
p.s. removing requestLayot() line remove slowdowns but ChannelRect size not changes :(
The reason why animation freezes it's a requestLayout() in ChannelRect.
Main LinearLayout that contains many (175+) custom views trying to redraw every children on requestLayout() called from updated child.
I'm changed my ChannelRect for drawing rectangle inside view and now calling Invalidate() instead requestLayout():
public void setValue(int value)
{
_value = value;
setOpacity((byte)255);
invalidate();
}
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawRect(0,getHeight() - (int)((-110 - _value) * ycoeff * -1),size,getHeight(), pnt);
}