I'm trying to make a simple airplane game to build my skills and have some fun. But I've hit a roadblock with Threads, Loopers, and message queues when trying to pass a message from an onTouch event of a TextView to the Thread drawing my airplanes. I'm going to try to include only the essential bits of code below and use "..." to indicate omitted lines.
I'm drawing in a separate, good old-fashioned android thread. Here's the constructor of the Thread:
public class BenThread extends Thread {
...
public BenThread(SurfaceHolder surfaceHolder, Context context,
BenSurfaceView surfaceView) {
this.surfaceHolder = surfaceHolder;
this.context = context;
this.surfaceView = surfaceView;
this.isRunning = false;
Bitmap planeImage = BitmapFactory.decodeResource(
context.getResources(), R.drawable.fighters);
airplane = new Airplane(50, 50, 2, 0, planeImage);
}
Before I show the run method, note that there's a SurfaceView that creates and starts the Thread when the SurfaceChanged() is called. In the onCreate() of my main Activity, I create a final instance of my custom SurfaceView:
final BenSurfaceView surfaceView = (BenSurfaceView) findViewById(R.id.surfaceView);
In the UI layout, there a TextView sitting at bottom center with an OnTouchListener hooked up. In onTouch(), for MotionEvent.ACTION_DOWN, the following line is called:
surfaceView.thread.handler.sendEmptyMessage(1);
Back to the thread class, the handler for this empty message is created in the run method, along with the Looper creation:
public void run() {
super.run();
Looper.prepare(); // Creates a Message Queue for the thread
MessageQueue queue = Looper.myQueue();
queue.addIdleHandler(new IdleHandler() {
#Override
public boolean queueIdle() {
Looper.myLooper().quit();
return false;
}
});
handler = new Handler() {
#Override
public void handleMessage(Message msg) {
Log.i("HANDLING", "SOMETHING");
}
};
Looper.loop();
while (isRunning) {
currentTime = System.currentTimeMillis();
// spin in a while loop for a while
while ((currentTime - previousTime) < REFRESH_RATE) {
currentTime = System.currentTimeMillis();
}
airplane.move();
canvas = surfaceHolder.lockCanvas();
if (canvas != null) {
surfaceView.draw(canvas);
airplane.draw(canvas);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
Now if I run as is, I get a nice little animation of airplanes moving across the screen.
But, when I click the Button at the bottom, I see from the log that I sent a message to a dead thread. Well, I guess I killed it in the IdleHandler. So now let me comment out the quit method:
// Looper.myLooper().quit();
Now my app looks considerably less exciting:
But, when I click the Button at the bottom and look at the log, there is proof that my message has been handled! So the big question is, how can I run the message loop and still see my animation?
After you call Looper.loop(), it should not return until the thread is ready to stop. Having your game loop after the Looper.loop() call doesn't make sense. At that point the thread is "dead" in the sense that the Looper is no longer listening for messages.
If you want your thread to run in while (isRunning), do that. If you want it to be message-driven, do that. Don't try to do both in the same thread. (And please don't spin on the CPU -- eats up battery quickly on a mobile device.)
You can find some notes about game loops, and about SurfaceView and threading, in appendices A and B of this article. There are various examples of animated rendering using Handler in Grafika. For example, the "record GL app" activity uses Choreographer to send a message to the render thread whenever it's time to draw.
It is because of the IdleHandler(). It is executed immediately quitting you looper. I commented your code:
public void run() {
super.run();
Looper.prepare();
MessageQueue queue = Looper.myQueue();
queue.addIdleHandler(new IdleHandler() {
// This is executed immediately when the looper is idle.
// So this looper is quitted
// and thread starts to execute "while" loop
#Override
public boolean queueIdle() {
Looper.myLooper().quit();
return false;
}
});
handler = new Handler() {
#Override
public void handleMessage(Message msg) {
Log.i("HANDLING", "SOMETHING");
}
};
Looper.loop(); // Take into account that this function is blocking
// This "while" loop is executed after the looper is quitted from IdleHandler
// So why your game is running
// When you click your button it tries to send message to quitted looper
// and you get the corresponding error message
while (isRunning) {
currentTime = System.currentTimeMillis();
// spin in a while loop for a while
while ((currentTime - previousTime) < REFRESH_RATE) {
currentTime = System.currentTimeMillis();
}
airplane.move();
canvas = surfaceHolder.lockCanvas();
if (canvas != null) {
surfaceView.draw(canvas);
airplane.draw(canvas);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
When you commented the IdleHandler the thread executes Looper.loop() (it is blocking) method so the while loop is not being reached.
After the clarification from #fadden and #armansimonyah13, I dropped the misguided attempt to send messages between threads. In the main Activity, rather than sending a message to the thread like so:
surfaceView.thread.handler.sendEmptyMessage(1);
and trying to handle it in the thread, I simply update public data in the thread (from within the TextView's onTouch handler):
surfaceView.thread.airplane.setVelocity(8);
Now when you click the TextView at the bottom, the planes speed up. That's the kind of UI-thread to rendering-thread interactivity I was going for.
By the way, now the run loop looks like:
#Override
public void run(){
while (isRunning) {
airplane.move();
canvas = surfaceHolder.lockCanvas();
if (canvas != null) {
surfaceView.draw(canvas);
airplane.draw(canvas);
surfaceHolder.unlockCanvasAndPost(canvas);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Related
I have my main thread which is creating an instance of a new thread. this new thread creates an instance of a metronome. if I try to call the metronomes play() method through a button click on the main thread then the metronome starts but the whole app freezes.
main activity code:
public class HomeScreen extends Activity {
MetroThread metronome;
/*** onCreate ***/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home_screen);
metronome = new MetroThread();
metronome.start();
}
/*** button clicks ***/
public void st44BtnClick(View v)
{
if(metronome.myMetronome==null)
{
metronome.playMetronome();
}
else if(metronome.myMetronome!=null)
{
metronome.stopMetronome();
}
}
and the metronome thread code:
public class MetroThread extends Thread
{
//create instance of the metronome
public Metro myMetronome;
public void run()
{
System.out.println("metroThread started");
}
/*** play metronome ***/
public void playMetronome()
{
myMetronome = new Metro();
myMetronome.bpm = 200;
myMetronome.beat = 7;
myMetronome.beatSound = 2000;
myMetronome.sound = 2600;
myMetronome.play();
}
/*** stop metronome ***/
public void stopMetronome()
{
myMetronome.stop();
myMetronome = null;
}
}
if instead of using a button click to call the playMetronome method i simply call the method from the run() in the metronome thread, it works fine and does not lock up the app.
Just because a method is associated with a thread object doesn't mean that it is run on that specific thread. In fact in this case your Metronome thread has actually exited immediately, it ends as soon as you return from the run() method.
When you call the playMetronome method you are actually calling it from the button click thread.
What you need to do is send a signal to the Metronome thread that then causes it to start playing... or create a new Metronome and start the thread whenever they press the button and have the thread always play the sound.
That's correct. The methods are executed in Thread that's actually calling the Thread. Since you are calling those from the UI Thread, those are executed in the UI Thread. Try calling playMetronome from the run()'s method
Only code called from run() of your worker thread will really works in separate thread. Now you are creating second thread, but make all operations in main thread (because you are call play() from it)
You can:
1) add boolean flag isPlaying into your second thread and change it value from mainThread
2) start infinite loop in second thread's run() and sometimes check this flag here.
I am following this tutorial to have a loading screen in my program. The tutorial says my activity should Sleep() using the Sleep() command, however it does not recognize Sleep() as a function and provides me with an error, asking if I would like to create a method called Sleep().
Here is the code sample:
public class LoadingScreenActivity extends Activity {
//Introduce an delay
private final int WAIT_TIME = 2500;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
System.out.println("LoadingScreenActivity screen started");
setContentView(R.layout.loading_screen);
findViewById(R.id.mainSpinner1).setVisibility(View.VISIBLE);
new Handler().postDelayed(new Runnable(){
#Override
public void run() {
//Simulating a long running task
this.Sleep(1000);
System.out.println("Going to Profile Data");
/* Create an Intent that will start the ProfileData-Activity. */
Intent mainIntent = new Intent(LoadingScreenActivity.this,ProfileData.class);
LoadingScreenActivity.this.startActivity(mainIntent);
LoadingScreenActivity.this.finish();
}
}, WAIT_TIME);
}
}
You can use one of the folllowing methods:
Thread.sleep(timeInMills);
or
SystemClock.sleep(timeInMills);
SystemClock.sleep(milliseconds) is a utility function very similar to Thread.sleep(milliseconds), but it ignores InterruptedException. Use this function for delays if you do not use Thread.interrupt(), as it will preserve the interrupted state of the thread.
The function is Thread.sleep(long).
Note, however, that you should not perform a sleep on the UI thread.
The code you posted is horrible. Please don't use that on an actual device. You will get an "Application Not Responding" error if you run something similar to this.
If you're using Handlers, keep in mind that a Handler is created on the thread where it runs. So calling new Handler().post(... on the UI thread will execute the runnable on the UI thread, including this "long running operation". The advantage is that you can create a Handler to the UI Thread which you can use later, as shown below.
To put the long running operation into a background thread, you need to create a Thread around the runnable, as shown below. Now if you want to update the UI once the long running operation is complete, you need to post that to the UI Thread, using a Handler.
Note that this functionality is a perfect fit for an AsyncTask which will make this look a lot cleaner than the pattern below. However, I included this to show how Handlers, Threads and Runnables relate.
public class LoadingScreenActivity extends Activity {
//Introduce a delay
private final int WAIT_TIME = 2500;
private Handler uiHandler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
uiHandler = new Handler(); // anything posted to this handler will run on the UI Thread
System.out.println("LoadingScreenActivity screen started");
setContentView(R.layout.loading_screen);
findViewById(R.id.mainSpinner1).setVisibility(View.VISIBLE);
Runnable onUi = new Runnable() {
#Override
public void run() {
// this will run on the main UI thread
Intent mainIntent = new Intent(LoadingScreenActivity.this,ProfileData.class);
LoadingScreenActivity.this.startActivity(mainIntent);
LoadingScreenActivity.this.finish();
}
};
Runnable background = new Runnable() {
#Override
public void run() {
// This is the delay
Thread.Sleep( WAIT_TIME );
// This will run on a background thread
//Simulating a long running task
Thread.Sleep(1000);
System.out.println("Going to Profile Data");
uiHandler.post( onUi );
}
};
new Thread( background ).start();
}
use Thread.sleep(1000);
1000 is the number of milliseconds that the program will pause.
try
{
Thread.sleep(1000);
}
catch(InterruptedException ex)
{
Thread.currentThread().interrupt();
}
Keep in mind: Using this code is not recommended, because it is a delay of time but without control and may need more or less time.
I have an onClickListener that triggers a network call so I would like to have some way to show the user that communications are in progress. The problem I am running into is that I can't seem to throw up a ProgressDialog or change the UI in any way for that matter before the call is made inside the onClick Listener. All of the code works just fine, but the UI changes don't come into effect until after all the code in onClickListener runs.
I was wondering if my problem is simply that an anonymous inner class like an onclicklistener can only update the UI at the end of its run? Or maybe my code is just bad.
Thanks in advance
Below is the code for the onclick Listener :
relayButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
cPanel.throwProgress(NCDTCPRelayActivity.this);
System.out.println(tvSocketConnection.getText().toString());
if (relayStatusArray[relayNumber] == 0)
{
if (cPanel.TurnOnRelay(relayNumber, 1) == false)
{
changeTitleToRed();
}else{
changeTitleToGreen();
}
}
else {
if (cPanel.TurnOffRelay(relayNumber, 1) == false){
changeTitleToRed();
}else{
changeTitleToGreen();
}
}
cPanel.hideProgress(NCDTCPRelayActivity.this);
}
});
Here is the code for the throwProgress and hideProgress respectively (these are in a subclass of the activity):
public boolean throwProgress(Context mContext) {
System.out.println("INSIDE THROWPROGRESS");
try {
tempDialog = ProgressDialog.show(mContext, "Connecting", "Connecting", true);
}
catch (RuntimeException e) {
return false;
}
return true;
}
public boolean hideProgress(Context mContext) {
System.out.println("OUTSIDE THROWPROGRESS");
tempDialog.hide();
return true;
}
**Edit
Here is the new code for the onClickListener that I put the runnable in:
public void onClick(View v) {
cPanel.throwProgress(NCDTCPRelayActivity.this);
System.out.println(tvSocketConnection.getText().toString());
handler.post(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
if (relayStatusArray[relayNumber] == 0)
{
if (cPanel.TurnOnRelay(relayNumber, 1) == false)
{
changeTitleToRed();
}else{
changeTitleToGreen();
}
}
else {
if (cPanel.TurnOffRelay(relayNumber, 1) == false){
changeTitleToRed();
}else{
changeTitleToGreen();
}
}
cPanel.hideProgress(NCDTCPRelayActivity.this);
relayStatusArray = cPanel.getBankStatus(1);
updateButtonText();
}
});
}
Changing UI from your click handler should work just fine. The problem is likely that you're doing some heavy work on the UI thread and it's blocking it so that the dialog is not really updated until after all of that work is done. Try moving all the heavy lifting into an AsyncTask (read this doc if you're unfamiliar with it) hiding the dialog when the task is complete and see if that fixes it.
All the UI updates are delayed on Android, as well as on pretty much every GUI platform out there. Changes to the looks of a view are never rendered right away; instead, the GUI subsystem marks the view as "needs redraw", and calls draw() some time later on the message loop.
If you want something to take place after the screen has been updated, use Handler.post(). The post()'ed code will execute some time on the message loop, typically later than the queued draw code.
Aside node: Windows GUI is a happy exception to that rule; you can draw on a Windows window outside WM_PAINT. But on Android you can't.
I have implemnted progressbar in my small game like below
gameProgressBar =(ProgressBar)findViewById(R.id.GameProcess);
thrd = new Thread(progressBarThread);
thrd.start();
}
//Progress bar function
private Runnable progressBarThread = new Runnable(){
public void run() {
// TODO Auto-generated method stub
while (GameProgressCount<60){ //60 = 1 minute
try{
myHandle.sendMessage(myHandle.obtainMessage());
Thread.sleep(1000);
}
catch(Throwable t){
}
}
thrd.stop();
}
Handler myHandle = new Handler(){
#Override
public void handleMessage(Message msg) {
GameProgressCount++;
gameProgressBar.setProgress(GameProgressCount);
}
};
My question is that when i click on back/home button of the device, app is getting minimized (or goes previous screen) but the Progress bar thread would be running in the background. is it possible to pause the thread and resume when i minimize/open the app screen.
Thanks
You can pause the Thread in the OnPause method of the Activity and you can resume the Thread on OnResume method of the Activity which is pretty stright forward. Now if you wanted to Keep the GameProgressCount intact and wanted to Resume back from where it has left then you can use SharedPreference to store the value, when you resume back use the same value to start with.
I have written a function to create a splash screen with a 5 second timeout for my app.
The code works fine, but when the timeout reaches zero and I want to redirect to my main activity, the app crashes with the following error:
Only the original thread that created a view hierarchy can touch its views.
So I looked around a bit and someone suggested nesting this inside my function. It seems like a good Idea, but now methods like sleep / stop won't work.
My code is below, I can provide more / explain more in details if it isn't clear enough just let me know. Thanks for the help.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
showSplashScreen();
}
protected boolean _active = true;
protected int _splashTime = 5000; // Splash screen is 5 seconds
public void showSplashScreen() {
setContentView(R.layout.splash_layout);
// Thread splashThread = new Thread() {
runOnUiThread(new Runnable() {
#Override
public void run() {
try {
int waited = 0;
while (_active && (waited < _splashTime)) {
Thread.sleep(100);
if (_active) {
waited += 100;
}
}
} catch (InterruptedException e) {
// do nothing
} finally {
showApplication();
}
}
});
}
Probably not what you want to hear, but you should never put a splash screen on your mobile app. With the exception of games, when people use a mobile app they want to get in, do what ever it is they need to do, and get out. If you make that process take longer, people are just going to get frustrated with you app. You should probably reconsider just not using a splash screen.
This will perform sleep on the UI thread. That's never a good idea.
Why not something like this?
new Handler().postDelayed(new Runnable() {
public void run() {
// start application ...
}
}, _splashTime);
But this answer has a good point. Displaying a splash screen for 5 seconds can be very annoying.
I believe you want AsyncTask for this. The method called on completion of the task will be called on your UI thread, making modifying UI elements much easier.
Use a Handler to post an event to the UI thread that will remove the splash.
Code should be something like...
splash.show()
new Handler().postDelayed(
new Runnable() {
void run() {
splash.remove();
},
delayTime);
I suggest you to make new activity for your spalsh screen, show it in a regular way (with startActivityForResult) and place in it such code (in it, not in your main activity):
new Handler().postDelayed( new Runnable()
{
public void run()
{ finish(); }
}, 5000 );
Also you can handle in this new activity click events for giving opportunity to user to close it faster, tapping on it.