fix (I hope) OOM errors when there are a ton of games by doing

snapshot creation and loading sequentially on a single thread so that
peak memory consumed by bitmap operations is lower.  Remove snapshots
from GameSummary: the db is the only source now.
This commit is contained in:
Eric House 2013-11-22 22:58:39 -08:00
parent d2c87647cd
commit 6f4cb4b0ca
5 changed files with 117 additions and 42 deletions

View file

@ -82,7 +82,7 @@ public class BoardCanvas extends Canvas implements DrawCtx {
private int m_trayOwner = -1;
private int m_pendingScore;
private int m_dictPtr = 0;
private String[] m_dictChars;
protected String[] m_dictChars;
private boolean m_hasSmallScreen;
private int m_backgroundUsed = 0x00000000;
@ -118,7 +118,7 @@ public class BoardCanvas extends Canvas implements DrawCtx {
int descentFor( int ht ) { return (int)(ht * m_descentProportion); }
int widthFor( int width ) { return (int)(width / m_widthProportion); }
}
private FontDims m_fontDims;
protected FontDims m_fontDims;
public BoardCanvas( Activity activity, Bitmap bitmap, JNIThread jniThread,
BoardDims dims )

View file

@ -135,7 +135,7 @@ public class DBUtils {
DBHelper.DICTLANG, DBHelper.GAMEID,
DBHelper.SCORES, DBHelper.HASMSGS,
DBHelper.LASTPLAY_TIME, DBHelper.REMOTEDEVS,
DBHelper.LASTMOVE, DBHelper.THUMBNAIL
DBHelper.LASTMOVE
};
String selection = String.format( ROW_ID_FMT, lock.getRowid() );
@ -184,18 +184,6 @@ public class DBUtils {
summary.lastMoveTime =
cursor.getInt(cursor.getColumnIndex(DBHelper.LASTMOVE));
if ( BuildConstants.THUMBNAIL_SUPPORTED ) {
byte[] data =
cursor.getBlob( cursor.
getColumnIndex(DBHelper.THUMBNAIL));
if ( null != data ) {
Bitmap thumb =
BitmapFactory.decodeByteArray( data, 0,
data.length );
summary.setThumbnail( thumb );
}
}
String scoresStr =
cursor.getString( cursor.getColumnIndex(DBHelper.SCORES));
int[] scores = new int[summary.nPlayers];
@ -322,8 +310,6 @@ public class DBUtils {
}
values.put( DBHelper.SERVERROLE, summary.serverRole.ordinal() );
addThumb( summary.getThumbnail(), values );
}
initDB( context );
@ -447,7 +433,15 @@ public class DBUtils {
long rowid = lock.getRowid();
String selection = String.format( ROW_ID_FMT, rowid );
ContentValues values = new ContentValues();
addThumb( thumb, values );
if ( null == thumb ) {
values.putNull( DBHelper.THUMBNAIL );
} else {
ByteArrayOutputStream bas = new ByteArrayOutputStream();
thumb.compress( CompressFormat.PNG, 0, bas );
values.put( DBHelper.THUMBNAIL, bas.toByteArray() );
}
initDB( context );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getWritableDatabase();
@ -957,15 +951,32 @@ public class DBUtils {
s_groupsCache = null;
}
private static void addThumb( Bitmap thumb, ContentValues values )
public static Bitmap getThumb( Context context, long rowid )
{
if ( null == thumb ) {
values.putNull( DBHelper.THUMBNAIL );
} else {
ByteArrayOutputStream bas = new ByteArrayOutputStream();
thumb.compress( CompressFormat.PNG, 0, bas );
values.put( DBHelper.THUMBNAIL, bas.toByteArray() );
Bitmap thumb = null;
if ( BuildConstants.THUMBNAIL_SUPPORTED ) {
byte[] data = null;
String[] columns = { DBHelper.THUMBNAIL };
String selection = String.format( ROW_ID_FMT, rowid );
initDB( context );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getReadableDatabase();
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
selection, null, null, null, null );
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
data = cursor.getBlob( cursor.
getColumnIndex(DBHelper.THUMBNAIL));
}
cursor.close();
db.close();
}
if ( null != data ) {
thumb = BitmapFactory.decodeByteArray( data, 0, data.length );
}
}
return thumb;
}
// Return map of string (group name) to info about all games in

View file

@ -30,6 +30,7 @@ import android.os.AsyncTask;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.View;
import java.util.concurrent.LinkedBlockingQueue;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@ -310,7 +311,6 @@ public class GameListItem extends LinearLayout
if ( BuildConstants.THUMBNAIL_SUPPORTED ) {
m_thumb = (ImageView)findViewById( R.id.thumbnail );
m_thumb.setImageBitmap( summary.getThumbnail() );
}
tview = (TextView)findViewById( R.id.role );
@ -340,11 +340,9 @@ public class GameListItem extends LinearLayout
private void makeThumbnailIf( boolean expanded )
{
if ( expanded && null != m_activity && null != m_summary
&& null == m_summary.getThumbnail()
if ( expanded && null != m_activity
&& XWPrefs.getThumbEnabled( m_context ) ) {
m_summary.setThumbnail( GameUtils.loadMakeBitmap( m_activity,
m_rowid ) );
enqueueGetThumbnail( this, m_rowid );
}
}
@ -412,4 +410,67 @@ public class GameListItem extends LinearLayout
toggleSelected();
}
private static class ThumbQueueElem {
public ThumbQueueElem( GameListItem item, long rowid ) {
m_item = item;
m_rowid = rowid;
}
long m_rowid;
GameListItem m_item;
}
private static LinkedBlockingQueue<ThumbQueueElem> s_queue
= new LinkedBlockingQueue<ThumbQueueElem>();
private static Thread s_thumbThread;
private static void enqueueGetThumbnail( GameListItem item, long rowid )
{
if ( BuildConstants.THUMBNAIL_SUPPORTED ) {
s_queue.add( new ThumbQueueElem( item, rowid ) );
synchronized( GameListItem.class ) {
if ( null == s_thumbThread ) {
s_thumbThread = makeThumbThread();
s_thumbThread.start();
}
}
}
}
private static Thread makeThumbThread()
{
return new Thread( new Runnable() {
public void run()
{
for ( ; ; ) {
ThumbQueueElem elem;
try {
elem = s_queue.take();
} catch ( InterruptedException ie ) {
DbgUtils.logf( "interrupted; killing "
+ "s_thumbThread" );
break;
}
Activity activity = elem.m_item.m_activity;
long rowid = elem.m_rowid;
Bitmap thumb = DBUtils.getThumb( activity, rowid );
if ( null == thumb ) {
// loadMakeBitmap puts in DB
thumb = GameUtils.loadMakeBitmap( activity, rowid );
}
final GameListItem item = elem.m_item;
final Bitmap ft = thumb;
activity.runOnUiThread( new Runnable() {
public void run() {
ImageView iview = item.m_thumb;
if ( null != iview ) {
iview.setImageBitmap( ft );
}
}
});
}
}
});
}
}

View file

@ -24,6 +24,8 @@ import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Rect;
import org.eehouse.android.xw4.jni.XwJNI;
public class ThumbCanvas extends BoardCanvas {
public ThumbCanvas( Activity activity, Bitmap bitmap )
@ -47,4 +49,17 @@ public class ThumbCanvas extends BoardCanvas {
return false;
}
// Unlike superclass, where the game was loaded on the main thread
// but dictChanged() is called on the JNI thread, this instance
// will have been created on the same background thread that's
// calling us. So don't switch threads for the dict_getChars()
// call
@Override
public void dictChanged( final int dictPtr )
{
if ( 0 != dictPtr ) {
m_fontDims = null;
m_dictChars = XwJNI.dict_getChars( dictPtr );
}
}
}

View file

@ -21,7 +21,6 @@
package org.eehouse.android.xw4.jni;
import android.content.Context;
import android.graphics.Bitmap;
import android.text.TextUtils;
import junit.framework.Assert;
@ -69,7 +68,6 @@ public class GameSummary {
private CurGameInfo m_gi;
private Context m_context;
private String[] m_remotePhones;
private Bitmap m_thumb;
private GameSummary() {}
@ -89,16 +87,6 @@ public class GameSummary {
m_gi = gi;
}
public void setThumbnail( Bitmap thumb )
{
m_thumb = thumb;
}
public Bitmap getThumbnail()
{
return m_thumb;
}
public boolean inNetworkGame()
{
return null != relayID;