From 50fae643305c975be8faf2dd13bff3b2c2e65bb0 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 6 May 2013 12:22:03 -0700 Subject: [PATCH] Replace LinkedHashMap with a SoftReference LRUCache. Add Slide cache. --- .../securesms/ConversationAdapter.java | 35 +++++++------- .../database/EncryptingSmsDatabase.java | 38 ++++++--------- .../securesms/database/MmsDatabase.java | 43 ++++++++++++++++- .../securesms/mms/ImageSlide.java | 13 ++--- .../thoughtcrime/securesms/mms/TextSlide.java | 48 +++++++++++-------- 5 files changed, 105 insertions(+), 72 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java index eb49dd8f47..9f204f8a05 100644 --- a/src/org/thoughtcrime/securesms/ConversationAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java @@ -31,9 +31,11 @@ import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; 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 @@ -46,8 +48,9 @@ import java.util.LinkedHashMap; public class ConversationAdapter extends CursorAdapter implements AbsListView.RecyclerListener { private static final int MAX_CACHE_SIZE = 40; + private final Map> messageRecordCache = + Collections.synchronizedMap(new LRUCache>(MAX_CACHE_SIZE)); - private final LinkedHashMap messageRecordCache; private final Handler failedIconClickHandler; private final Context context; private final MasterSecret masterSecret; @@ -62,7 +65,6 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re this.masterSecret = masterSecret; this.failedIconClickHandler = failedIconClickHandler; this.groupThread = groupThread; - this.messageRecordCache = initializeCache(); this.inflater = (LayoutInflater)context .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) { - if (messageRecordCache.containsKey(type + messageId)) - return messageRecordCache.get(type + messageId); + SoftReference reference = 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(); - messageRecordCache.put(type + messageId, messageRecord); + messageRecordCache.put(type + messageId, new SoftReference(messageRecord)); + return messageRecord; } @@ -136,13 +146,4 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re public void onMovedToScrapHeap(View view) { ((ConversationItem)view).unbind(); } - - private LinkedHashMap initializeCache() { - return new LinkedHashMap() { - @Override - protected boolean removeEldestEntry(Entry eldest) { - return this.size() > MAX_CACHE_SIZE; - } - }; - } } diff --git a/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java b/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java index 8a165676f5..00a18b612c 100644 --- a/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java @@ -30,10 +30,12 @@ import org.thoughtcrime.securesms.database.model.DisplayRecord; import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.util.InvalidMessageException; +import org.thoughtcrime.securesms.util.LRUCache; import java.lang.ref.SoftReference; -import java.util.LinkedHashMap; +import java.util.Collections; import java.util.List; +import java.util.Map; public class EncryptingSmsDatabase extends SmsDatabase { @@ -156,37 +158,25 @@ public class EncryptingSmsDatabase extends SmsDatabase { private static class PlaintextCache { private static final int MAX_CACHE_SIZE = 2000; - - private static final LinkedHashMap> decryptedBodyCache - = new LinkedHashMap>() - { - @Override - protected boolean removeEldestEntry(Entry> eldest) { - return this.size() > MAX_CACHE_SIZE; - } - }; + private static final Map> decryptedBodyCache = + Collections.synchronizedMap(new LRUCache>(MAX_CACHE_SIZE)); public void put(String ciphertext, String plaintext) { decryptedBodyCache.put(ciphertext, new SoftReference(plaintext)); } public String get(String ciphertext) { - synchronized (decryptedBodyCache) { - SoftReference plaintextReference = decryptedBodyCache.get(ciphertext); - - if (plaintextReference != null) { - String plaintext = plaintextReference.get(); - - if (plaintext != null) { - return plaintext; - } else { - decryptedBodyCache.remove(ciphertext); - return null; - } - } + SoftReference plaintextReference = decryptedBodyCache.get(ciphertext); - return null; + if (plaintextReference != null) { + String plaintext = plaintextReference.get(); + + if (plaintext != null) { + return plaintext; + } } + + return null; } } } diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 9614ccae6d..76bf2af903 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -41,12 +41,16 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.InvalidMessageException; +import org.thoughtcrime.securesms.util.LRUCache; import org.thoughtcrime.securesms.util.ListenableFutureTask; import org.thoughtcrime.securesms.util.Trimmer; import org.thoughtcrime.securesms.util.Util; import java.io.UnsupportedEncodingException; +import java.lang.ref.SoftReference; +import java.util.Collections; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; @@ -130,6 +134,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns { }; public static final ExecutorService slideResolver = Util.newSingleThreadedLifoExecutor(); + private static final Map> slideCache = + Collections.synchronizedMap(new LRUCache>(20)); public MmsDatabase(Context context, SQLiteOpenHelper databaseHelper) { super(context, databaseHelper); @@ -801,6 +807,12 @@ public class MmsDatabase extends Database implements MmsSmsColumns { private ListenableFutureTask getSlideDeck(final MasterSecret masterSecret, final long id) { + ListenableFutureTask future = getCachedSlideDeck(id); + + if (future != null) { + return future; + } + Callable task = new Callable() { @Override public SlideDeck call() throws Exception { @@ -808,16 +820,43 @@ public class MmsDatabase extends Database implements MmsSmsColumns { return null; 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)); + + return slideDeck; } }; - ListenableFutureTask future = new ListenableFutureTask(task, null); + future = new ListenableFutureTask(task, null); slideResolver.execute(future); return future; } + private ListenableFutureTask getCachedSlideDeck(final long id) { + SoftReference reference = slideCache.get(id); + + if (reference != null) { + final SlideDeck slideDeck = reference.get(); + + if (slideDeck != null) { + Callable task = new Callable() { + @Override + public SlideDeck call() throws Exception { + return slideDeck; + } + }; + + ListenableFutureTask future = new ListenableFutureTask(task, null); + future.run(); + + return future; + } + } + + return null; + } + public void close() { cursor.close(); } diff --git a/src/org/thoughtcrime/securesms/mms/ImageSlide.java b/src/org/thoughtcrime/securesms/mms/ImageSlide.java index 620706497c..d134d2507d 100644 --- a/src/org/thoughtcrime/securesms/mms/ImageSlide.java +++ b/src/org/thoughtcrime/securesms/mms/ImageSlide.java @@ -33,13 +33,15 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.util.BitmapUtil; +import org.thoughtcrime.securesms.util.LRUCache; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; 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.pdu.PduPart; @@ -47,13 +49,8 @@ import ws.com.google.android.mms.pdu.PduPart; public class ImageSlide extends Slide { private static final int MAX_CACHE_SIZE = 10; - - private static final LinkedHashMap> thumbnailCache = new LinkedHashMap>() { - @Override - protected boolean removeEldestEntry(Entry> eldest) { - return this.size() > MAX_CACHE_SIZE; - } - }; + private static final Map> thumbnailCache = + Collections.synchronizedMap(new LRUCache>(MAX_CACHE_SIZE)); public ImageSlide(Context context, MasterSecret masterSecret, PduPart part) { super(context, masterSecret, part); diff --git a/src/org/thoughtcrime/securesms/mms/TextSlide.java b/src/org/thoughtcrime/securesms/mms/TextSlide.java index 2fc5204490..330490d93f 100644 --- a/src/org/thoughtcrime/securesms/mms/TextSlide.java +++ b/src/org/thoughtcrime/securesms/mms/TextSlide.java @@ -16,30 +16,28 @@ */ package org.thoughtcrime.securesms.mms; -import java.io.UnsupportedEncodingException; -import java.util.LinkedHashMap; -import java.util.Map.Entry; +import android.content.Context; +import android.net.Uri; +import android.util.Log; 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.pdu.CharacterSets; 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 { private static final int MAX_CACHE_SIZE = 10; - - private static final LinkedHashMap textCache = new LinkedHashMap() { - @Override - protected boolean removeEldestEntry(Entry eldest) { - return this.size() > MAX_CACHE_SIZE; - } - }; - + private static final Map> textCache = + Collections.synchronizedMap(new LRUCache>(MAX_CACHE_SIZE)); + public TextSlide(Context context, MasterSecret masterSecret, PduPart part) { super(context, masterSecret, part); } @@ -54,16 +52,25 @@ public class TextSlide extends Slide { } @Override - public String getText() { + public String getText() { try { - if (textCache.containsKey(part.getDataUri())) - return textCache.get(part.getDataUri()); - + SoftReference reference = 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())); - textCache.put(part.getDataUri(), text); + textCache.put(part.getDataUri(), new SoftReference(text)); return text; } catch (UnsupportedEncodingException uee) { + Log.w("TextSlide", uee); return new String(getPartData()); } } @@ -75,7 +82,7 @@ public class TextSlide extends Slide { part.setData(message.getBytes(CharacterSets.MIMENAME_ISO_8859_1)); 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) { Log.w("TextSlide", "ISO_8859_1 must be supported!", e); @@ -89,5 +96,4 @@ public class TextSlide extends Slide { return part; } - }