Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQL Cursor throws out of memory while calling getString

Attachment Reference to my earlier question :- Out of memory

I would try to be as precise as possible. I am getting a response from my web-service call a long base64 string. I decode the string and get a huge string which contains my data. I de-serialize the string and create the object of my class using the string as below.

String decryptedXml = XmlObject.toDecryptedXmlString(gameDetail.getGameData(), app.getSessionEncryptionKey());
Game noviceGame = deserialiseGame(decryptedXml, NoviceGamer.class);

desrialiseGame() is just a method which desrialise the data and create and return my game instance. To maintain this object to multiple session(login/logout) I store my entire gameData(my string whose De-serialization gave me my Game instance) in database.

Next time when user login, to create the Game instance I fetch the string from my DB and again try to De-serialize so that I get back my Game instance. But when I try to get the string from my DB, I get 'OUT OF MEMORY' exception while fetching the string.

Method called to De-serialize the game is below.

private HashMap<String, Game> games = new HashMap<String, Game>();

public void load(LocalDatabaseHelper localDbHelper) throws Exception
{
    synchronized(gameLockObject) {
        GameDetailDAO dao = new GameDetailDAO(localDbHelper);

        //this will fetch me the all the entities from databse
        ArrayList<GameDetailEntity> dbGameDetails = dao.getEntities(null, null);

        for (GameDetailEntity gameDetail : dbGameDetails) {
            String gameLevel = gameDetail.getDetailLevel();             

            String gameXml = gameDetail.getGameData();

            Game game = null;
            if(gameLevel.equalsIgnoreCase("Novice")) {
                game = Job.deserialiseJob(gameXml, NoviceLevel.class);
            }
            else if (gameLevel.equalsIgnoreCase("Expert")) { 
                game = Job.deserialiseJob(gameXml, ExpertLevel.class);
            }

            //set the job version
            game.setGameversion(gameDetail.getGameVersion());
            game.setMagicNumber(gameDetail.getMagicNumber());
            game.setInactiveUser(gameDetail.getInactiveUser());
            game.setStartTime(gameDetail.getStartTime());
            game.setFinishTime(gameDetail.getFinishTime());
            game.setGameCompletionTime(gameDetail.getGameCompletionTime());
            if (!StringUtils.isNullOrEmpty(gameDetail.getGameStatus())) {
                game.setGameStatus(GameStatus.valueOf(gameDetail.getGameStatus()));
            }

            //add the job to the store
            games.put(gameDetail.getGameRef().toLowerCase(Locale.getDefault()), game);
        }
    }
}

My Database transaction is as below:

@Override
    protected GameEntity getEntityFromCursor(Cursor cursor) 
    {
        String gameRef = cursor.getString(cursor.getColumnIndex(GAME_REF));
        String detailLevel = cursor.getString(cursor.getColumnIndex(DETAIL_LEVEL));
        int gameVersion = cursor.getInt(cursor.getColumnIndex(GAME_VERSION));
        String gameData = cursor.getString(cursor.getColumnIndex(GAME_DATA));
        String status = cursor.getString(cursor.getColumnIndex(GAME_STATUS));

        long longStart = cursor.getLong(cursor.getColumnIndex(VISIT_START_TIME));
        Date startTime = longStart == -1 ? null : new Date(longStart);

        long longFinish = cursor.getLong(cursor.getColumnIndex(VISIT_END_TIME));
        Date finishTime = longFinish == -1 ? null : new Date(longFinish);

        long longComplete = cursor.getLong(cursor.getColumnIndex(GAME_COMPLETION_TIME));
        Date completionTime = longComplete == -1 ? null : new Date(longComplete);

        GameEntity entity = new GameEntity(gameRef, detailLevel, gameVersion, gameData, );
        entity.setGameStatus(status);
        entity.setStartTime(startTime);
        entity.setFinishTime(finishTime);
        entity.setGameCompletionTime(completionTime);
        return entity;
    }

but when I try to fetch the data from database @Line String gameData = cursor.getString(cursor.getColumnIndex(GAME_DATA)); I get out of memory error. As per my finding, When I add the flag largeHeap=true in manifest in application tag, my application gets damn slow. And also developer.android states

Never request a large heap simply because you've run out of memory and you need a quick fix—you should use it only when you know exactly where all your memory is being allocated and why it must be retained. Yet, even when you're confident your app can justify the large heap, you should avoid requesting it to whatever extent possible.

Can anybody suggest me how to avoid this. There is no bitmap use which most of the SO question contains. Any help will be appreciated.

like image 882
Android Avatar asked Oct 21 '22 01:10

Android


1 Answers

SQLCipher for Android allocates a memory buffer for each cursor window with a maximum size of 1 MB. Since you commented that the game data file is quite large, the size is likely exceeding the maximum size of the cursor window, causing the memory allocation error. In this case, we would probably recommend one of two options:

  1. Normalize the game data into a more typical database structures (i.e. multiple tables, columns, rows).
  2. Split the game data file into fragments, with each of them smaller than 1 MB, then reassemble them in the application.
like image 126
Stephen Lombardo Avatar answered Oct 23 '22 03:10

Stephen Lombardo