Replace LinkedHashMap with a SoftReference LRUCache. Add Slide cache.

pull/1/head
Moxie Marlinspike 11 years ago
parent a362c8755a
commit 50fae64330

@ -31,9 +31,11 @@ import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.LRUCache;
import java.util.LinkedHashMap; import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.Map;
/** /**
* A cursor adapter for a conversation thread. Ultimately * A cursor adapter for a conversation thread. Ultimately
@ -46,8 +48,9 @@ import java.util.LinkedHashMap;
public class ConversationAdapter extends CursorAdapter implements AbsListView.RecyclerListener { public class ConversationAdapter extends CursorAdapter implements AbsListView.RecyclerListener {
private static final int MAX_CACHE_SIZE = 40; private static final int MAX_CACHE_SIZE = 40;
private final Map<String,SoftReference<MessageRecord>> messageRecordCache =
Collections.synchronizedMap(new LRUCache<String, SoftReference<MessageRecord>>(MAX_CACHE_SIZE));
private final LinkedHashMap<String,MessageRecord> messageRecordCache;
private final Handler failedIconClickHandler; private final Handler failedIconClickHandler;
private final Context context; private final Context context;
private final MasterSecret masterSecret; private final MasterSecret masterSecret;
@ -62,7 +65,6 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
this.masterSecret = masterSecret; this.masterSecret = masterSecret;
this.failedIconClickHandler = failedIconClickHandler; this.failedIconClickHandler = failedIconClickHandler;
this.groupThread = groupThread; this.groupThread = groupThread;
this.messageRecordCache = initializeCache();
this.inflater = (LayoutInflater)context this.inflater = (LayoutInflater)context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE); .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
} }
@ -111,14 +113,22 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
} }
private MessageRecord getMessageRecord(long messageId, Cursor cursor, String type) { private MessageRecord getMessageRecord(long messageId, Cursor cursor, String type) {
if (messageRecordCache.containsKey(type + messageId)) SoftReference<MessageRecord> reference = messageRecordCache.get(type + messageId);
return messageRecordCache.get(type + messageId);
MmsSmsDatabase.Reader reader = DatabaseFactory.getMmsSmsDatabase(context).readerFor(cursor, masterSecret); if (reference != null) {
MessageRecord record = reference.get();
if (record != null)
return record;
}
MmsSmsDatabase.Reader reader = DatabaseFactory.getMmsSmsDatabase(context)
.readerFor(cursor, masterSecret);
MessageRecord messageRecord = reader.getCurrent(); MessageRecord messageRecord = reader.getCurrent();
messageRecordCache.put(type + messageId, messageRecord); messageRecordCache.put(type + messageId, new SoftReference<MessageRecord>(messageRecord));
return messageRecord; return messageRecord;
} }
@ -136,13 +146,4 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
public void onMovedToScrapHeap(View view) { public void onMovedToScrapHeap(View view) {
((ConversationItem)view).unbind(); ((ConversationItem)view).unbind();
} }
private LinkedHashMap<String,MessageRecord> initializeCache() {
return new LinkedHashMap<String,MessageRecord>() {
@Override
protected boolean removeEldestEntry(Entry<String,MessageRecord> eldest) {
return this.size() > MAX_CACHE_SIZE;
}
};
}
} }

@ -30,10 +30,12 @@ import org.thoughtcrime.securesms.database.model.DisplayRecord;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.InvalidMessageException; import org.thoughtcrime.securesms.util.InvalidMessageException;
import org.thoughtcrime.securesms.util.LRUCache;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.util.LinkedHashMap; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
public class EncryptingSmsDatabase extends SmsDatabase { public class EncryptingSmsDatabase extends SmsDatabase {
@ -156,37 +158,25 @@ public class EncryptingSmsDatabase extends SmsDatabase {
private static class PlaintextCache { private static class PlaintextCache {
private static final int MAX_CACHE_SIZE = 2000; private static final int MAX_CACHE_SIZE = 2000;
private static final Map<String, SoftReference<String>> decryptedBodyCache =
private static final LinkedHashMap<String,SoftReference<String>> decryptedBodyCache Collections.synchronizedMap(new LRUCache<String, SoftReference<String>>(MAX_CACHE_SIZE));
= new LinkedHashMap<String,SoftReference<String>>()
{
@Override
protected boolean removeEldestEntry(Entry<String,SoftReference<String>> eldest) {
return this.size() > MAX_CACHE_SIZE;
}
};
public void put(String ciphertext, String plaintext) { public void put(String ciphertext, String plaintext) {
decryptedBodyCache.put(ciphertext, new SoftReference<String>(plaintext)); decryptedBodyCache.put(ciphertext, new SoftReference<String>(plaintext));
} }
public String get(String ciphertext) { public String get(String ciphertext) {
synchronized (decryptedBodyCache) { SoftReference<String> plaintextReference = decryptedBodyCache.get(ciphertext);
SoftReference<String> plaintextReference = decryptedBodyCache.get(ciphertext);
if (plaintextReference != null) {
String plaintext = plaintextReference.get();
if (plaintext != null) {
return plaintext;
} else {
decryptedBodyCache.remove(ciphertext);
return null;
}
}
return null; if (plaintextReference != null) {
String plaintext = plaintextReference.get();
if (plaintext != null) {
return plaintext;
}
} }
return null;
} }
} }
} }

@ -41,12 +41,16 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.InvalidMessageException; import org.thoughtcrime.securesms.util.InvalidMessageException;
import org.thoughtcrime.securesms.util.LRUCache;
import org.thoughtcrime.securesms.util.ListenableFutureTask; import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.Trimmer; import org.thoughtcrime.securesms.util.Trimmer;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -130,6 +134,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
}; };
public static final ExecutorService slideResolver = Util.newSingleThreadedLifoExecutor(); public static final ExecutorService slideResolver = Util.newSingleThreadedLifoExecutor();
private static final Map<Long, SoftReference<SlideDeck>> slideCache =
Collections.synchronizedMap(new LRUCache<Long, SoftReference<SlideDeck>>(20));
public MmsDatabase(Context context, SQLiteOpenHelper databaseHelper) { public MmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper); super(context, databaseHelper);
@ -801,6 +807,12 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
private ListenableFutureTask<SlideDeck> getSlideDeck(final MasterSecret masterSecret, private ListenableFutureTask<SlideDeck> getSlideDeck(final MasterSecret masterSecret,
final long id) final long id)
{ {
ListenableFutureTask<SlideDeck> future = getCachedSlideDeck(id);
if (future != null) {
return future;
}
Callable<SlideDeck> task = new Callable<SlideDeck>() { Callable<SlideDeck> task = new Callable<SlideDeck>() {
@Override @Override
public SlideDeck call() throws Exception { public SlideDeck call() throws Exception {
@ -808,16 +820,43 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
return null; return null;
PduBody body = getPartDatabase(masterSecret).getParts(id, false); PduBody body = getPartDatabase(masterSecret).getParts(id, false);
return new SlideDeck(context, masterSecret, body); SlideDeck slideDeck = new SlideDeck(context, masterSecret, body);
slideCache.put(id, new SoftReference<SlideDeck>(slideDeck));
return slideDeck;
} }
}; };
ListenableFutureTask<SlideDeck> future = new ListenableFutureTask<SlideDeck>(task, null); future = new ListenableFutureTask<SlideDeck>(task, null);
slideResolver.execute(future); slideResolver.execute(future);
return future; return future;
} }
private ListenableFutureTask<SlideDeck> getCachedSlideDeck(final long id) {
SoftReference<SlideDeck> reference = slideCache.get(id);
if (reference != null) {
final SlideDeck slideDeck = reference.get();
if (slideDeck != null) {
Callable<SlideDeck> task = new Callable<SlideDeck>() {
@Override
public SlideDeck call() throws Exception {
return slideDeck;
}
};
ListenableFutureTask<SlideDeck> future = new ListenableFutureTask<SlideDeck>(task, null);
future.run();
return future;
}
}
return null;
}
public void close() { public void close() {
cursor.close(); cursor.close();
} }

@ -33,13 +33,15 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.LRUCache;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.LinkedHashMap; import java.util.Collections;
import java.util.Map;
import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.pdu.PduPart; import ws.com.google.android.mms.pdu.PduPart;
@ -47,13 +49,8 @@ import ws.com.google.android.mms.pdu.PduPart;
public class ImageSlide extends Slide { public class ImageSlide extends Slide {
private static final int MAX_CACHE_SIZE = 10; private static final int MAX_CACHE_SIZE = 10;
private static final Map<Uri, SoftReference<Bitmap>> thumbnailCache =
private static final LinkedHashMap<Uri,SoftReference<Bitmap>> thumbnailCache = new LinkedHashMap<Uri,SoftReference<Bitmap>>() { Collections.synchronizedMap(new LRUCache<Uri, SoftReference<Bitmap>>(MAX_CACHE_SIZE));
@Override
protected boolean removeEldestEntry(Entry<Uri,SoftReference<Bitmap>> eldest) {
return this.size() > MAX_CACHE_SIZE;
}
};
public ImageSlide(Context context, MasterSecret masterSecret, PduPart part) { public ImageSlide(Context context, MasterSecret masterSecret, PduPart part) {
super(context, masterSecret, part); super(context, masterSecret, part);

@ -16,30 +16,28 @@
*/ */
package org.thoughtcrime.securesms.mms; package org.thoughtcrime.securesms.mms;
import java.io.UnsupportedEncodingException; import android.content.Context;
import java.util.LinkedHashMap; import android.net.Uri;
import java.util.Map.Entry; import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.util.LRUCache;
import java.io.UnsupportedEncodingException;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.Map;
import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.pdu.CharacterSets; import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.PduPart; import ws.com.google.android.mms.pdu.PduPart;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
public class TextSlide extends Slide { public class TextSlide extends Slide {
private static final int MAX_CACHE_SIZE = 10; private static final int MAX_CACHE_SIZE = 10;
private static final Map<Uri, SoftReference<String>> textCache =
private static final LinkedHashMap<Uri,String> textCache = new LinkedHashMap<Uri,String>() { Collections.synchronizedMap(new LRUCache<Uri, SoftReference<String>>(MAX_CACHE_SIZE));
@Override
protected boolean removeEldestEntry(Entry<Uri,String> eldest) {
return this.size() > MAX_CACHE_SIZE;
}
};
public TextSlide(Context context, MasterSecret masterSecret, PduPart part) { public TextSlide(Context context, MasterSecret masterSecret, PduPart part) {
super(context, masterSecret, part); super(context, masterSecret, part);
} }
@ -54,16 +52,25 @@ public class TextSlide extends Slide {
} }
@Override @Override
public String getText() { public String getText() {
try { try {
if (textCache.containsKey(part.getDataUri())) SoftReference<String> reference = textCache.get(part.getDataUri());
return textCache.get(part.getDataUri());
if (reference != null) {
String cachedText = reference.get();
if (cachedText != null) {
return cachedText;
}
}
String text = new String(getPartData(), CharacterSets.getMimeName(part.getCharset())); String text = new String(getPartData(), CharacterSets.getMimeName(part.getCharset()));
textCache.put(part.getDataUri(), text); textCache.put(part.getDataUri(), new SoftReference<String>(text));
return text; return text;
} catch (UnsupportedEncodingException uee) { } catch (UnsupportedEncodingException uee) {
Log.w("TextSlide", uee);
return new String(getPartData()); return new String(getPartData());
} }
} }
@ -75,7 +82,7 @@ public class TextSlide extends Slide {
part.setData(message.getBytes(CharacterSets.MIMENAME_ISO_8859_1)); part.setData(message.getBytes(CharacterSets.MIMENAME_ISO_8859_1));
if (part.getData().length == 0) if (part.getData().length == 0)
throw new AssertionError("Part data should not be zero!"); throw new AssertionError("Part data should not be zero!");
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
Log.w("TextSlide", "ISO_8859_1 must be supported!", e); Log.w("TextSlide", "ISO_8859_1 must be supported!", e);
@ -89,5 +96,4 @@ public class TextSlide extends Slide {
return part; return part;
} }
} }

Loading…
Cancel
Save