I recently uploaded my app to the google play store Helicopter Combat
But every now and then it currupts. I got the error report here:
java.lang.ArrayIndexOutOfBoundsException: length=3; index=3
at de.krissini.server.Animation.getImage(Animation.java:36)
That's the refered code:
public class Animation {
private Bitmap[] frames;
private int currentFrame;
private long startTime, delay;
private boolean playedOnce;
public void setFrames(Bitmap[] frames){
this.frames = frames;
currentFrame = 0;
startTime = System.nanoTime();
}
public void setDelay(long d){
delay = d;
}
public void setFrame(int i){
currentFrame = i;
}
public void update(){
long elapsed = (System.nanoTime()-startTime)/1000000;
if(elapsed>delay){
currentFrame++;
startTime = System.nanoTime();
}
if(currentFrame == frames.length){
currentFrame = 0;
playedOnce = true;
}
}
public Bitmap getImage(){
return frames[currentFrame]; // line 36
}
public int getFrame(){
return currentFrame;
}
public boolean playedOnce(){
return playedOnce;
}
}
I understand the error, but how can I prevent it?
I appreciate every answer
The error is caused by the misconception that the length of an array is the same as the max index. Where length starts at 1 and index starts at 0, they always differ 1.
Change this:
if(currentFrame == frames.length ){
// your reset code
}
To this:
if(currentFrame == frames.length - 1){
// your reset code
}
I have used the following animation class because i have multiple images to make an animation. After the 7th animation i get OutofMemoryError. androidgraphics.Bitmap.createBitmap
public class AnimationsContainer {
public int FPS = 30; // animation FPS
// single instance procedures
private static AnimationsContainer mInstance;
private AnimationsContainer() {
};
public static AnimationsContainer getInstance() {
if (mInstance == null)
mInstance = new AnimationsContainer();
return mInstance;
}
// animation progress dialog frames
private int[] mProgressAnimFrames = {};
// animation splash screen frames
/**
* #param imageView
* #return progress dialog animation
*/
public FramesSequenceAnimation createProgressDialogAnim(ImageView imageView) {
return new FramesSequenceAnimation(imageView, mProgressAnimFrames);
}
/**
* #param imageView
* #return splash screen animation
*/
public FramesSequenceAnimation createSplashAnim(ImageView imageView, int[] n) {
return new FramesSequenceAnimation(imageView, n);
}
/**
* AnimationPlayer. Plays animation frames sequence in loop
*/
public class FramesSequenceAnimation {
private int[] mFrames; // animation frames
private int mIndex; // current frame
private boolean mShouldRun; // true if the animation should continue running. Used to stop the animation
private boolean mIsRunning; // true if the animation currently running. prevents starting the animation twice
private SoftReference<ImageView> mSoftReferenceImageView; // Used to prevent holding ImageView when it should be dead.
private Handler mHandler;
private int mDelayMillis;
private OnAnimationStoppedListener mOnAnimationStoppedListener;
private Bitmap mBitmap = null;
private BitmapFactory.Options mBitmapOptions;
public FramesSequenceAnimation(ImageView imageView, int[] frames) {
mHandler = new Handler();
mFrames = frames;
mIndex = -1;
mSoftReferenceImageView = new SoftReference<ImageView>(imageView);
mShouldRun = false;
mIsRunning = false;
mDelayMillis = 50;
imageView.setImageResource(mFrames[0]);
// use in place bitmap to save GC work (when animation images are the same size & type)
if (Build.VERSION.SDK_INT >= 11) {
Bitmap bmp = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
int width = bmp.getWidth();
int height = bmp.getHeight();
Bitmap.Config config = bmp.getConfig();
mBitmap = Bitmap.createBitmap(width, height, config);
mBitmapOptions = new BitmapFactory.Options();
// setup bitmap reuse options.
mBitmapOptions.inBitmap = mBitmap;
mBitmapOptions.inMutable = true;
mBitmapOptions.inSampleSize = 1;
}
}
private int getNext() {
mIndex++;
if (mIndex == mFrames.length){
mIndex = mIndex - 1;
mShouldRun = false;
}
return mFrames[mIndex];
}
/**
* Starts the animation
*/
public synchronized void start() {
mShouldRun = true;
if (mIsRunning)
return;
Runnable runnable = new Runnable() {
#Override
public void run() {
ImageView imageView = mSoftReferenceImageView.get();
if (!mShouldRun || imageView == null) {
mIsRunning = false;
if (mOnAnimationStoppedListener != null) {
mOnAnimationStoppedListener.onAnimationStopped();
}
return;
}
mIsRunning = true;
mHandler.postDelayed(this, mDelayMillis);
if (imageView.isShown()) {
int imageRes = getNext();
if (mBitmap != null) { // so Build.VERSION.SDK_INT >= 11
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeResource(imageView.getResources(), imageRes, mBitmapOptions);
} catch (Exception e) {
e.printStackTrace();
}
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(imageRes);
mBitmap.recycle();
mBitmap = null;
}
} else {
imageView.setImageResource(imageRes);
}
}
}
};
mHandler.post(runnable);
}
/**
* Stops the animation
*/
public synchronized void stop() {
mShouldRun = false;
}
}
}
Can anyone explain me why and tell me how to fix it? I already added
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
to my manifest file.
It might help to be sure that you are not keeping references to unneeded images. This is happening because you probably have the "standard" memory heap of 32MB or something similar (maybe 64MB or maybe 16MB). If you consider that most images are 5MB or more, it's not surprising you are out of memory.
You can increase the heap size using android:largeHeap="true" like this:
How to increase heap size of an android application?
I have really wired problem..I searced a lot in google and here and con't solve my problem.
I have main activity which create and start 3 threads (only "gameThread" is relevant now).
When I click on the back buttom it seems ok , but when I come back to the activity and the thread continue running , it seems it run duplicate - I see only one ContentView on the screen, but there is more one running (maybe behind).
I hope I explain myselg good..
this is the code:
package com.example.newpingpong;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.widget.Toast;
/*
* The main activity class of the Ping Pong game
*/
public class MainActivity extends Activity
{
/*
* The following class variables hold references to the objects the game is composed of -
* PingPongView - main view,
* gameBall - the ball in the game,
* gamePaddle - the paddle in the game,
* gameThread - main thread updating the position of the ball,
* paddleMoverThread - another thread which is responsible for moving the paddle.
*/
private PingPongView gameView;
private Tray gamePaddle;
private GameLevel gameLevel;
private GameLevel [] levels;
//threads
private PingPongGame gameThread;
private PaddleMover paddleMoverThread;
private PresentThread giftThread;
private Messages message;
//balls container
private BallView [] ballViewArr;
private Ball [] ballArr;
//gifts container
private Gift[] giftArr;
private GiftView[] GiftViewArr;
public GameSounds gameSounds;
private int screenWidth;
private int screenHeight;
private TrayView paddleView;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
Toast.makeText(this, "onCreate!", Toast.LENGTH_SHORT).show();
// Get the message from the intent
Intent intent = getIntent();
String levelChosed = intent.getStringExtra(ImageAdapter.EXTRA_MESSAGE);
int levelChosed_int = Integer.parseInt(levelChosed);
Const.currentLevel = levelChosed_int;
Const.isCompleteThisLevel = false;
GameLevelView gameLevelView;
//for sounds
if(gameSounds == null){
gameSounds = new GameSounds();
gameSounds.addSound(Const.CATCH_GIFT, R.raw.cathc_gift, getBaseContext());
gameSounds.addSound(Const.GIFT_START_FALL, R.raw.gift_start_fall, getBaseContext());
gameSounds.addSound(Const.HIT_BLOCK, R.raw.hit_block, getBaseContext());
gameSounds.addSound(Const.HIT_KIR, R.raw.hit_kir, getBaseContext());
gameSounds.addSound(Const.GAME_OVER, R.raw.game_over, getBaseContext());
gameSounds.addSound(Const.HIT_PADDLE, R.raw.hit_paddle, getBaseContext());
}
if(this.ballViewArr == null){
this.ballViewArr = new BallView[Const.MAX_BALLS];
this.ballArr = new Ball[Const.MAX_BALLS];
this.giftArr = new Gift[Const.MAX_GIFTS];
this.GiftViewArr = new GiftView[Const.MAX_GIFTS];;
// Getting the screen width & height
screenWidth = this.getWindowManager().getDefaultDisplay().getWidth();
screenHeight = this.getWindowManager().getDefaultDisplay().getHeight();
for(int i=0 ; i< Const.MAX_BALLS ; i++){
this.ballArr[i] = new Ball(screenWidth / 2, screenHeight / 2, screenWidth, screenHeight);
this.ballViewArr[i] = new BallView(this, this.ballArr[i]);
this.ballArr[i].setMode(Const.NOT_ACTIVE);
}
this.ballArr[0].setMode(Const.ACTIVE);
for(int i=0 ; i< Const.MAX_GIFTS ; i++){
this.giftArr[i] = new Gift(screenWidth / 2, screenHeight / 4,screenWidth, null, Const.NOT_ACTIVE);
this.GiftViewArr[i] = new GiftView(this, this.giftArr[i]);
}
// Creating the paddle
gamePaddle = new Tray(screenWidth / 2, (int)(screenHeight * 0.75), screenWidth);
// Creating the paddle view, and giving it the gameBall as a parameter
paddleView = new TrayView(this, gamePaddle);
levels = new GameLevel[Const.LevelsAmount];
levels[0] = new GameLevel(Const.level0, screenWidth , screenHeight, this.giftArr);
levels[1] = new GameLevel(Const.level1, screenWidth , screenHeight, this.giftArr);
levels[2] = new GameLevel(Const.level2,screenWidth , screenHeight, this.giftArr);
}
gameLevel=levels[levelChosed_int];
gameLevelView = new GameLevelView(this,gameLevel);
// Creating the game view
this.gameView = new PingPongView(this);
message = new Messages(this , screenWidth , screenHeight);
// Give the gameView our ballView & paddleView.
gameView.setViews(paddleView,gameLevelView , message ,this.ballViewArr , this.GiftViewArr);
// Setting the gameView as the main view for the PingPong activity.
setContentView(gameView);
if(gameThread == null){
//create the main thread
gameThread = new PingPongGame(this.getResources(), gamePaddle, gameView, gameLevel , message , ballArr , gameSounds);
//create the thread responsible for moving the paddle
paddleMoverThread = new PaddleMover(gamePaddle, gameView);
//create the thread responsible for present
giftThread = new PresentThread(gamePaddle , gameView , gameLevel, message , giftArr , ballArr,gameSounds );
gameThread.start();
paddleMoverThread.start();
giftThread.start();
}
}
//This method is automatically called when the user touches the screen
#Override
public boolean onTouchEvent(MotionEvent event)
{
float destination;
// Toast.makeText(this, "try!", Toast.LENGTH_SHORT).show();
//get the x coordinate of users' press
destination = event.getX();
//notify the paddle mover thread regarding the new destination
gamePaddle.setPaddleDestination(destination);
return true;
}
#Override
protected void onPause() {
Toast.makeText(this, "onPause!", Toast.LENGTH_SHORT).show();
Const.toStop = true;
while(Const.toStopMessageHasRecieved == false){
} //the thread not see the message that he shoult stop
//here the thrad recived the message and stoped.
super.onPause();
}
#Override
public void onResume()
{
Toast.makeText(this, "onResume!", Toast.LENGTH_SHORT).show();
for(int i=0 ; i< Const.MAX_BALLS ; i++){
this.ballArr[i] = new Ball(screenWidth / 2, screenHeight / 2, screenWidth, screenHeight);
this.ballViewArr[i] = new BallView(this, this.ballArr[i]);
this.ballArr[i].setMode(Const.NOT_ACTIVE);
}
this.ballArr[0].setMode(Const.ACTIVE);
synchronized (Const.mPauseLock) {
Const.toStop = false;
Const.mPauseLock.notifyAll();
}
super.onResume();
}
}
AND THE THREAD:
package com.example.newpingpong;
import android.content.res.Resources;
import android.graphics.Color;
public class PingPongGame extends Thread {
private PingPongView gameView;
private Tray gamePaddle;
private GameLevel gameLevel;
private Messages message;
private Ball [] ballArr;
private GameSounds gameSounds;
private Resources res;
//private int screenWidth;
//private int screenHight;
public PingPongGame(Resources res ,Tray theTray , PingPongView mainView , GameLevel gameLevel ,
Messages message , Ball ballArr[] ,GameSounds gameSounds)
{
this.gameView = mainView;
this.ballArr = ballArr;
this.gamePaddle = theTray;
this.gameLevel=gameLevel;
this.message = message;
this.gameSounds=gameSounds;
this.res=res;
//this.screenWidth = screenWidth;
//this.screenHight = screenHight;
//for stop and resume threads
}
//main method of the current thread
#Override
public void run()
{
try {
PingPongGame.sleep(2000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
this.message.setMessage(res.getString(R.string.lives) + gameLevel.getLives());
gameView.postInvalidate();
//infinitely loop, and move the balls accordingly
while ((Const.isLose == false) && (Const.isCompleteThisLevel==false))
{
for(int i=0; i<Const.MAX_BALLS ; i++){
if(ballArr[i].getMode() != Const.NOT_ACTIVE){
//move the ball one step
if(ballArr[i].move()) //hit kir
gameSounds.play(Const.HIT_KIR,false);
gameView.postInvalidate();
//check if the ball hit some block
if(gameLevel.updateIfHit(ballArr[i])){
ballArr[i].down();
gameSounds.play(Const.HIT_BLOCK,false);
}
gameView.postInvalidate();
if(gameLevel.getLives() == 0){
//gameLevel.restart();
this.message.setMessage(res.getString(R.string.game_over));
gameView.postInvalidate();
gameSounds.play(Const.GAME_OVER,false);
Const.isLose = true;
try
{
PingPongGame.sleep(2000);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
//check if the ball has reached the paddle
if(checkHitPaddle(i,ballArr[i]) == true){ //משתמש פספס and lives --
this.message.setMessage(res.getString(R.string.lives)+"" + gameLevel.getLives());
//send a request to refresh the display
gameView.postInvalidate();
}
}
}
try
{
PingPongGame.sleep(5);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
//public static boolean toStop = false;
//public static boolean toStopMessageHasRecieved = false;
//for stop and resume
//for stop and resume
synchronized (Const.mPauseLock) {
while (Const.toStop) {
Const.toStopMessageHasRecieved = true;
try {
Const.mPauseLock.wait();
} catch (InterruptedException e) {
}
}
}
}
// Const.toStopMessageHasRecieved = true;
}
//This method is responsible for handling the situation in which the ball has reached the height of the paddle
//return true if lives--
private boolean checkHitPaddle(int i , Ball ball)
{
// Checking if the ball had hit the paddle
if (ball.getBottom() >= gamePaddle.getTop())
{
// check if the paddle is in the right place
if ((ball.getRight() > gamePaddle.getLeft()) &&
(ball.getLeft() < gamePaddle.getRight()))
{
//The ball had hit the paddle - so it should be bounced
gameSounds.play(Const.HIT_PADDLE,false);
ball.bounceUp();
}
else
{
//The ball had missed the paddle - player looses - restart the game
gameSounds.play(Const.GAME_OVER,false); //not really game over - just lose live
boolean isMoreBall = false;
for(int j=0 ; j< Const.MAX_BALLS ; j++)
if( (j != i) && (ballArr[j].getMode() == Const.ACTIVE)){
isMoreBall = true;
break;
}
if(isMoreBall){
ball.setMode(Const.NOT_ACTIVE);
//for(int k=0; k<Const.MAX_BALLS ; k++){
//ballArr[k].downSpeed(0.25);
//}
}
else{
gameLevel.setLives(gameLevel.getLives()-1);
ball.restart();
return true;
}
}
}
return false;
}
}
Everytime the Activity is started you create a new gameThread in onCreate since the Activity is a new object then and gameThread is null. But you never stop them. You should probably insert something that ends your threads in onDestroy.
At the moment both Threads modify your Const object. Which leads me to the main problem:
I think the main problem is that all your communication is handled by some 'Global Variable Collection' in the Const object which can be very confusing the bigger the game gets. For now a solution could be to let the Const object handle the Threads too, but that would possibly only make it worse on the long run.
Try using
Timer rather than Thread.
And cancel timer using timer.cancel() in onBackPressed() method.
I'm using a SurfaceView and I have to update it as fast as I can. Since the onDraw method isn't called automatically I have to call explicitly. So my first attempt was on threads, but I got some lag. There is a better way to do this? If there is a better solution, can you, please, explain me? I'm new on Android's World!
What I have so far
public void run() {
while(running){
Canvas c = null;
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
}
}
Here's my code that I use as a rendering thread. The fps is updated frame dependent. I usually run my games at 30 fps.
(There's some code you won't need, but I'm sire you'll be able to find the pieces you are looking for.)
public class RenderView extends SurfaceView implements Runnable {
Game game;
Bitmap gameScreen;
Thread gameloop;
SurfaceHolder holder;
boolean running;
int sleepTime;
int numberOfFramesSkipped;
int maxFrameSkips;
long beginTime;
long endTime;
long lastTime;
int differenceTime;
int framePeriod;
Canvas frameBuffer;
int frameCount;
public RenderView(int fps, int maxFrameSkips) {
super(game);
this.gameScreen = gameScreen;
this.holder = getHolder();
this.framePeriod = 1000/fps;
this.maxFrameSkips = maxFrameSkips;
lastTime = System.currentTimeMillis();
beginTime = System.currentTimeMillis();
}
#Override
public void run() {
while(running == true) {
if(holder.getSurface().isValid()) {
beginTime = System.currentTimeMillis();
// call your update method here
// render here
frameBuffer = holder.lockCanvas();
this.onDraw();
holder.unlockCanvasAndPost(frameBuffer);
// Frame Per Second Count
frameCount++;
if(lastTime + 1000 < System.currentTimeMillis()) {
lastTime = System.currentTimeMillis();
frameCount = 0;
}
endTime = System.currentTimeMillis();;
differenceTime = (int) (endTime - beginTime);
sleepTime = (int) (framePeriod - differenceTime);
if(sleepTime > 0) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException exception) {
exception.printStackTrace();
}
}
else {
while(sleepTime < 0 && numberOfFramesSkipped < this.maxFrameSkips) {
// Call your update method here
sleepTime += framePeriod;
numberOfFramesSkipped++;
}
}
}
}
}
private void renderFrameBuffer() {
// Update the current virtual screen image
game.getCurrentScreen().render();
// Render the current virtual screen to the real phone screen
frameBuffer = holder.lockCanvas();
if(frameBuffer != null) { // Fix for mysterious bug ( FATAL EXCEPTION: Thread)
// The viewing area of our virtual screen on our real screen
// Composition
// First layer (bottom)
frameBuffer.drawBitmap(this.gameScreen, null, game.getWSScreen().getGameScreendst(), null);
// Second layer (top)
frameBuffer.drawBitmap(this.gameScreenExtended, null, game.getWSScreen().getGameScreenextendeddst(), null);
holder.unlockCanvasAndPost(frameBuffer);
}
else {
gameEngineLog.e(classTAG, "Surface has not been created or otherwise cannot be edited");
}
}
public void resume() {
this.running = true;
gameloop = new Thread(this);
gameloop.start();
}
public void pause() {
this.running = false;
running = false;
while(true) {
try {
gameloop.join();
break;
} catch (InterruptedException e) {
// retry
}
}
}
}
public void onDraw() {
}
I suggest you take a look at this link or have a look at
http://code.google.com/p/beginning-android-games/
or http://www.edu4java.com/en/androidgame/androidgame3.html
I've been trying to figure this out for a while now... I need to place marks over top of a seekBar to show the user places that they bookmarked in the past. The data is stored in xml. The problem is making the little ovals appear over the seekBar... It just doesn't work...
Here's my code:
public class seekMark extends View {
private int seekLength; // in pixels
private int seekLeftPad; // in pixels
private int seekBottomPad; // in pixels
private int trackLength; // in ms
private float pxOverMs; // in px/ms
ShapeDrawable lmark;
private seekMark instance;
public seekMark(Context context){
super(context);
instance = this;
seekLength = progressBar.getWidth();
seekLeftPad = progressBar.getPaddingLeft();
seekBottomPad = progressBar.getBottom();
trackLength = player.getDuration();
pxOverMs = pxPerMs();
lmark = new ShapeDrawable(new OvalShape());
}
private float pxPerMs(){
return ((float) seekLength)/((float) trackLength);
}
private int[] markPxList() throws XmlPullParserException, IOException {
int bmStartTime = 0;
String bmNames[] = bmNameList(xmlPath);
int[] bmPos = new int[bmNames.length];
for(int i=0; i < bmNames.length; i++){
bmStartTime = getBookmark(xmlPath, bmNames[i]);
bmPos[i] = (int) (bmStartTime * pxOverMs);
}
return (bmPos);
}
public void markPlace() throws XmlPullParserException, IOException {
int y = seekBottomPad;
int x = 0;
int bmPos[] = markPxList();
for(int i = 0; i < bmPos.length; i++){
x = bmPos[i] + seekLeftPad;
lmark = new ShapeDrawable();
lmark.getPaint().setColor(0xff74AC23);
lmark.setBounds(x, y, x + 1, y + 1);
instance.invalidate();
}
}
protected void onDraw(Canvas canvas) {
lmark.draw(canvas);
}
}
It's called from onCreate using this code. I call it using in another thread to avoid the problem where the dimensions of progressBar aren't yet set in onCreate.
Display display = ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
if (display.getRotation() == 1){ // if landscape
final Runnable runner = new Runnable() {
public void run() {
seekMark seekMarks = new seekMark(context);
try {
seekMarks.markPlace();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// runs in another thread to avoid the problem with calling
// seekMark directly from onCreate
}
};
handler.postDelayed(runner, 1000);
}
The program crashes whenever I try to call seekMark.markPlace()... I'm trying to draw this over top of my layout main.xml.
im not sure if this is what you are trying to do.
Customize Seekbar
this seems to be similar while the approach is different.