简体   繁体   中英

Android app stores high score value as int in sharedpreferences. The app throws an outofbounds exception if a score over 100 is reached

I have an Android game that uses the SharedPreferences to store the high score for the game on the device. This works perfectly well until the high score on the device exceeds 100 points at which time it causes the app to crash the next time it gets the int from the SharedPreferences. Here is the code related to storing and retrieving the score from SharedPreferences.

public void setHighScore(int score) {
    SharedPreferences.Editor settingsEditor = prefs.edit();
    settingsEditor.putInt(Constants.KEY_HIGHSCORE, score);
    settingsEditor.commit();
    }

public int getHighScore() {
    return prefs.getInt(Constants.KEY_HIGHSCORE, 0);
}  



if (score > activity.getHighScore()) {
    activity.setHighScore(score);
}

        yourScoreText.setText("Your Score: " + score);
        yourScoreText.setColor(Color.GREEN);
        yourScoreText.setVisible(true);

        highScoreText.setText("High Score: " + activity.getHighScore());
        highScoreText.setColor(Color.RED);
        highScoreText.setVisible(true);

Logcat

09-19 22:00:13.612: W/dalvikvm(26458): threadid=12: thread exiting with uncaught exception (group=0x417a9898)
09-19 22:00:13.622: E/AndroidRuntime(26458): FATAL EXCEPTION: UpdateThread
09-19 22:00:13.622: E/AndroidRuntime(26458): java.lang.ArrayIndexOutOfBoundsException: length=360; index=360
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.entity.text.vbo.HighPerformanceTextVertexBufferObject.onUpdateVertices(HighPerformanceTextVertexBufferObject.java:124)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.entity.text.Text.onUpdateVertices(Text.java:333)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.entity.shape.Shape.setSize(Shape.java:146)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.entity.text.Text.setText(Text.java:221)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at hungryfish.scene.GameScene.reset(GameScene.java:246)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at hungryfish.scene.GameScene.onSceneTouchEvent(GameScene.java:308)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.entity.scene.Scene.onSceneTouchEvent(Scene.java:387)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.engine.Engine.onTouchScene(Engine.java:470)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.engine.Engine.onTouchEvent(Engine.java:456)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.input.touch.controller.BaseTouchController$TouchEventRunnablePoolItem.run(BaseTouchController.java:102)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.util.adt.pool.RunnablePoolUpdateHandler.onHandlePoolItem(RunnablePoolUpdateHandler.java:54)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.util.adt.pool.RunnablePoolUpdateHandler.onHandlePoolItem(RunnablePoolUpdateHandler.java:1)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.util.adt.pool.PoolUpdateHandler.onUpdate(PoolUpdateHandler.java:88)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.input.touch.controller.BaseTouchController.onUpdate(BaseTouchController.java:62)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.engine.Engine.onUpdate(Engine.java:604)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.engine.LimitedFPSEngine.onUpdate(LimitedFPSEngine.java:57)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.engine.Engine.onTickUpdate(Engine.java:568)
09-19 22:00:13.622: E/AndroidRuntime(26458):    at org.andengine.engine.Engine$UpdateThread.run(Engine.java:858)

I would comment this but since i'm under 50 in score i cant.. A simple solution would be to just store the value in a String. Like this:

SharedPreferences sp = getSharedPreferences(1);
sp.edit().putString("highScore", String.valueOf(highScore)).apply

and when you get it out of the shared preference just do

int score = Integer.valueOf(sp.getString("highScore", "0"));

Personally i'm not a huge fan of shared preferences and instead opt for a sqlite database. The read / write sequences are so much faster when using a database..

One thing you might try is using .apply instead of .commit - .apply runs in the background..

As you asked, i'm providing some code i wrote to help you with your database. I just wrote it and tried it so i know that it works or at least worked on my GS3 (4.4.4) and my OnePlus one (4.4.4).

I would create the classes in separate files but its up-to you. For the example i just used them in my default activity and ran some set / get score code in the onCreate.

 public class DatabaseHelper extends SQLiteOpenHelper{

     //final static stuff because you'll want to access it(the data) in other classes.
     public static final String DB_NAME = "scores.db";
     public static final String SCORE_TABLE = "scores";
     public static final int VERSION = 3;
     private Context context;

     public DatabaseHelper(Context context){
         super(context, DB_NAME, null, VERSION);
         this.context = context;
     }

     public SQLiteDatabase getDatabase(){
         return getWritableDatabase();
     }

     @Override
     public void onCreate(SQLiteDatabase db) {
         //our code for creating the ScoreTable..
         db.execSQL("CREATE TABLE "
                 + SCORE_TABLE +
                 "(" +
                 "ID INT NOT NULL, " +
                 "Score INT);");
         //now we create one row of data.
         db.execSQL("INSERT INTO " + DatabaseHelper.SCORE_TABLE + " (ID, Score) VALUES (1, 0);");
     }

     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

         //if the version is greater than the previous one, we re-create it.
         if(newVersion > oldVersion) {
             db.execSQL("DROP TABLE IF EXISTS " + SCORE_TABLE);
             onCreate(db);
         }
     }
 }
public class DataSource{
    //this is a link between your app and the database. you don't have to use this.
    private SQLiteDatabase database;
    private Context context;
    private DatabaseHelper helper;
    public DataSource(Context context){
        //initiate our objects.
        this.helper = new DatabaseHelper(context);
        this.database = helper.getWritableDatabase();
        this.context = context;
    }

    public void setScore(int score){
        //we update the Score column where the ID is 1
            database.execSQL("UPDATE " + DatabaseHelper.SCORE_TABLE + " SET Score = " + score + " WHERE ID = 1;");
    }
    public int getScore(){
        //we get all of the columns that have a ID of one. There should only be one column.
        Cursor score = database.rawQuery("SELECT * FROM " + DatabaseHelper.SCORE_TABLE + " WHERE ID = 1", null);
        //this isn't especially needed, but i found that adding checks and balances keeps crashes down.
        if(score.getColumnCount() < 1){
            return 0;
        } else {
            //this is important! You have to move the cursor to the first row.
            score.moveToFirst();
            //now we get the data on row 1 (actually 2 if you look at it from a 1 based number system..)
            int mscore = score.getInt(1);
            //close the cursor
            score.close();
            //and finally return the score.
            return mscore;
        }
    }

}

To use the above code just Create a DataSource object and call the setScore and getScore methods.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM