I have a Screen implementation with InputMultiplexer which is initialized in the show() method. The InputMultiplexer is initialized with InputAdapter and the Stage object.
The InputAdapter object listens for the back button.
class MyInputAdapter extends InputAdapter {
#Override
public boolean keyDown(int keycode) {
if (keycode == Keys.BACK) {
// do someting
return true;
}
return false;
}
}
class MyScreen implements Screen {
#Override
public void show() {
initInputProcessors();
}
private void initInputProcessors() {
if (backButtonInputProcessor != null) {
initInputMultiplexer();
Gdx.input.setCatchBackKey(true);
Gdx.input.setInputProcessor(inputMiltiplexer);
} else {
Gdx.input.setCatchBackKey(false);
Gdx.input.setInputProcessor(stage);
}
}
private void initInputMultiplexer() {
if (inputMiltiplexer == null) {
inputMiltiplexer = new InputMultiplexer();
inputMiltiplexer.addProcessor(backButtonInputProcessor);
inputMiltiplexer.addProcessor(stage);
}
}
}
All works fine, and the back button reacts without any problem.
The problem occurs, in the following scenario. I use admob. So when clicking an ad banner, this brings you to browser. When you are back from browser to the app, the back button is not intercepted and the application just exits.
I also tried calling the InitInputProcessors method inside the resume() method, same result.
The answer to my question on LibGDX forum has solved it. Following is the solution by skunktrader:
Try adding this to your android MainActivity
#Override
public void onResume() {
super.onResume();
theView.requestFocus();
theView.requestFocusFromTouch();
}
Where theView is the value returned from initializeForView().
Try to set your InputProcessor as null in hide() method. Like this:
#Override
public void hide() {
Gdx.input.setInputProcessor(null);
}
Related
I have a Timer in my App that infinitely runs an Animation. like this:
Timer t = new Timer();
t.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
//Running Animation Code
}
});
}
}, 1000, 1000);
Now I realized that this code runs even if user click Back Button of android. if fact it runs in the background and it seems uses a lot of memory.
I need this code run ONLY if user in the app. In fact when user click on Back Button, this Timer goes to end and if user clicks on Home Button, after a while that user doesn't use the App, terminates this Timer.
What I need is to prevent using memory. Because i realized if this codes runs a while, App freezes! I need a normal behavior.
If your Activity is the last element in the BackStack, then it will be put in the background as if you pressed the Home button.
As such, the onPause() method is triggered.
You can thus cancel your animation there.
#Override protected void onPause() {
this.timer.cancel();
}
You should as well start your animation in the onResume() method.
Note that onResume() is also called right after onCreate(); so it's even suitable to start the animation from a cold app start.
#Override protected void onResume() {
this.timer.scheduleAtFixedRate(...);
}
onPause() will be also called if you start another Application from your app (e.g: a Ringtone Picker). In the same way, when you head back to your app, onResume() will be triggered.
There is no need to add the same line of code in onBackPressed().
Also, what's the point in stopping the animation in onStop() or onDestroy()?
Do it in onPause() already. When your are app goes into the background, the animation will already be canceled and won't be using as much memory.
Don't know why I see such complicated answers.
You can do it like this, in onBackPressed() or onDestroy(), whatever suits you.
if (t != null) {
t.cancel();
}
If you need, you can start timer in onResume() and cancel it in onStop(), it entirely depend on you requirement.
If a caller wants to terminate a timer's task execution thread
rapidly, the caller should invoke the timer's cancel method. - Android Timer documentation
You should also see purge and
How to stop the Timer in android?
Disclaimer: This might not be the 100% best way to do this and it might be considered bad practice by some.
I have used the below code in a production app and it works. I have however edited it (removed app specific references and code) into a basic sample that should give you a very good start.
The static mIsAppVisible variable can be called anywhere (via your App class) in your app to check if code should run based on the condition that the app needs to be in focus/visible.
You can also check mIsAppInBackground in your activities that extend ParentActivity to see if the app is actually interactive, etc.
public class App extends Application {
public static boolean mIsAppVisible = false;
...
}
Create a "Parent" activity class, that all your other activities extend.
public class ParentActivity extends Activity {
public static boolean mIsBackPressed = false;
public static boolean mIsAppInBackground = false;
private static boolean mIsWindowFocused = false;
public boolean mFailed = false;
private boolean mWasScreenOn = true;
#Override
protected void onStart() {
applicationWillEnterForeground();
super.onStart();
}
#Override
protected void onStop() {
super.onStop();
applicationDidEnterBackground();
}
#Override
public void finish() {
super.finish();
// If something calls "finish()" it needs to behave similarly to
// pressing the back button to "close" an activity.
mIsBackPressed = true;
}
#Override
public void onWindowFocusChanged(boolean hasFocus) {
mIsWindowFocused = hasFocus;
if (mIsBackPressed && !hasFocus) {
mIsBackPressed = false;
mIsWindowFocused = true;
}
if (!mIsWindowFocused && mFailed)
applicationDidEnterBackground();
if (isScreenOn() && App.mIsAppVisible && hasFocus) {
// App is back in focus. Do something here...
// this can occur when the notification shade is
// pulled down and hidden again, for example.
}
super.onWindowFocusChanged(hasFocus);
}
#Override
public void onResume() {
super.onResume();
if (!mWasScreenOn && mIsWindowFocused)
onWindowFocusChanged(true);
}
#Override
public void onBackPressed() {
// this is for any "sub" activities that you might have
if (!(this instanceof MainActivity))
mIsBackPressed = true;
if (isTaskRoot()) {
// If we are "closing" the app
App.mIsAppVisible = false;
super.onBackPressed();
} else
super.onBackPressed();
}
private void applicationWillEnterForeground() {
if (mIsAppInBackground) {
mIsAppInBackground = false;
App.mIsAppVisible = true;
// App is back in foreground. Do something here...
// this happens when the app was backgrounded and is
// now returning
} else
mFailed = false;
}
private void applicationDidEnterBackground() {
if (!mIsWindowFocused || !isScreenOn()) {
mIsAppInBackground = true;
App.mIsAppVisible = false;
mFailed = false;
// App is not in focus. Do something here...
} else if (!mFailed)
mFailed = true;
}
private boolean isScreenOn() {
boolean screenState = false;
try {
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
screenState = powerManager.isInteractive();
} catch (Exception e) {
Log.e(TAG, "isScreenOn", e);
}
mWasScreenOn = screenState;
return screenState;
}
}
For your use you might want to create a method in your activity (code snippet assumes MainActivity) that handles the animation to call the t.cancel(); method that penguin suggested. You could then in the ParentActivity.applicationDidEnterBackground() method add the following:
if (this instanceof MainActivity) {
((MainActivity) this).cancelTimer();
}
Or you could add the timer to the ParentActivity class and then not need the instanceof check or the extra method.
I'm making a litle game in android studio where I want background music to play when the user is interacting with the application (and have it turned on in the settings). For this I used a service for playing backgroundmusic. As you can see below
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
public class BackgroundMusicService extends Service {
MediaPlayer player;
public IBinder onBind(Intent arg0) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
player = MediaPlayer.create(this, R.raw.backgroundmusic);
player.setLooping(true); // Set looping
}
public int onStartCommand(Intent intent, int flags, int startId) {
player.start();
return 1;
}
#Override
public void onDestroy() {
player.stop();
player.release();
}
#Override
public void onLowMemory() {
}
}
I want my backgroundmusic to play on all my activities, but stop when the user leaves the application, as it is impossible to detect a menu button press or a home button press in android. I tried to solve it like this.
import java.util.Timer;
import java.util.TimerTask;
public class LeaveAppDetector extends BackgroundMusicService{
private boolean Deactivated;
private int Time = 700;
private int wait = 0;
private Timer timer = new Timer();
public void Activate() {
Deactivated = false;
timer.schedule(new TimerTask() {
#Override
public void run() {
if (wait >= 1) {
if (!getDeactivate()) {
//app has been closed
StopPlaying();
StopTimer();
} else {StopTimer();} //app has not been closed
}
wait++;
}
}, Time);
}
public void Deactivate() { //call at the start of each activity
Deactivated = true;
}
private boolean getDeactivate() {
return Deactivated;
}
private void StopTimer() {
if (timer != null) {
timer.cancel();
timer.purge();
}
}
}
The idea is that when an activity starts, it calls Deactivate(); and when it closes, it calls Activate(); so that the value of Activated is updated after a short period of time. I added the following method to my BackgrondMusicService class in order to be able to turn it off remotely
public void StopPlaying() {
player.stop();
player.release();
}
Now the problem, it gives me the error that the reference to the mediaplayers in the method I just showed, is one referring to a null object, and I don't know why or why it won't work. Can someone help?
You need to use a Bound Service like this answer suggest:
https://stackoverflow.com/a/2660696/3742122
If you only want the service running while activities are using it, consider getting rid of startService(). Instead, use bindService() in the onStart() methods of the activities that need the service, and call unbindService() in their corresponding onStop() methods. You can use BIND_AUTO_CREATE to have the service be lazy-started when needed, and Android will automatically stop the service after all connections have been unbound.
Basing on the RoboGuice API for firing an event, inside my CustomButtonView implementation I have did like so:
#Override
public void onClick(View v) {
CommonApplication.getInstance().fireEvent(new InteractionButtonClicked());
// setSelected();
}
public class InteractionButtonClicked
{
public String getRef()
{
return (String)getTag();
}
}
// handle the click event
protected void handleClick(#Observes InteractionButtonClicked button) {
if (getTag().equals(button.getRef())) {
//do A
} else {
//do B
}
}
However, handleClick does not get called in this context=> when I set an #Observer in the main activity it's containing method does get called.
I'm trying to understand why, and if there is an option to observe the event in the Customview context...
I'm making a simple Android game written in Java.
I have my activity...
public class GameName extends Activity{
...
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
Inside the "main" layout I have a button that calls a method called "startTheGame":
public void startTheGame(View v) {
theGame = new Panel(this);
setContentView(theGame);
}
Here is the panel code (simplified)
class Panel extends SurfaceView implements SurfaceHolder.Callback {
public GameThread _thread;
public Panel(Context context) {
super(context);
getHolder().addCallback(this);
setFocusable(true);
}
...
#Override
public void surfaceCreated(SurfaceHolder holder) {
_thread = new GameThread(getHolder(), this);
_thread.setRunning(true);
_thread.start();
}
...
}
So as you can see I have a "GameThread" class that is started... here it is below:
class GameThread extends Thread {
private SurfaceHolder _surfaceHolder;
private Panel _panel;
private boolean _run = false;
public GameThread(SurfaceHolder surfaceHolder, Panel panel) {
_surfaceHolder = surfaceHolder;
_panel = panel;
}
public void setRunning(boolean run) {
_run = run;
}
#Override
public void run() {
Canvas c;
while (_run) {
c = null;
try {
...
if (health <= 0) {
_run = false;
//change views?
setContentView(R.layout.over);
}
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
_panel.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);
}
try {
Thread.sleep(60);
} catch (Exception e) {
}
}
}
}
Hopefully you can see that if the health is <= 0 it stops the thread and I am trying to change to another layout I created called "over".
It just stops the screen from updating (drawing) but I never see the new panel.
I've tried:
GameName cs = ((GameName)getApplicationContext())
cs.setContentView(R.layout.over);
But I'm getting a ClassCastException...
Please Help!
You cannot cast your ApplicationContext to your main Activity (GameName) because this is not the same object. In a certain way, getApplicationContext() does not correspond to the first lauched Activity but to the app itself.
To me you should try to have distinct activities instead of trying to change the layout and the behaviour of a single Activity. It would be more simple for you and it would avoid the kind of issues you are facing.
Actually, each setContentView() I see in the code should correspond to a switch.
This way, you would have something like:
WelcomeActivity (probably GameName here): select the options of the game and start
PlayingActivity: the game itself. This is where the actual gameplay is.
GameOverActivity: displays the score, or anything you would like to show once the game ended
This without knowing which kind of game you are doing, but this skeleton should work well with arcade/action, roleplay/adventure or even maze/god-games.
I'd recommend looking into using Handlers. It allows you to safely communicate with the UI thread without worrying which thread you're currently on.
I have an android app I am just experimenting things on and I cannot seem to figure out why my app force closes when I update a TextView via a while loop. When I comment out the updateText method it runs fine.
public class GameThread extends Thread {
Thread t;
private int i;
private boolean running;
private long sleepTime;
GameView gv;
public GameThread() {
t = new Thread(this);
t.start();
i = 0;
sleepTime = 1000;
}
public void initView(GameView v) {
this.gv = v;
}
public void setRunning(boolean b) {
this.running = b;
}
public boolean getRunning() {
return running;
}
public void run() {
while(running) {
i++;
update();
try {
t.sleep(sleepTime);
} catch(InterruptedException e) {
}
}
}
public void update() {
gv.setText(i); // when this is uncommented, it causes force close
Log.v("Semajhan", "i = " + i);
}
public class GameView extends LinearLayout {
public TextView tv;
public GameView(Context c) {
super(c);
this.setBackgroundColor(Color.WHITE);
tv = new TextView(c);
tv.setTextColor(Color.BLACK);
tv.setTextSize(20);
this.addView(tv);
}
public void setText(int i) {
tv.setText("i count: " + i);
}
public class Exp extends Activity {
GameThread t;
GameView v;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
v = new GameView(this);
setContentView(v);
t = new GameThread();
t.setRunning(true);
t.initView(v);
}
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (t.getRunning() == true) {
t.setRunning(false);
Log.v("Semajhan", "STOPPED");
} else {
t.setRunning(true);
Log.v("Semajhan", "RESTART");
}
}
return true;
}
protected void onDestroy() {
Log.v("Semajhan", "DESTROYING");
super.onDestroy();
}
protected void onStop() {
Log.v("Semajhan", "Stopping");
super.onStop();
}
I though i'd post the whole app since it is relatively small and so that I could get some help without confusion.
First, when you get a Force Close dialog, use adb logcat, DDMS, or the DDMS perspective in Eclipse to examine LogCat and look at the stack trace associated with your crash.
In this case, your exception will be something to the effect of "Cannot modify the user interface from a non-UI thread". You are attempting to call setText() from a background thread, which is not supported.
Using a GameThread makes sense if you are using 2D/3D graphics. It is not an appropriate pattern for widget-based applications. There are many, many, many, many examples that demonstrate how to create widget-based applications without the use of a GameThread.
You have to call it from the UI thread.
For more info check: Painless Threading .
If you decide to use a Handler, the easiest solution for you will be to:
Extend a View, override it's onDraw , in it draw the game objects, after you have calculated the game data for them first of course
The Handler: (in your Activity)
private Handler playHandler = new Handler() {
public void handleMessage(Message msg) {
gameView.postInvalidate(); // gameView is the View that you extended
}
};
The game thread has a simple
Message.obtain(playHandler).sendToTarget();
In 2 words, the View is responsible for the drawing (you can move the calculations in a separate class, and call it before the onDraw), the thread is responsible only for scheduled calls to the Handler, and the Handler is responsible only to tell the View to redraw itself.
You cannot update the UI of your app outside of the UI Thread, which is the 'main' thread you start in. In onCreate(Context) of you app, you are creating the game thread object, which is what is doing the updating of your UI.
You should use a Handler:
http://developer.android.com/reference/android/os/Handler.html