Android onResume called when screen turns off - android

First of all, I don't speak English, so please forgive me for my terrible grammar.
I try to create Android game, using GLSurfaceView.
I read "Beginning of Android Game" (the link is sample code) in Japanese and imitate its code, so my game almost works fine.
But I have a problem.
When android turns off the screen, onPause of the activity is called as expected.
But sometimes (not always) onResume is called and then onPause() is called again, although the screen is still black.
In onResume I call GLSurfaceView().resume, so onSurfaceCreated() and onSurfaceChanged() are also called.
After this, the game puts out ANR.
[Expected flow]
onPause - (turn on the screen) - onResume - onSurfaceCreated - onSurfaceChanged
[Problematic flow]
onPause - onResume - onPause - onSurfaceCreated - onSurfaceChanged - (turn on the screen) - ANR
Even in the problematic case, GLSurfaceView.onDrawFrame is called normally, and the game is being rendered.
But it doesn't accept any tap.Is there infinite loop?
I found similar questions here and here, for example.
But I think there is no configuration Change.
In Android Manifest,
android:configChanges="keyboard|keyboardHidden|orientation|screenSize
was already declared.
And I can't move my code into Application class because I want to do something (e.g. reload some textures) in onSurfaceCreated method.
So my question is...
Why is the onResume method called?
Why it causes ANR?
And How can I avoid this ANR?
EDIT: the problem was solved.I appreciate it!
Here is my activity.
public abstract class GLGame extends Activity implements Game, Renderer {
enum GLGameState {
Initialized,
Running,
Paused,
Finished,
Idle
}
/////////////////////////////inter-ad
private static class AdHandler extends Handler {
private final WeakReference<GLGame> mActivity;
AdHandler(GLGame activity) {
mActivity = new WeakReference<GLGame>(activity);
}
#Override
public void handleMessage(Message msg) {
GLGame activity = mActivity.get();
switch(msg.what){
case 0:
if (activity != null) {
activity.showInterstitial();
}
break;
case 1:
if (activity != null) {
activity.prepareInterstitial();
}
break;
}
return;
}
};
Handler handler =new AdHandler(this);
/////////////////////////////inter-ad
//ad
static InterstitialAd interstitial;
static AdRequest adRequest;
AdView adView;
//////////////////////////////// //ad
GLSurfaceView glView;
GLGraphics glGraphics;
Audio audio;
Input input;
FileIO fileIO;
Screen screen;
GLGameState state = GLGameState.Initialized;
Object stateChanged = new Object();
long startTime = System.nanoTime();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
/////////////////////////////calculate and normalize the view size
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
float density = displaymetrics.densityDpi/160f;
int adpix = (int)( 50*density);
int navvarpix = (int)( 25*density);
int heightSubAd = displaymetrics.heightPixels -navvarpix-adpix;
int ratioWidth=10;
int ratioHeight=16;
boolean isWidthLong = displaymetrics.widthPixels/(float)heightSubAd > ratioWidth/(float)ratioHeight;
int width = isWidthLong ? heightSubAd * ratioWidth / ratioHeight : displaymetrics.widthPixels;
int height = isWidthLong ? heightSubAd : displaymetrics.widthPixels * ratioHeight/ratioWidth;
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(width, height);
lp.setMargins((displaymetrics.widthPixels-width)/2, 0, 0, (displaymetrics.widthPixels-width)/2);
///////////////////////////// //calc
glView = new GLSurfaceView(this);
glView.setLayoutParams(lp);
glView.setEGLConfigChooser(8 , 8, 8, 8, 16, 0);
glView.setRenderer(this);
adView = new AdView(this, AdSize.BANNER, "MY_NUMBER");
lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, adpix);
lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
adView.setLayoutParams(lp);
RelativeLayout layout = new RelativeLayout(this);
layout.setBackgroundColor(Color.BLACK);
layout.setId(0);
layout.addView(glView);
layout.addView(adView);
adView.loadAd(new AdRequest());
setContentView(layout);
/////////////////////////////////request inter-ad
interstitial = new InterstitialAd(this, "MY_NUMBER");
adRequest = new AdRequest();
adRequest.addTestDevice("MY_NUMBER");
adRequest.addTestDevice("MY_NUMBER");
interstitial.loadAd(adRequest);
/////////////////////////////////inter-ad
glGraphics = new GLGraphics(glView);
fileIO = new AndroidFileIO(getAssets());
audio = new AndroidAudio(this);
input = new AndroidInput(this, glView, 1, 1);
}
public void onResume() {
super.onResume();
glView.onResume();
}
#Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
glGraphics.setGL(gl);
synchronized(stateChanged) {
if(state == GLGameState.Initialized)
screen = getStartScreen();
state = GLGameState.Running;
screen.resume();
startTime = System.nanoTime();
}
}
#Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
}
#Override
public void onDrawFrame(GL10 gl) {
GLGameState state = null;
synchronized(stateChanged) {
state = this.state;
}
if(state == GLGameState.Running) {
float deltaTime = (System.nanoTime()-startTime) / 1000000000.0f;
startTime = System.nanoTime();
screen.update(deltaTime);
screen.present(deltaTime);
}
if(state == GLGameState.Paused) {
screen.pause();
synchronized(stateChanged) {
this.state = GLGameState.Idle;
stateChanged.notifyAll();
}
}
if(state == GLGameState.Finished) {
screen.pause();
screen.dispose();
synchronized(stateChanged) {
this.state = GLGameState.Idle;
stateChanged.notifyAll();
}
}
}
#Override
public void onPause() {
synchronized(stateChanged) {
if(isFinishing())
state = GLGameState.Finished;
else
state = GLGameState.Paused;
while(true) {
try {
stateChanged.wait();
break;
} catch(InterruptedException e) {
}
}
}
glView.onPause();
super.onPause();
}
public GLGraphics getGLGraphics() {
return glGraphics;
}
#Override
public Input getInput() {
return input;
}
#Override
public FileIO getFileIO() {
return fileIO;
}
#Override
public Graphics getGraphics() {
throw new IllegalStateException("We are using OpenGL!");
}
#Override
public Audio getAudio() {
return audio;
}
#Override
public void setScreen(Screen screen) {
if (screen == null)
throw new IllegalArgumentException("Screen must not be null");
this.screen.pause();
this.screen.dispose();
screen.resume();
screen.update(0);
this.screen = screen;
}
#Override
public Screen getCurrentScreen() {
return screen;
}
#Override
public Handler getHandler(){
return handler;
}
private void showInterstitial(){
if(interstitial.isReady()){
interstitial.show();
}
}
private void prepareInterstitial(){
interstitial.loadAd(adRequest);
}
}
thank you for your kindness!

Related

Why is game over screen lagging?

I have done a game in Android Studio with the goal of avoiding falling objects.
Generally the app is running good, but for some reason when i get to the game over screen and press an edittext to add highscore the game experience a lot of stutter and lag (with the keyboard und keypresses).
I have already called finish() (which can be seen in "FishView") on my main activity so I don't understand how it can be so slow on the game over screen, as it shouldn't have to worry about anything but the game over screen once it's there and the game over screen is very simple.
I'm having a hard time locating where the problem comes from, hence why I'm asking for help here.
Here is some code that I hope is sufficent for locating the problem:
MainActivity (deals with animation, level increase, spawn objects and interaction between objects and rules)
public class MainActivity extends AppCompatActivity implements GarbageListener {
//global variable of FishView
private FishView gameView;
//handle animation task
private final Handler handler = new Handler();
//global variable of screen
private RelativeLayout screen;
//time before level update
private int levelChangeTime = 3; //initialize small garbage in X seconds
private int spawnBossGarbage = 25; //initialize big garbage in X seconds
private int spawnHeart = 40; //initialize heart in X seconds
//pause variables
private Button pauseButton;
private boolean pauseFlag = false;
//left and right button
private Button leftButton;
private Button rightButton;
//List of small garbage on screen
private final List<SmallGarbage> smallGarbages = new ArrayList<>();
//List of big garbage on screen
private List<BigGarbage> bigGarbages = new ArrayList<>();
//List of heart on screen
private List<LifePoint> lifePoints = new ArrayList<>();
//create timer for animation and level increase
private Timer mainTimer;
//create timer fro holding left or right
private Timer movingLeft;
private Timer movingRight;
private final boolean buttonIsPressed = false; //so players can't hold both buttons down
private final int holdMovementPeriod = 9;
//keep track of song
public static Intent themeSong;
//keep track of how far we are in the song, serviceStop() deletes everything in service ThemeSong so variable must be saved elsewhere
public static int lengthOfSong = 0;
public static boolean backButtonPressed = false; //check if backButton was pressed in service ThemeSong oonDestroy() since that's the last thing that is run
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
themeSong=new Intent(this, ThemeSong.class);
startService(themeSong); //OR stopService(svc);
leftButton = findViewById(R.id.leftArrow);
rightButton = findViewById(R.id.rightArrow);
screen = findViewById(R.id.gameScreen);
gameView = new FishView(this);
screen.addView(gameView);
pauseButton = findViewById(R.id.pauseButton);
mainTimer = new Timer();
createNewAnimationTask();
createNewLevelTask();
//create listeners fo holding left or right button
findViewById(R.id.leftArrow).setOnTouchListener(new View.OnTouchListener() {
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
holdLeft();
rightButton.setEnabled(false);}
if (event.getAction() == MotionEvent.ACTION_UP) {
rightButton.setEnabled(true);
if (movingLeft!=null){
movingLeft.cancel();
}}
return false;}
});
findViewById(R.id.rightArrow).setOnTouchListener(new View.OnTouchListener() {
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
holdRight();
leftButton.setEnabled(false);}
if (event.getAction() == MotionEvent.ACTION_UP) {
leftButton.setEnabled(true);
if (movingRight!=null){
movingRight.cancel();}}
return false;}
});
}
public void moveLeft(#SuppressWarnings("unused") View v){
if (buttonIsPressed){return;}
gameView.setLeftPressed(true);
gameView.leftFishAnimation();//before running the animations we first set which fish animations to run (left or right)
gameView.invalidate();
}
public void moveRight(#SuppressWarnings("unused") View view) {
if (buttonIsPressed){return;}
gameView.setRightPressed(true);
gameView.rightFishAnimation();
gameView.invalidate();
}
public void pauseGame(View v){
String resume = "Resume";
String pause = "Pause";
if (!pauseFlag){
stopService(themeSong); //turn of music
pauseFlag = true;
pauseButton.setText(resume);
pauseButton.setBackgroundResource(R.drawable.roundbuttonred);
//disable animation and level tasks
mainTimer.cancel();
//disable all falling garbage on screen
for (SmallGarbage smallGarbage : smallGarbages) {smallGarbage.disableGarbageTimer();}
for (BigGarbage bigGarbage : bigGarbages) {bigGarbage.disableGarbageTimer();}
for (LifePoint lifePoint : lifePoints) {lifePoint.disableGarbageTimer();}
//disable buttons
leftButton.setEnabled(false);
rightButton.setEnabled(false);
}
else{
startService(themeSong); //start music
pauseFlag=false;
pauseButton.setText(pause);
leftButton.setEnabled(true);
rightButton.setEnabled(true);
pauseButton.setBackgroundResource(R.drawable.roundbuttonblue);
//resume falling garbage
for (SmallGarbage smallGarbage : smallGarbages) {smallGarbage.startFallingGarbage();}
for (BigGarbage bigGarbage : bigGarbages) {bigGarbage.startFallingGarbage();}
for (LifePoint lifePoint : lifePoints) {lifePoint.startFallingGarbage();}
//resume animation and level increase
mainTimer = new Timer();
createNewAnimationTask();
createNewLevelTask();
}
}
private void createNewAnimationTask(){
TimerTask newAnimationTask = new TimerTask() {
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
//here we set the animation
int selectedFish = gameView.getSelectedFish();
selectedFish ++;
if (selectedFish==2){
selectedFish = 0;}
gameView.setSelectedFish(selectedFish);
//update screen
gameView.invalidate();
}
});
}
};
long animationPeriod = 600;
mainTimer.scheduleAtFixedRate(newAnimationTask, 0, animationPeriod);
}
private void createNewLevelTask(){
TimerTask levelCountDown = new TimerTask(){
#Override
public void run() {
levelChangeTime--;
spawnBossGarbage--;
spawnHeart--;
if (levelChangeTime==0 || spawnBossGarbage == 0 || spawnHeart == 0){
//move task that updates the UI onto the main thread
runOnUiThread(new Runnable() { //this tells the program to run this on the UI(aka main) thread, we could also call on new Thread if wanted to start new thread
#Override
public void run() {
if (levelChangeTime==0){generateNewGarbage("smallGarbage");}
if (spawnBossGarbage==0){generateNewGarbage("bigGarbage");}
if (spawnHeart==0){generateNewGarbage("lifePoint");}// when this is added we can't lose life?
}
});
}
}
};
mainTimer.scheduleAtFixedRate(levelCountDown,0,1000);
}
private void holdLeft(){
movingLeft = new Timer();
final View v = new View(this); //create view so moveLeft() can called
TimerTask holdLeftTask = new TimerTask(){
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
moveLeft(v);
}
});
}};
movingLeft.scheduleAtFixedRate(holdLeftTask,0,holdMovementPeriod);
}
private void holdRight(){
movingRight = new Timer();
final View v = new View(this);
TimerTask holdRightTask = new TimerTask(){
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
moveRight(v);
}
});
}};
movingRight.scheduleAtFixedRate(holdRightTask,0,holdMovementPeriod);
}
private void generateNewGarbage(String garbage){
switch (garbage){
case "bigGarbage":
spawnBossGarbage = 40; //time to next spawn
BigGarbage newBigGarbage = new BigGarbage(MainActivity.this);
newBigGarbage.setListener(MainActivity.this);
bigGarbages.add(newBigGarbage);
screen.addView(newBigGarbage);
break;
case "smallGarbage":
levelChangeTime = new Random().nextInt(20)+3; //set seconds between 3 and 20 at random
//this create SmallGarbage and initialize its task
SmallGarbage newGarbage = new SmallGarbage(MainActivity.this);
newGarbage.setListener(MainActivity.this); // set listener for garbage
smallGarbages.add(newGarbage);
screen.addView(newGarbage);
break;
case "lifePoint":
spawnHeart=30; //time to next spawn
//this create SmallGarbage and initialize its task
LifePoint newLifePoint = new LifePoint(MainActivity.this);
newLifePoint.setListener(MainActivity.this); // set listener for garbage
lifePoints.add(newLifePoint);
screen.addView(newLifePoint);
break;
}
}
//here starts the GarbageListener
#Override
public void handleAvoidedGarbage(String avoidedGarbage) {
gameView.avoidedGarbage(avoidedGarbage);
}
#Override
public boolean handleHitPlayer(int x, int y, String garbageType) {
return gameView.hitWasteChecker(x,y, garbageType);
}
#Override
public void handleLoseLife() {
gameView.loseLife();
}
//empty lives on screen, once they have landed or hit player
#Override
public void emptyLifePointList(){
lifePoints.clear();
lifePoints = new ArrayList<>();
}
//empty big garbage on screen, once they have landed or hit player
#Override
public void emptyBigGarbageList(){
bigGarbages.clear();
bigGarbages = new ArrayList<>();
}
//saving and setting length of played song
public static int getLengthOfSong() {
return lengthOfSong;
}
public static void setLengthOfSong(int lengthOfSong) {
MainActivity.lengthOfSong = lengthOfSong;
}
//onStop runs AFTER onBackPressed(), so lengthOfSong must be reset there
#Override
public void onBackPressed() {
super.onBackPressed();
backButtonPressed = true;
}
public static boolean isBackButtonPressed() {
return backButtonPressed;
}
public static void setBackButtonPressed(boolean backButtonPressed) {
MainActivity.backButtonPressed = backButtonPressed;
}
//this runs whenever the app is closed
#Override
protected void onStop(){
super.onStop();
//stop music
stopService(themeSong);
setLengthOfSong(0);
//pause game, this will also reset sound upon start
final View v = new View(this);
pauseFlag = false;
pauseGame(v);
}
}
FishView (deals with creating the player, rules and HANDELING the starting the game over screen)
public class FishView extends View {
private final Bitmap[] fish = new Bitmap[3];
private final Bitmap gameBackground;
private final Bitmap[] lifePoints = new Bitmap[2];
private int selectedFish;
private final Paint scorePaint = new Paint();
private int score, fishLives;
private static final int fishY = 1200;
private int fishX = 400;
private int speedX = 0;
private boolean leftPressed = false;
private boolean rightPressed = false;
public FishView(Context context) {
super(context);
//set background
gameBackground = BitmapFactory.decodeResource(getResources(),R.drawable.underwater);
//set default/start fish animations
leftFishAnimation();
//set selected fish animation to default start on 0
selectedFish = 0;
//set life points
lifePoints[1] = BitmapFactory.decodeResource(getResources(),R.drawable.lifepoint);
lifePoints[0] = BitmapFactory.decodeResource(getResources(),R.drawable.deadlife);
//set score
scorePaint.setColor(Color.WHITE);
scorePaint.setTextSize(80);
// scorePaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD)); //??
scorePaint.setAntiAlias(true); //(graphic improvement) this removes the staircase effect aka make smoother
scorePaint.setTypeface(Typeface.SERIF);
score = 0;
//set fish lives
fishLives = 3;
}
//in a View, the onDraw method is called whenever:
//the view is initially drawn or whenever invalidate() is called on the view
//in our case we call on the constructor which initially the View
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//should maybe be canvas.getWidth() here
int canvasWidth=getWidth();
//set game boundaries
int minFishX = 0; //should not be able to go of screen (to the left)
int maxFishX = canvasWidth-fish[0].getWidth(); //furthers you can go to the right (to the right)
//check boundaries
if (fishX < minFishX) {
fishX = minFishX;
}
if (fishX > maxFishX) {
fishX = maxFishX;
}
//set position dependent on speed
fishX += speedX;
//draw background
canvas.drawBitmap(gameBackground, 0, 0, null);
//this draws the bitmap we decoded from the image
if (leftPressed){
speedX -= 15;
}
else if (rightPressed){
speedX += 15;
}
if (speedX != 0){
while (speedX != 0){
if (leftPressed){
fishX -= 1;
speedX += 1;
canvas.drawBitmap(fish[selectedFish],fishX,fishY,null);
invalidate();
}
else if (rightPressed){
fishX += 1;
speedX -= 1;
canvas.drawBitmap(fish[selectedFish],fishX,fishY,null);
invalidate();
}
}}
else{ //if nothing happens when we stay here
canvas.drawBitmap(fish[selectedFish],fishX,fishY, null);
}
leftPressed=false;
rightPressed=false;
//draw score
canvas.drawText("Score: " + score, 20 , 90, scorePaint);
//draw life points and life point we have lost
for (int lives = 0; lives < 3 ; lives++) {
int lifeX = 650 + 140*lives;
int lifeY = 10;
if (lives < fishLives){
canvas.drawBitmap(lifePoints[1],lifeX,lifeY,null);
}
else{
canvas.drawBitmap(lifePoints[0],lifeX,lifeY,null);
}
}
}
public boolean hitWasteChecker(int x, int y, String garbageType){
switch (garbageType){
//define hit boxes
//first check is how far above, second how much underneath, third how much to the left, and fourth how much to the right
case "smallGarbage":
return fishY <= y + 80 && fishY + fish[selectedFish].getHeight() >= y + 75 && fishX <= x + 75 && x + 20 <= (fishX + fish[selectedFish].getWidth());
case "bigGarbage":
return fishY <= y + 170 && fishY + fish[selectedFish].getHeight() >= y + 75 && fishX <= x + 180 && x + 20 <= (fishX + fish[selectedFish].getWidth());
case "lifePoint":
if (fishY <= y + 25 && fishY + fish[selectedFish].getHeight() >= y + 60 && fishX <= x + 110 && x + 35 <= (fishX + fish[selectedFish].getWidth())){
if (fishLives<3){fishLives++;
return true;} //if not full life gain a life
if (fishLives==3){score+=40; //if already full life then gain 40 points
return true;}}
return false;
default:
return false;
}}
public void loseLife(){
fishLives--;
if (fishLives<=0){
//stop theme song from playing
getContext().stopService(MainActivity.themeSong);
//through these lines a new Activity can be created from a View
Intent gameOverIntent = new Intent(getContext(), GameOverActivity.class);
gameOverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); //not possible to go back from game over screen
gameOverIntent.putExtra("final score", score); // send data to game over activity
getContext().startActivity(gameOverIntent);
((MainActivity) getContext()).overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
((MainActivity) getContext()).finish(); //TIMERS is till running
}
}
public void leftFishAnimation(){
fish[0] = BitmapFactory.decodeResource(getResources(),R.drawable.leftfish1);
fish[1] = BitmapFactory.decodeResource(getResources(),R.drawable.leftfish2);
}
public void rightFishAnimation(){
fish[0] = BitmapFactory.decodeResource(getResources(),R.drawable.rightfish1);
fish[1] = BitmapFactory.decodeResource(getResources(),R.drawable.rightfish2);
}
public void setLeftPressed(boolean leftPressed) {
this.leftPressed = leftPressed;
}
public void setRightPressed(boolean rightPressed) {
this.rightPressed = rightPressed;
}
public int getSelectedFish() {
return selectedFish;
}
public void setSelectedFish(int selectedFish) {
this.selectedFish = selectedFish;
}
public void avoidedGarbage(String avoidedGarbage){
switch (avoidedGarbage){
case "smallGarbage":
score += 10;
break;
case "bigGarbage":
score += 25;
break;
}
}
}
GameOver: (display the game over screen)
public class GameOverActivity extends AppCompatActivity {
//create instance of database
private DatabaseHelper db;
private EditText usernameInput;
private int score;
private MediaPlayer gameOverSound;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game_over);
score = Objects.requireNonNull(getIntent().getExtras()).getInt("final score");
usernameInput = findViewById(R.id.addUsername);
db = new DatabaseHelper(this);
//easier way of doing it
gameOverSound = MediaPlayer.create(this, R.raw.gameoversound);
gameOverSound.setVolume(0.2f,0.2f);
gameOverSound.start();
String yourFinalScore = "Your final score: " + score;
TextView finalScore = findViewById(R.id.finalScore);
finalScore.setText(yourFinalScore);
}
public void restartGame(View v){
Intent restartIntent = new Intent(this, MainActivity.class);
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); //so we can't go back to game over
startActivity(restartIntent);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); //transition between activities
finish(); //end this activity, MainActivity is already ended so can't only call on finish here to go back
}
public void backToStartMenu(View view) {
Intent startMenuIntent = new Intent(this, MenuActivity.class);
startActivity(startMenuIntent);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
finish(); //end this activity
}
public void addHighscore(View view) {
String writtenUsername = usernameInput.getText().toString();
if (!writtenUsername.equals("") && score != 0){
//insert writtenUsername and score into database
boolean insertedData = db.insertData(writtenUsername, score);
if (insertedData){
Toast.makeText(this, "Highscore was added", Toast.LENGTH_SHORT).show();
Intent startMenuIntent = new Intent(this, MenuActivity.class);
startActivity(startMenuIntent);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
finish();}
else{
Toast.makeText(this, "Highscore couldn't be added", Toast.LENGTH_SHORT).show();
}
}
}
//this runs whenever the app is closed, mobile arrow is pressed or we switch activity
#Override
protected void onStop(){
super.onStop();
gameOverSound.stop();
gameOverSound.release(); //solve error: if run twice the app will close because we cant release it twice
}
//if pressed mobile back button go back to start menu
#Override
public void onBackPressed() {
super.onBackPressed();
View v = new View(this);
backToStartMenu(v);
}
}
What I find weird about this problem is that the MainActivity, with the player animation and all the falling view objects, runs fine. However, something as small as the game over screen is lagging. This leaves me to believe that I somehow don't quit my Activities as I should which leads to the main thread not being able to handel it. Anyways thank you for your time! :)
Update:
I checked it and it seems to be some real problem with the transition from MainActivity to GameOver. Using Android help -> find-action -> profiler,
I was able to see that the memory useage was about 110 MB while in MainActivity and as soon as I got to the game over screen it went all the way up to 400 MB. But still I'm not able to locate why it occurs.
I figured it out. The problem was the sharp background image that I was using in my GameOver screen, apparently it was too much for the game too handle. So if you have any similar problem try changing the images and backgrounds on the screen.

Simple game works on tablet but sometimes lags on phone

I'm developing simple Android game, but I'm running into problems while testing it. When I run it on Lenovo Tab3 7 tablet (Android 5.0.1 ) or LG P880 phone (Android 4.0.3) it works fine. When I run it on Samsung S7 phone (Android 7.0) game usually runs fine. What I mean by this is that I can run it 10 times in a row with no problems, but sometimes game halts for 5-30 seconds or stops responding. This usually happens during starting of new Activity or very shortly after it.
Game has 4 Activities which use extended SurfaceView as layout. All SurfaceViews implement Runnable. Activities are: Splash screen (noHistory = "true" in Manifest), Menu, Difficulty choice and Game.
I use only mdpi drawables and scale them proportionally to all screen sizes. Bitmaps are loaded using BitmapFactory.decodeResource with BitmapFactory.Options inDensity = 1, inScaled = false.
When the problem occurs logcat shows only garbage collection. Sometimes game "pauses" (no taps are registered) for 5-30 seconds and resumes normally, sometimes it has to be restarted due to no response. I feel like game stops collecting input for some reason. Input is handled by overriding onTouchEvent and checking if ACTION_UP is within tapped image bounds. As I said, this happens only on S7 (I tried it on two phones), not on tablet or P880, so I'm thinking it might be something to do with Nougat or me forcing lower density on the phone.
So, since I'm running out of ideas what could be causing this and me being new to Android game development, does anyone know/have any idea where I should be looking for solution? Is there anything Nougat-specific I should be setting/checking? Does forcing pixel density affect device performance in any way?
Edit 1
globalApp
public class globalApp extends Application {
SoundPool soundPool;
SoundPool.Builder soundPoolBuilder;
AudioAttributes audioAttributes;
AudioAttributes.Builder audioAttributesBuilder;
int soundTap, soundCorrect, soundIncorrect, soundVictory, soundDefeat;
int soundBarrelVerySlow, soundBarrelSlow, soundBarrelNormal, soundBarrelFast, soundBarrelVeryFast;
#Override
public void onCreate() {
super.onCreate();
}
public void buildSoundPool(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
audioAttributesBuilder = new AudioAttributes.Builder();
audioAttributesBuilder.setUsage(AudioAttributes.USAGE_GAME);
audioAttributesBuilder.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION);
audioAttributes = audioAttributesBuilder.build();
soundPoolBuilder = new SoundPool.Builder();
soundPoolBuilder.setMaxStreams(2);
soundPoolBuilder.setAudioAttributes(audioAttributes);
soundPool = soundPoolBuilder.build();
}
else {
soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
}
}
public void loadSounds(){
soundBarrelVerySlow = soundPool.load(this,R.raw.very_slow_move, 1);
soundBarrelSlow = soundPool.load(this, R.raw.slow_move, 1);
soundBarrelNormal = soundPool.load(this, R.raw.slow_move, 1);
soundBarrelFast = soundPool.load(this,R.raw.fast_move, 1);
soundBarrelVeryFast = soundPool.load(this,R.raw.very_fast_move, 1);
soundTap = soundPool.load(this, R.raw.tap_sound, 1);
soundCorrect = soundPool.load(this, R.raw.correct, 1);
soundIncorrect = soundPool.load(this, R.raw.incorrect, 1);
soundVictory = soundPool.load(this, R.raw.victory, 1);
soundDefeat = soundPool.load(this, R.raw.defeat, 1);
}
public void playTap(){
soundPool.play(soundTap, 1, 1,1, 0, 1);
}
public void playCorrect(){
soundPool.play(soundCorrect, 1, 1,1, 0, 1);
}
public void playIncorrect(){
soundPool.play(soundIncorrect, 1, 1,1, 0, 1);
}
public void playVictory(){
soundPool.play(soundVictory, 1, 1,1, 0, 1);
}
public void playDefeat(){
soundPool.play(soundDefeat, 1, 1,1, 0, 1);
}
public void playBarrelVerySlow(){soundPool.play(soundBarrelVerySlow, 1, 1, 1, 0, 1);}
public void playBarrelSlow(){soundPool.play(soundBarrelSlow, 1, 1, 1, 0, 1);}
public void playBarrelNormal(){
soundPool.play(soundBarrelNormal, 1, 1,1, 0, 1);
}
public void playBarrelFast(){soundPool.play(soundBarrelFast, 1, 1, 1, 0, 1);}
public void playBarrelVeryFast(){soundPool.play(soundBarrelVeryFast, 1, 1, 1, 0, 1);}
}
MenuItem
public class MenuItem {
private Bitmap bmp;
private Context context;
private Rect sourceRect;
private RectF destRect;
private int srcWidth;
private int srcHeight;
private int destW, destH;
private int x, y;
private int screenH;
public MenuItem(Context ctx, String bmpName, int w, int x, int y, int sX, int sY){
context = ctx;
BitmapFactory.Options bmpFOptions = new BitmapFactory.Options();
bmpFOptions.inDensity = 1;
bmpFOptions.inScaled = false;
int res = context.getResources().getIdentifier(bmpName, "drawable", ctx.getPackageName());
bmp = BitmapFactory.decodeResource(ctx.getResources(), res, bmpFOptions);
srcWidth = w;
srcHeight = bmp.getHeight();
this.x = x;
this.y = y;
screenH = sY;
sourceRect = new Rect(0,0, srcWidth, srcHeight);
destRect = new RectF();
setProportionalDestinationRect(sX, sY);
}
private void setProportionalDestinationRect(int scrX, int scrY) {
if (scrX != 1024 || scrY != 552){
float propX = (float)scrX/1024;
float propY = (float)scrY/600;
// All drawables are designed for 1024x600 screen
// if device screen is different, scale image proportionally
destW = (int)(srcWidth * propX);
destH = (int) (srcHeight * propY);
x = (int) (x*propX);
y = (int) (y*propY);
}
else {
destW = srcWidth;
destH = srcHeight;
}
destRect.set(x,y, x+destW,y+destH);
}
public void update(){
}
public Bitmap getBmp() {
return bmp;
}
public void setBmp(Bitmap bmp) {
this.bmp = bmp;
}
public Rect getSourceRect() {
return sourceRect;
}
public void setSourceRect(Rect sourceRect) {
this.sourceRect = sourceRect;
}
public RectF getDestRect() {
return destRect;
}
public void setDestRect(RectF destRect) {
this.destRect = destRect;
}
public boolean contains(int x, int y){
if (destRect.left <= x && destRect.right >= x)
if (destRect.top <= y && destRect.bottom >= y)
return true;
return false;
}
public void setY(int y) {
this.y = y;
if (screenH != 552){
float propY = (float)screenH/600;
y = (int) (y*propY);
}
destRect.set(x,y, x+destW,y+destH);
}
}
MainActivity
public class MainActivity extends Activity {
private boolean backPressedOnce = false;
long backPressedTime = 0;
private MainActivitySurface mainActivitySurface;
globalApp app;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Setting full screen
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
decorView.setSystemUiVisibility(uiOptions);
int x = getIntent().getIntExtra("screenWidth", 500);
int y = getIntent().getIntExtra("screenHeight", 500);
app = (globalApp) getApplication();
app.buildSoundPool();
app.loadSounds();
mainActivitySurface = new MainActivitySurface(this, app, x, y);
mainActivitySurface.setParentActivity(MainActivity.this);
setContentView(mainActivitySurface);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1001) {
if (resultCode == RESULT_OK) {
int result = data.getIntExtra("difficulty", 3);
mainActivitySurface.setResultDifficulty(result);
}
}
}
#Override
protected void onPause() {
super.onPause();
mainActivitySurface.pause();
}
#Override
protected void onResume() {
super.onResume();
backPressedOnce = false;
mainActivitySurface.resume();
}
#Override
public void onBackPressed() {
if (backPressedOnce && backPressedTime + 2000 > System.currentTimeMillis()) {
Process.killProcess(Process.myPid());
System.exit(1);
} else {
Toast.makeText(this, "Press back again to exit.", Toast.LENGTH_SHORT).show();
backPressedOnce = true;
}
backPressedTime = System.currentTimeMillis();
}
}
MainActivitySurface
public class MainActivitySurface extends SurfaceView implements Runnable {
private Context context;
private SurfaceHolder surfaceHolder;
private Canvas canvas;
private Thread thread = null;
volatile private boolean running = false;
private boolean surfaceCreated = false;
private Intent playIntent;
private Intent difficultyIntent;
// Screen size
private int screenWidth, screenHeight;
//Menu items
private MenuItem menuItemPlay, menuItemDifficulty, middleBarrel, bg;
private int difficulty = 3;
private Activity parentActivity;
private globalApp app;
public MainActivitySurface(Context ctx, globalApp a, int scrW, int scrH){
super(ctx);
context = ctx;
screenHeight = scrH;
screenWidth = scrW;
app = a;
surfaceHolder = getHolder();
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder holder) {
surfaceCreated = true;
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
bg = new MenuItem(context, "main_activity_background_single", 1024, 0, 0, scrW, scrH);
menuItemPlay = new MenuItem(context, "menu_item_play_single", 233,(1024-233)/2,100, scrW, scrH);
menuItemDifficulty = new MenuItem(ctx, "menu_item_difficulty_single", 520,(1024 - 520)/2,400,scrW,scrH);
middleBarrel = new MenuItem(ctx, "middle_barrel_single", 323,(1024-323)/2,200,scrW,scrH);
playIntent = new Intent(context, GameActivity.class);
playIntent.putExtra("screenWidth", screenWidth);
playIntent.putExtra("screenHeight", screenHeight);
}
#Override
public void run() {
while (running){
draw();
}
}
private void draw() {
if(surfaceHolder.getSurface().isValid()){
canvas = surfaceHolder.lockCanvas();
canvas.drawBitmap(bg.getBmp(), bg.getSourceRect(), bg.getDestRect(), null);
canvas.drawBitmap(menuItemPlay.getBmp(), menuItemPlay.getSourceRect(), menuItemPlay.getDestRect(), null);
canvas.drawBitmap(menuItemDifficulty.getBmp(), menuItemDifficulty.getSourceRect(), menuItemDifficulty.getDestRect(), null);
canvas.drawBitmap(middleBarrel.getBmp(), middleBarrel.getSourceRect(), middleBarrel.getDestRect(), null);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
public void resume(){
running = true;
thread = new Thread(this);
thread.start();
}
public void pause(){
running = false;
boolean retry = false;
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
e.printStackTrace();
Log.d("info", "MainActivitySurface: Error joining thread");
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & event.ACTION_MASK){
case MotionEvent.ACTION_UP:
if (menuItemPlay.contains((int) event.getX(), (int) event.getY())){
app.playTap();
parentActivity.startActivity(playIntent);
parentActivity.overridePendingTransition(0,0);
break;
}
if (menuItemDifficulty.contains((int) event.getX(), (int) event.getY())){
app.playTap();
difficultyIntent = new Intent(parentActivity, DifficultyActivity.class);
difficultyIntent.putExtra("screenWidth", screenWidth);
difficultyIntent.putExtra("screenHeight", screenHeight);
difficultyIntent.putExtra("difficulty", difficulty);
parentActivity.startActivityForResult(difficultyIntent, 1001);
parentActivity.overridePendingTransition(0, 0);
break;
}
}
return true;
}
public void setParentActivity(Activity act){
parentActivity = act;
}
public void setResultDifficulty(int diff){
difficulty = diff;
playIntent.putExtra("difficulty", difficulty);
}
}
DifficultyActivity
public class DifficultyActivity extends Activity {
private DifficultySurface surface;
private globalApp app;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Setting full screen
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
decorView.setSystemUiVisibility(uiOptions);
app = (globalApp) getApplication();
surface = new DifficultySurface(this, app, getIntent().getIntExtra("screenWidth", 500), getIntent().getIntExtra("screenHeight", 500));
setContentView(surface);
}
#Override
protected void onPause() {
super.onPause();
app.soundPool.release();
surface.pause();
overridePendingTransition(0, 0);
}
#Override
protected void onResume() {
super.onResume();
app.buildSoundPool();
app.loadSounds();
surface.resume();
}
}
DifficultySurface
public class DifficultySurface extends SurfaceView implements Runnable {
private SurfaceHolder surfaceHolder;
private Thread thread = null;
private Canvas canvas;
private Context context;
private globalApp app;
private boolean surfaceCreated = false;
private boolean running = false;
private MenuItem bgProp, arrowBarrel, okButton, diffVeryEasy, diffEasy, diffNormal, diffHard, diffVeryHard;
private int difficulty;
public DifficultySurface(Context ctx, globalApp a, int scrW, int scrH){
super(ctx);
context = ctx;
app = a;
surfaceHolder = getHolder();
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder holder) {
surfaceCreated = true;
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
difficulty = ((Activity)context).getIntent().getIntExtra("difficulty", 3);
bgProp = new MenuItem(ctx, "difficulty_background", 1024, 0, 0, scrW, scrH);
diffVeryEasy = new MenuItem(ctx, "very_easy",796, 100, 100, scrW, scrH);
diffEasy = new MenuItem(ctx, "easy",796, 100, 200 , scrW, scrH);
diffNormal = new MenuItem(ctx, "normal",796, 100, 300, scrW, scrH);
diffHard = new MenuItem(ctx, "hard",796, 100, 400 , scrW, scrH);
diffVeryHard = new MenuItem(ctx, "very_hard",796, 100, 500, scrW, scrH);
okButton = new MenuItem(ctx, "ok_button", 100, 924, 500, scrW, scrH);
arrowBarrel = new MenuItem(ctx, "barrel_arrow", 100, 0, 100*difficulty, scrW, scrH);
}
#Override
public void run() {
while (running) {
if (surfaceCreated) {
update();
draw();
}
}
}
private void update() {
arrowBarrel.setY(difficulty*100);
}
private void draw() {
if (surfaceHolder.getSurface().isValid()){
canvas = surfaceHolder.lockCanvas();
canvas.drawBitmap(bgProp.getBmp(), bgProp.getSourceRect(), bgProp.getDestRect(), null);
canvas.drawBitmap(arrowBarrel.getBmp(), arrowBarrel.getSourceRect(), arrowBarrel.getDestRect(), null);
canvas.drawBitmap(diffVeryEasy.getBmp(), diffVeryEasy.getSourceRect(), diffVeryEasy.getDestRect(), null);
canvas.drawBitmap(diffEasy.getBmp(), diffEasy.getSourceRect(), diffEasy.getDestRect(), null);
canvas.drawBitmap(diffNormal.getBmp(), diffNormal.getSourceRect(), diffNormal.getDestRect(), null);
canvas.drawBitmap(diffHard.getBmp(), diffHard.getSourceRect(), diffHard.getDestRect(), null);
canvas.drawBitmap(diffVeryHard.getBmp(), diffVeryHard.getSourceRect(), diffVeryHard.getDestRect(), null);
canvas.drawBitmap(okButton.getBmp(), okButton.getSourceRect(), okButton.getDestRect(), null);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & event.ACTION_MASK){
case MotionEvent.ACTION_UP:{
if (diffVeryEasy.contains((int) event.getX(), (int) event.getY())){
app.playTap();
difficulty = 1; }
if (diffEasy.contains((int) event.getX(), (int) event.getY())){
app.playTap();
difficulty = 2;
}
if (diffNormal.contains((int) event.getX(), (int) event.getY())){
app.playTap();
difficulty = 3;
}
if (diffHard.contains((int) event.getX(), (int) event.getY())){
app.playTap();
difficulty = 4;
}
if (diffVeryHard.contains((int) event.getX(), (int) event.getY())){
app.playTap();
difficulty = 5;
}
if (okButton.contains((int)event.getX(), (int) event.getY())){
app.playTap();
((Activity)context).getIntent().putExtra("difficulty", difficulty);
((Activity)context).setResult(Activity.RESULT_OK, ((Activity)context).getIntent());
((Activity)context).finish();
((Activity)context).overridePendingTransition(0, 0);
}
break;
}
}
return true;
}
public void pause(){
running = false;
boolean retry = true;
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
((Activity)context).overridePendingTransition(0, 0);
}
public void resume(){
running = true;
thread = new Thread(this);
thread.start();
}
}
GameActivity
public class GameActivity extends Activity {
private GameSurface surface;
private globalApp app;
private int difficulty;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Setting full screen
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
decorView.setSystemUiVisibility(uiOptions);
difficulty = getIntent().getIntExtra("difficulty", 3);
app = (globalApp) getApplication();
surface = new GameSurface(this, app, getIntent().getIntExtra("screenWidth", 500), getIntent().getIntExtra("screenHeight", 500), difficulty);
surface.setParentActivity(this);
setContentView(surface);
}
#Override
protected void onPause() {
super.onPause();
app.soundPool.release();
surface.pause();
}
#Override
protected void onPostResume() {
super.onPostResume();
app.buildSoundPool();
app.loadSounds();
surface.resume();
}
#Override
protected void onStop() {
super.onStop();
surface.stop();
}
#Override
public void onBackPressed() {
super.onBackPressed();
finish();
}
}
Game halting happens either when I start DificultyActivity (I tap one MenuItem objects but nothing happens) or when I start GameActivity (game still shows MainActivity + MainActivitySurface).
Android Monitor show less than 40MB of allocated memory, so bitmaps shouldn't be the problem in my opinion. I tried recycling all bitmaps but the problem was present (that's why I opted to use only mdpi drawables; at first I used all pixel densities but tried lowering resources in case that was causing halts).
It is hard to find the problem without looking at the code. There's nothing nougat-specific way of handling the resources.
But android N claims to have a better memory management and since you are complaining a lot of garbage collections, it may be one of the cause. Make sure to recycle the unused bitmaps. And use RGB_565 as the preferred bitmap config which requires half memory than RGB_8888.
I have solved my problem. After posting question I came across this. It seems we had the same problem. When I slowed down drawing speed (using thread.sleep) there were no more issues.
Thanks to those who helped me.

Issue with getting GestureDetector to work with handleMessage

my app shows a bunch of bitmaps that animate one by one as it's stored in a Bitmap array and initially when app starts up curPosition is 0 to start animating obj at index 0. The handleMessage will animate each obj with a 1000ms delay between each obj when app starts up first time and so when app starts up first time handleMessage animates each obj normally.
The issue is once I double tap the restart button bitmap. I used the GestureDetector class and I have a Restart button constructed via a bitmap that when I listen for onDoubleTap and what should happen is the handleMessage will go thru each bitmap restarting animating them.
For some reason, when I double tap the Restart button bitmap, the handleMessage is running and i know this since I put in a logcat message and for that logcat message in update method I see it running thru each curPosition so I mean this line: Log.i ("cur pos. so far:", "" + curPosition );
but I don't visually see the animation running thru update method unless I interrupt app (i.e. close app OR minimize then resume app) and double tap the Restart button bitmap and the double tapping Restart button bitmap WORKS ONLY ONCE so I need to interrupt app to get it to work again.
public class MainActivity extends Activity implements
GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener {
private MySurfaceView mysurfaceView;
private Bitmap restart_buttonBitmap;
private GestureDetector mGestureDetector;
private MiniProj2Button restart;
private MiniProj2Button newLevel;
private final int SIZE = 5;
private final int DELAY = 1000;
private Bitmap[] myObjs = new Bitmap[ SIZE ];
//Screen size info
private int screenWidth;
private int screenHeight;
//The size in pixels of a place on the game board
private int blockSize;
private int numBlocksWide;
private int numBlocksHigh;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mySurfaceView = new MySurfaceView(this);
setContentView(mySurfaceView);
// Create a GestureDetector
mGestureDetector = new GestureDetector(this, this);
// Attach listeners that'll be called for double-tap and related gestures
mGestureDetector.setOnDoubleTapListener( this );
}//END METHOD: onCreate
#Override
protected void onStop() {
super.onStop();
Log.d("onStop", "Main Activity's onStop called");
gameView.surfaceDestroyed( mySurfaceView.getHolder() );
this.finish();
}
#Override
protected void onResume() {
super.onResume();
Log.d("onResume", "Main Activity's onResume called");
mySurfaceView = new MySurfaceView(this);
setContentView(mySurfaceView);
mySurfaceView.surfaceCreated( mySurfaceView.getHolder() );
}
#Override
protected void onPause() {
super.onPause();
Log.i("onPause", "Main Activity's onPause called");
mySurfaceView.surfaceDestroyed( mySurfaceView.getHolder() );
}
#Override
public boolean onDoubleTap(MotionEvent e) {
//reset animation if Restart button pressed!
if ( restart.isButtonTouched(e.getX(), e.getY(), blockSize,blockSize) ) {
curPosition = 0;
}
return true;
}
public void configureDisplay() {
//find out the width and height of the screen
Display display =
getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
screenWidth = size.x;
screenHeight = size.y;
//Determine the size of each block/place on the board
// blockSize = screenWidth/10;
blockSize = screenWidth/10;
//Determine how many blocks will fit into the
//height and width
// numBlocksWide = 10;
numBlocksWide = 10;
numBlocksHigh = ( ( screenHeight ) ) / blockSize;
}//END METHOD: configureDisplay
#Override
public boolean onTouchEvent(MotionEvent e) {
mGestureDetector.onTouchEvent(e);
return super.onTouchEvent(e);
}//END onTouchEvent
class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Handler.Callback {
Canvas canvas = new Canvas();
SurfaceHolder surfaceHolder;//this lets us access the canvas but still need to use Canvas class
Handler myHandler;
Paint paint = new Paint ();
public MySurfaceView(Context context) {
super(context);
paint.setColor(Color.argb(255, 0, 0, 0));
path = new Path ();
myHandler = new Handler(this);
getHolder().addCallback(this);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
this.surfaceHolder = holder;
myHandler.sendEmptyMessage(0);
}
#Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
// What do I enter here???
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.i("Me", "surfaceDestroyed: bye!" );
myHandler.removeCallbacksAndMessages(null);
}
protected void drawIt ( ) {
if ( surfaceHolder.getSurface().isValid() ) {
canvas = surfaceHolder.lockCanvas();
canvas.drawColor(Color.WHITE);//the background
//for each obj, draw it
surfaceHolder.unlockCanvasAndPost(canvas);
}//END IF BLOCK: For valid surfaceHolder
}//END METHOD: drawIt
public void update() {
Log.i ("cur pos. so far:", "" + curPosition );
if ( curPosition < SIZE ) {
myObjs [ curPosition ].animateMe ( );
curPosition++;
}
}//END METHOD: update
#Override
public boolean handleMessage(Message msg) {
if (curPosition < SIZE ) {
Log.i("Still objs 2 animate","Yes");
Log.i ("cur pos. so far:", "" + curPosition );
gameView.update();
gameView.drawIt();
myHandler.sendEmptyMessageDelayed(msg.what, DELAY);
}
else {
myHandler.sendEmptyMessageDelayed(msg.what, 10);
gameView.drawIt();
}
return true;
}//END METHOD: handleMessage
}//END INNER CLASS: MySurfaceView
}//END CLASS: MainActivity

Does SurfaceHolder or creating new Thread interfere with activity life cycle?

I am creating an application that requires a thread. When I test the life cycle I get a weird result. When I run the app from eclipse everything is called correctly - onCreate() then onResume(). When I press the home button on my phone onPause() is called. However, when I relaunch it from my phone onResume() is never called. Does this mean my thread is not created and started again? Here are my classes:
public class RenderView extends SurfaceView implements Runnable {
Thread thread = null;
SurfaceHolder holder;
boolean running = false;
Bitmap frameBuffer;
public RenderView(Context context, Bitmap frameBuffer) {
super(context);
holder = getHolder();
this.frameBuffer = frameBuffer;
}
#Override
public void run() {
while(running) {
if(!holder.getSurface().isValid()) {
continue;
}
Canvas canvas = holder.lockCanvas();
canvas.drawARGB(255, 150, 150, 10);
holder.unlockCanvasAndPost(canvas);
}
}
public void pause() {
running = false;
while(true) {
try {
thread.join();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
public void resume() {
running = true;
thread = new Thread(this);
thread.start();
}
}
public class GameHandler extends Activity {
RenderView view;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("Game Handler", "onCreate called");
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
boolean isLandscape = this.getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE;
int frameBufferWidth = isLandscape ? 1280 : 800;
int frameBufferHeight = isLandscape ? 800: 1280;
Bitmap frameBuffer = Bitmap.createBitmap(frameBufferWidth, frameBufferHeight, Bitmap.Config.RGB_565);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
float scaleX = (float) frameBufferWidth / metrics.widthPixels;
float scaleY = (float) frameBufferHeight / metrics.heightPixels;
view = new RenderView(this, frameBuffer);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
#Override
public void onResume() {
super.onResume();
Log.d("GameHandler ", "OnResume Called");
view.resume();
}
#Override
public void onPause() {
super.onPause();
Log.d("GameHandler ", "onPaused Called");
view.pause();
}
}

Starting libGdx from android code

I am making android- specific game, and would like to use android API's. But I'm wondering, is it possible to have like an android menu, and from there call libGdx? I've tried adapting the manifest in the android part, so android activity (menu) is the LAUNCHER activity and gets called first. Then I thought I could start the ApplicationListener with an intent, but it doesn't work. What am I doing wrong? Is it even possible?
It is pretty simple:
// my call of the loading screen from inside a fragment
Intent intent = new Intent(getActivity(), LoadingScreenActivity.class);
startActivity(intent);
and the LoadingScreenActivity is, guess what, an Activity.
// the onCreate method
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
cfg.useGL20 = true;
cfg.useAccelerometer = false;
cfg.useCompass = false;
initialize(new LoadingScreen(), cfg);
}
and LoadingScreen is the the ApplicationListener you already mentioned
public class LoadingScreen implements ApplicationListener {
// create(), resize() and more
}
So basically you need embed libgdx inside an Activity...
Edit:
Here is my complete loading screen activity which uses libgdx. It should work (just remove the CustomApplication stuff that you don't have).
public class LoadingScreenActivity extends AndroidApplication {
private final static String LOG_TAG = LoadingScreenActivity.class.getSimpleName();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
cfg.useGL20 = true;
cfg.useAccelerometer = false;
cfg.useCompass = false;
initialize(new LoadingScreen(), cfg);
}
private class LoadingScreen implements ApplicationListener {
private OrthographicCamera mCamera = new OrthographicCamera();
private SpriteBatch mBatch;
private Stage mStage;
private ShapeRenderer mProgressLine;
#Override
public void create() {
AssetManager manager = CustomApplication.getAssetManager();
manager.load("ic_launcher.png", Texture.class);
mStage = new Stage();
mProgressLine = new ShapeRenderer();
mBatch = new SpriteBatch();
mCamera.setToOrtho(false, DesignUtil.getScreenWidth(), DesignUtil.getScreenHeight());
mStage.setCamera(mCamera);
}
#Override
public void resize(int width, int height) {
mStage.setViewport(width, height, true);
}
#Override
public void render() {
if (CustomApplication.getAssetManager().update()) {
IntentUtil.startActivity(LoadingScreenActivity.this, GameActivity.class);
finish();
return;
}
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// tell the camera to update its matrices.
mCamera.update();
// tell the SpriteBatch to render in the
// coordinate system specified by the camera.
mBatch.setProjectionMatrix(mCamera.combined);
mBatch.begin();
mProgressLine.begin(ShapeRenderer.ShapeType.Rectangle);
mProgressLine.rect(0 + mStage.getWidth() / 5, mStage.getHeight() / 2, mStage.getWidth() - mStage.getWidth() / 2.5f, DesignUtil.percentToPixel(5, mStage.getHeight()));
mProgressLine.setColor(0, 1, 0, 1);
mProgressLine.end();
mProgressLine.begin(ShapeRenderer.ShapeType.FilledRectangle);
mProgressLine.filledRect(0 + mStage.getWidth() / 5, mStage.getHeight() / 2, (mStage.getWidth() - mStage.getWidth() / 2.5f) * CustomApplication.getAssetManager().getProgress(), DesignUtil.percentToPixel(5, mStage.getHeight()));
mProgressLine.setColor(0, 1, 0, 1);
mProgressLine.end();
mBatch.end();
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void dispose() {
mBatch.dispose();
mStage.dispose();
mProgressLine.dispose();
}
}
}
If it helps, you CAN use the Android UI elements from Libgdx. That way, you can get the behavior you want, but not the method you want.
http://code.google.com/p/libgdx-users/wiki/IntegratingAndroidNativeUiElements
http://code.google.com/p/libgdx-users/wiki/IntegratingAndroidNativeUiElements3TierProjectSetup

Categories

Resources