I've been working on a sort of a game engine for Android but I have some serious stability issues.
I use a SurfaceView with an implemented Runnable for Multithreading, to get some work of my main / UI thread. The simplified version of this class is shown below:
private class MenuView extends SurfaceView implements Runnable
{
Thread T = null;
SurfaceHolder holder;
Canvas c;
boolean run;
public MenuView(Context context) {
super(context);
holder = getHolder();
}
#Override
public void run() {
while(run)
{
if(!holder.getSurface().isValid())
{
continue;
}
c = holder.lockCanvas();
//Doing updates and drawing stuff
holder.unlockCanvasAndPost(c);
}
}
public void pause()
{
run = false;
try
{
T.join();
}
catch (InterruptedException e)
{
Log.e("Error:", "joining thread failed!");
}
}
public void resume()
{
run = true;
T = new Thread(this);
T.start();
}
//bunch of helper methods with no effect on this problem :(
}
So as you can see, my "Worker" thread is caught in an infinite while loop that is controlled by the "run" boolean, and this boolean is set using pause() and resume() methods called in onPause() and onResume() methods of my activity as shown here:
MenuView menuV;
Random Rand = new Random();
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Screen = getRealScreenSize(getApplicationContext());
menuV = new MenuView(this);
menuV.setOnTouchListener(this);
logoV = new LogoView(this);
setContentView(logoV); Log.d("APP", "SET LOGO VIEW");
//^ This is my splash screen, again irrelevant to the issue, after few seconds of running, this view calls the resume() of my SurfaceView and sets it as the contentView.
}
#Override
protected void onPause() {
super.onPause();
Log.d("APP", "ON PAUSE");
menuV.pause();
}
#Override
protected void onResume() {
super.onResume();
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
//^hides the goddamn navigation bar...
Log.d("APP", "ON RESUME");
menuV.resume();
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
Log.d("APP", "ON TOUCH DOWN");
for(int i = 0; i < menuV.button.length ; i++)
{
if(menuV.button[i].active)
{
if(event.getX() > menuV.button[i].pos.left && event.getX() < menuV.button[i].pos.right && event.getY() > menuV.button[i].pos.top && event.getY() < menuV.button[i].pos.bottom)
{
menuV.button[i].pressed = true;
return true;
}
}
}
}
else if(event.getAction() == MotionEvent.ACTION_UP)
{
Log.d("APP", "ON TOUCH UP");
for(int i = 0; i < menuV.button.length ; i++)
{
menuV.button[i].pressed = false;
}
}
return false;
}
So my touch event is handled by the UI thread, but it's accesing info that the worker thread is constantly using, the same thing happens in onPause() and onResume(), the UI thread is changing the "run" boolean which is always used by the worker thread, I'm saying this because it seems that the ANRs are happening when touching the screen and when power-off-ing and power-on-ing the phone. Rendering doesn't stop, it seems that the worker thread is just fine but the UI thread messes up badly, it stops responding and this line shows on my Android Monitor:
Thread[2,tid=18257,WaitingInMainSignalCatcherLoop,Thread*=0x558cbf1310,peer=0x12c470a0,"Signal Catcher"]: reacting to signal 3
Wrote stack traces to '/data/anr/traces.txt'
Please let me know if there is any further useful information - I will EDIT in anything you point out as important after this line, Thanks!
Try marking run variable as volatile:
volatile boolean run;
Maybe you have caching-related problem.
Related
java.lang.IllegalThreadStateException: Thread already started
at java.lang.Thread.checkNotStarted(Thread.java:849)
at java.lang.Thread.start(Thread.java:1059)
at com.name.MainActivity$MenuView$1.surfaceCreated(MainActivity.java:496)
How can I avoid this? I have an inner class MenuView which extends SurfaceView. It updates a canvas on a thread. onSurfaceCreated() I start the thread, and onSurfaceDestroyed() I call thread.join() to stop the thread. However, if the Google Play Sign in window pops up, and I press the home button while it is in focus, and reopen the app. I get a force close error and the exception that I posted above.
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder holder) {
allow_thread_to_run = true;
boolean retry = true;
while (retry) {
if (t!=null && !t.isAlive()) {
t.start();
retry = false;
}
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
allow_thread_to_run = false;
boolean retry = true;
if(t!=null && t.isAlive()) {
while (retry) {
try {
t.join();
retry = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
}
So why does this Google Play window coming into focus cause my thread to start twice. From what I see, my setup has the necessary check isAlive() to make sure to avoid this!
I agree with #adelphus's view.If you care for the code of Thread calss,you will find that.Every Thread object has a flag that reflects whether this Thread has already been started.
boolean hasBeenStarted = false;
Once you invoke start() method,it will check the state and change if it is false,otherwise throw an exception like what you have got.see,
public synchronized void start() {
checkNotStarted();
hasBeenStarted = true;
nativeCreate(this, stackSize, daemon);
}
later,the state will never be changed. When you invoke start() again,it will throw an exception.
I'm working on my first app for android - snake. Right now I'm trying to implement 'game over' for that app. So basically when the snake hits the wall, app should ask your name and put it witch your score on HiScore. Well i'm stuk on hitting the wall fragment. Im experimenting with threads and so far I've found no way to stop it without getting errors.
I googled a lot, and everyone is saying that thread.join(); is waiting for thread to end, so how long does it take to end the thread that is drawing simple squares once per half second? When im hitting the back button on my phone while playing, pause(); function works perfectly. Log "game has ended" appears on LogCat.
So the problem is that i cant stop this activity when snake hits the wall, Log "game has ended" never occurs. Why is that?
My code:
public class SnakeCage extends SurfaceView implements Runnable{
// blah blah .. functions that draw and stuff ..
public void pause() {
isRunning = false;
while(true){
try {
aThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
aThread = null;
Log.d("pause()","game has ended"); // <<<<<<<<<THIS ONE>>>>>>>>>
}
public void resume() {
isRunning = true;
aThread = new Thread(this);
aThread.start();
}
public void init(){
// blah blah...
}
private void gameOver() {
int pHeadX = snakeHead.posX;
int pHeadY = snakeHead.posY;
Log.d("gameOver()", "checking");
if(pHeadY<0 || pHeadX<0 || pHeadX>23 || pHeadY>19){
Log.d("gameOver()", "game now will end");
gameOver = true;
}
}
public void run() {
while (isRunning){
if(!aHolder.getSurface().isValid())
continue;
canvas = aHolder.lockCanvas();
// drawing
gameOver();
if(gameOver) break;
// more drawing
aHolder.unlockCanvasAndPost(canvas);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(gameOver){
pause();
}
}
and the activity class:
public class PlayingActivity extends Activity implements OnClickListener{
SnakeCage v;
Button snakeGoUp;
Button snakeGoDown;
Button snakeGoLeft;
Button snakeGoRight;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.frame_layout);
v = (SnakeCage)findViewById(R.id.sView);
// listeners and stuff
}
#Override
protected void onPause() {
super.onPause();
v.pause();
}
#Override
protected void onResume() {
super.onResume();
v.resume();
}
#Override
public void onClick(View view) {
switch(view.getId()){
case R.id.snakeUp:
v.move(1);
break;
case R.id.snakeDown:
v.move(2);
break;
case R.id.snakeLeft:
v.move(3);
break;
case R.id.snakeRight:
v.move(4);
break;
}
}
}
i just did:
Context context = getContext();
((PlayingActivity)context).finish();
from:
How can I end an activity from inside a SurfaceView class or nested thread
although it does not satisfy me... its enough for now
I would suggest using a LocalBroadcastManager to send a message to the Activity, to notify it to kill itself. You can have an inner class in the Activity to receive the broadcast and call a private method in the Activity which will end it.
public class MyActivity extends Activity {
public void onCreate(Bundle state) {
LocalBroadcastManager.getInstance(this).registerReceiver(new MessageHandler(),
new IntentFilter("kill"));
}
private void killActivity() {
finish();
}
public class MessageHandler extends BroadcastReceiver {
onReceive(Context context, Intent intent) {
killActivity();
}
}
Then in your SurfaceView all you need to do is:
Intent intent = new Intent("kill");
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
Its a little more code but its a lot cleaner IMHO.
This is the comple java class (GFXSurface). Inside this class, a second class is defined,
public class GFXSurface extends Activity implements OnTouchListener {
AnotherSurface ourSurfaceView;
float x, y;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
ourSurfaceView = new AnotherSurface(this);
ourSurfaceView.setOnTouchListener(this);
x = 0;
y = 0;
setContentView(ourSurfaceView);
}
#Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
ourSurfaceView.ourPause();
}
#Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
ourSurfaceView.ourResume();
}
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
x = event.getX();
y = event.getY();
return true;
}
/*------------------------Class within a class-------------------------------*/
public class AnotherSurface extends SurfaceView implements Runnable {
SurfaceHolder ourHolder;
Thread ourThread = null;
boolean isRunning;
public AnotherSurface(Context context) {
// TODO Auto-generated constructor stub
super(context); //not auto-generated; set it up manually
isRunning = false;
ourHolder = getHolder();
}
public void ourPause(){
isRunning = false;
while(true){
try {
ourThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
ourThread = null;
}
public void ourResume(){
isRunning = true;
ourThread = new Thread(this);
ourThread.start();
}
#Override
public void run() {
// TODO Auto-generated method stub
while(true){
if(!ourHolder.getSurface().isValid())
continue;
Canvas canvas = ourHolder.lockCanvas();
canvas.drawRGB(255, 0, 0);
if(x!=0 && y!=0){
Bitmap ourBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.green_ball);
canvas.drawBitmap(ourBitmap, x-(ourBitmap.getWidth()/2), y-(ourBitmap.getHeight()/2), null);
}
ourHolder.unlockCanvasAndPost(canvas);
}
}
}
}
Now, when I start the activity, it works as is required, it creates a bitmap at the place where the screen is clicked (centered at the point of click). The problem it gives is that, when I press the back key on my phone, the app stops responding and the phone gives an option to force shut the app. I think the it has got to do something with the "ourThread" thread not being joined properly.
Any idea where the problem lies? Thanks.
EDIT: I have actually found where I was going wrong. In the while loop:
while(true){
if(!ourHolder.getSurface().isValid())
continue;
Canvas canvas = ourHolder.lockCanvas();
canvas.drawRGB(255, 0, 0);
if(x!=0 && y!=0){
Bitmap ourBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.green_ball);
canvas.drawBitmap(ourBitmap, x-(ourBitmap.getWidth()/2), y-(ourBitmap.getHeight()/2), null);
}
I just changed "true" to "isRunning". And now the thread does end, and thus, the activity closes on pressing the back button. Thanks all for your valuable suggestions.
The problem is that, "ourThread" never stops when it is joined.
You have to realize that calling ourThread.join() only waits for the thread to finish -- it won't stop the thread. Your ourThread may be hung for some reason. A thread dump will show where it is running. Maybe it is stuck in a loop? Maybe it is waiting on a network connection?
If the join() returns successfully, by definition, the thread was was joined is no longer running. So the thread calling ourThread.join() will wait until the ourThread thread has completed.
If the thread is no longer running then maybe there are other threads running keeping your application open? A thread dump will show you what other non-daemon threads are still around. They will need to terminate before your application will stop.
In looking at your thread code, I don't see any way for you to exit the infinite loop unless a RuntimeException is thrown. When does the thread stop working? Did you mean to do a break; instead of a continue;?
while(true){
if(!ourHolder.getSurface().isValid())
continue; // should this be a break?
Canvas canvas = ourHolder.lockCanvas();
canvas.drawRGB(255, 0, 0);
ourHolder.unlockCanvasAndPost(canvas);
}
Now that you've added more code, you need to make isRunning be volatile so that multiple threads can update it and see the updates and you should change the while loop in your run() method to be:
private volatile boolean isRunning;
...
while(!isRunning){
I've written a AsyncTask:
public class AudioTransition extends AsyncTask <Void, Void, MediaPlayer>
{
private int goalID;
private int milliseconds;
private MediaPlayer tempPlayer;
AudioTransition(int ID, int Milliseconds)
{
goalID = ID;
milliseconds = (int)(((float)Milliseconds)/100);
}
#Override
protected void onPreExecute()
{
tempPlayer = MediaPlayer.create(context, goalID);
tempPlayer.setVolume(0, 0);
tempPlayer.setLooping(true);
tempPlayer.start();
}
#Override
protected MediaPlayer doInBackground(Void... arg0) {
for (int i = 0; i < 100; i++)
{
value = i;
publishProgress();
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (!player.isPlaying())
tempPlayer.pause();
}
return tempPlayer;
}
#Override
protected void onProgressUpdate(Void... v) {
super.onProgressUpdate(v);
player.setVolume(100-value, 100-value);
tempPlayer.setVolume(value, value);
}
#Override
protected void onPostExecute( MediaPlayer result ) {
super.onPostExecute(result);
player.reset();
player = tempPlayer;
player.setVolume(100,100);
transitioning = false;
}
}
But the volume doesn't fade out. It just starts both tracks, then stops. The MediaPlayers are not updated until doInBackground completes. How can I make the MediaPlayers get updated within this type of background worker? It seems like the publishProgress() thing should work.
Oh lord. Dont be sleeping threads inside of AsyncTask! You are completely misusing AsyncTask. You couldn't think of another way to do a timer type thing, so you're latching onto the idea of publishprogress from AsyncTask (which doesn't even work how I think you think it works) even though AsyncTask should be used for one thing and one thing only: doing heavy work off of the main (UI) thread.
If you just wanted to fade the volume out then use something like this: (this goes somewhere outside of any method).
private Runnable VolumeFadeRunnable = new Runnable() {
#Override
public void run() {
volume--;
player.setVolume(volume, volume);
if(volume>0)
handler.postDelayed(this, 1000);
else
handler.removeCallbacks(this);
}
};
just initialize your handler as a field inside of onCreate or whatever and make sure that and the counter variable are visible inside of that runnable.
I try to use this code to prevent multi-click in ImageView but it doesn't help.
Boolean isClicked = false;
#Override
public void onClick(View v)
{
if (v == imgClick && !isClicked)
{
//lock the image
isClicked = true;
Log.d(TAG, "button click");
try
{
//I try to do some thing and then release the image view
Thread.sleep(2000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
isClicked = false;
}
}
In the log cat, I can see 5 lines "button click" when I click on ImageView for 5 times as quickly as possible. I can see the log cat print the first line, wait for a while (2 seconds) and then print the next line. I think when I click the ImageView, the fired event is moved to queue in order, isn't it?. So how can I stop that?
I also try to use setEnable() or setClickable() instead of isClicked variable but it doesn't work too.
Just try this working code
Boolean canClick = true; //make global variable
Handler myHandler = new Handler();
#Override
public void onClick(View v)
{
if (canClick)
{
canClick= false; //lock the image
myHandler.postDelayed(mMyRunnable, 2000);
//perform your action here
}
}
/* give some delay..*/
private Runnable mMyRunnable = new Runnable()
{
#Override
public void run()
{
canClick = true;
myHandler.removeMessages(0);
}
};
Instead of sleeping in 2 seconds, I use some task like doSomeThing() method (has accessed UI thread), and I don't know when it completed. So how can I try your way?
//I referred this android link. You can handle thread more efficiently but i hope below code will work for you..
//you try this and
Boolean canClick = true; //make global variable
public void onClick(View v) {
if(canClick){
new DownloadImageTask().execute();
}
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
protected Bitmap doInBackground(String... urls) {
Log.d("MSG","Clicked");
canClick =false;
//perform your long operation here
return null;
}
protected void onPostExecute(Bitmap result) {
canClick =true;
}
}
You could keep track of the last consumed click upon your View, and based on it either perform the necessary actions, or simply return:
private long calcTime;
private boolean isClickedLately(final long millisToWait)
{
if (System.currentTimeMillis() - calcTime < millisToWait)
return true;
return false;
}
#Override
public void onClick(View v)
{
if (isClickedLately(2000))
return;
calcTime = System.currentTimeMillis();
Log.d(TAG, "consuming button click");
// perform the necessary actions
}
With the millisToWait parameter you can adjust the threshold of "waiting", but if you know that you want to wait exactly 2 seconds between two consecutive clicks, you can eliminate it.
This way you don't have to deal with Threads, which is good, since it's not a great idea to make the gui thread wait.