Better asynchronous loading for Recipient information.

1) Switch back from AsyncTasks to an Executor and Futures.

2) Make the Executor operate LIFO.

3) Make the Executor thread a BACKGROUND_PRIORITY thread.
This commit is contained in:
Moxie Marlinspike 2012-12-27 12:00:14 -08:00
parent 9939830551
commit 9b45e6068b
5 changed files with 1300 additions and 105 deletions

View File

@ -20,8 +20,11 @@ import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.util.Log;
import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails; import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails;
import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -46,38 +49,33 @@ public class Recipient implements Parcelable {
private RecipientModifiedListener listener; private RecipientModifiedListener listener;
private boolean asynchronousUpdateComplete = false; private boolean asynchronousUpdateComplete = false;
// public Recipient(String name, String number, Uri contactUri, Bitmap contactPhoto) { public Recipient(String number, Bitmap contactPhoto,
// this(name, number, contactPhoto); ListenableFutureTask<RecipientDetails> future)
// this.contactUri = contactUri; {
// } this.number = number;
this.contactPhoto.set(contactPhoto);
// public Recipient(String number, Bitmap contactPhoto, future.setListener(new FutureTaskListener<RecipientDetails>() {
// ListenableFutureTask<RecipientDetails> future) @Override
// { public void onSuccess(RecipientDetails result) {
// this.number = number; if (result != null) {
// this.contactUri = null; Recipient.this.name.set(result.name);
// this.contactPhoto.set(contactPhoto); Recipient.this.contactUri.set(result.contactUri);
// Recipient.this.contactPhoto.set(result.avatar);
// future.setListener(new FutureTaskListener<RecipientDetails>() {
// @Override synchronized(this) {
// public void onSuccess(RecipientDetails result) { if (listener == null) asynchronousUpdateComplete = true;
// if (result != null) { else listener.onModified(Recipient.this);
// Recipient.this.name.set(result.name); }
// Recipient.this.contactPhoto.set(result.avatar); }
// }
// synchronized(this) {
// if (listener == null) asynchronousUpdateComplete = true; @Override
// else listener.onModified(Recipient.this); public void onFailure(Throwable error) {
// } Log.w("Recipient", error);
// } }
// } });
// }
// @Override
// public void onFailure(Throwable error) {
// Log.w("Recipient", error);
// }
// });
// }
public Recipient(String name, String number, Uri contactUri, Bitmap contactPhoto) { public Recipient(String name, String number, Uri contactUri, Bitmap contactPhoto) {
this.number = number; this.number = number;
@ -110,18 +108,18 @@ public class Recipient implements Parcelable {
return 0; return 0;
} }
public void updateAsynchronousContent(RecipientDetails result) { // public void updateAsynchronousContent(RecipientDetails result) {
if (result != null) { // if (result != null) {
Recipient.this.name.set(result.name); // Recipient.this.name.set(result.name);
Recipient.this.contactUri.set(result.contactUri); // Recipient.this.contactUri.set(result.contactUri);
Recipient.this.contactPhoto.set(result.avatar); // Recipient.this.contactPhoto.set(result.avatar);
//
synchronized(this) { // synchronized(this) {
if (listener == null) asynchronousUpdateComplete = true; // if (listener == null) asynchronousUpdateComplete = true;
else listener.onModified(Recipient.this); // else listener.onModified(Recipient.this);
} // }
} // }
} // }
public synchronized void setListener(RecipientModifiedListener listener) { public synchronized void setListener(RecipientModifiedListener listener) {
this.listener = listener; this.listener = listener;

View File

@ -21,22 +21,27 @@ import android.database.Cursor;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.Process;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup; import android.provider.ContactsContract.PhoneLookup;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.util.LRUCache; import org.thoughtcrime.securesms.util.LRUCache;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.Util;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
public class RecipientProvider { public class RecipientProvider {
private static final Map<String,Recipient> recipientCache = Collections.synchronizedMap(new LRUCache<String,Recipient>(1000)); private static final Map<String,Recipient> recipientCache = Collections.synchronizedMap(new LRUCache<String,Recipient>(1000));
// private static final ExecutorService asyncRecipientResolver = Executors.newSingleThreadExecutor(); // private static final ExecutorService asyncRecipientResolver = Executors.newSingleThreadExecutor();
private static final ExecutorService asyncRecipientResolver = Util.newSingleThreadedLifoExecutor();
private static final String[] CALLER_ID_PROJECTION = new String[] { private static final String[] CALLER_ID_PROJECTION = new String[] {
PhoneLookup.DISPLAY_NAME, PhoneLookup.DISPLAY_NAME,
@ -70,70 +75,43 @@ public class RecipientProvider {
private Recipient getAsynchronousRecipient(final Context context, final String number) { private Recipient getAsynchronousRecipient(final Context context, final String number) {
Log.w("RecipientProvider", "Cache miss [ASYNC]!"); Log.w("RecipientProvider", "Cache miss [ASYNC]!");
Recipient recipient = new Recipient(null, number, null, ContactPhotoFactory.getDefaultContactPhoto(context)); // Recipient recipient = new Recipient(null, number, null, ContactPhotoFactory.getDefaultContactPhoto(context));
recipientCache.put(number, recipient);
new AsyncTask<Recipient, Void, RecipientDetails>() {
private Recipient recipient;
@Override
protected RecipientDetails doInBackground(Recipient... recipient) {
this.recipient = recipient[0];
return getRecipientDetails(context, number);
}
@Override
protected void onPostExecute(RecipientDetails result) {
recipient.updateAsynchronousContent(result);
}
}.execute(recipient);
return recipient;
// ListenableFutureTask<RecipientDetails> future =
// new ListenableFutureTask<RecipientDetails>(new Callable<RecipientDetails>() {
// @Override
// public RecipientDetails call() throws Exception {
// return getRecipientDetails(context, number);
//// RecipientDetails recipientDetails = getRecipientDetails();
////
//// if (recipientDeta)
////
//// Recipient cachedRecipient = recipientCache.get(number);
////
//// if (cachedRecipient != null) {
//// return new RecipientDetails(cachedRecipient.getName(), cachedRecipient.getContactPhoto());
//// }
////
//// Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
//// Cursor cursor = context.getContentResolver().query(uri, CALLER_ID_PROJECTION,
//// null, null, null);
////
//// try {
//// if (cursor != null && cursor.moveToFirst()) {
//// Uri contactUri = Contacts.getLookupUri(cursor.getLong(2), cursor.getString(1));
//// Bitmap contactPhoto = getContactPhoto(context, Uri.withAppendedPath(Contacts.CONTENT_URI,
//// cursor.getLong(2)+""));
////
//// recipientCache.put(number, new Recipient(cursor.getString(0), number, contactPhoto));
//// return new RecipientDetails(cursor.getString(0), contactPhoto);
//// } else {
//// recipientCache.put(number, new Recipient(null, number, ContactPhotoFactory.getDefaultContactPhoto(context)));
//// }
//// } finally {
//// if (cursor != null)
//// cursor.close();
//// }
////
//// return null;
// }
// }, null);
//
// asyncRecipientResolver.submit(future);
// Recipient recipient = new Recipient(number, ContactPhotoFactory.getDefaultContactPhoto(context), future);
// recipientCache.put(number, recipient); // recipientCache.put(number, recipient);
// //
// new AsyncTask<Recipient, Void, RecipientDetails>() {
// private Recipient recipient;
//
// @Override
// protected RecipientDetails doInBackground(Recipient... recipient) {
// this.recipient = recipient[0];
// return getRecipientDetails(context, number);
// }
//
// @Override
// protected void onPostExecute(RecipientDetails result) {
// recipient.updateAsynchronousContent(result);
// }
// }.execute(recipient);
//
// return recipient; // return recipient;
// ListenableFutureTask<RecipientDetails> future = new ListenableFutureTask<RecipientDetails>(new Callable<RecipientDetails>() {
Callable<RecipientDetails> task = new Callable<RecipientDetails>() {
@Override
public RecipientDetails call() throws Exception {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return getRecipientDetails(context, number);
}
};
ListenableFutureTask<RecipientDetails> future = new ListenableFutureTask<RecipientDetails>(task, null);
asyncRecipientResolver.submit(future);
Recipient recipient = new Recipient(number, ContactPhotoFactory.getDefaultContactPhoto(context), future);
recipientCache.put(number, recipient);
return recipient;
//// return new Recipient(null, number, ContactPhotoFactory.getDefaultContactPhoto(context)); //// return new Recipient(null, number, ContactPhotoFactory.getDefaultContactPhoto(context));
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
package org.thoughtcrime.securesms.util;
public class LinkedBlockingLifoQueue<E> extends LinkedBlockingDeque<E> {
@Override
public void put(E runnable) throws InterruptedException {
super.putFirst(runnable);
}
@Override
public boolean add(E runnable) {
super.addFirst(runnable);
return true;
}
@Override
public boolean offer(E runnable) {
super.addFirst(runnable);
return true;
}
}

View File

@ -16,6 +16,10 @@
*/ */
package org.thoughtcrime.securesms.util; package org.thoughtcrime.securesms.util;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Util { public class Util {
public static byte[] combine(byte[] one, byte[] two) { public static byte[] combine(byte[] one, byte[] two) {
@ -62,6 +66,10 @@ public class Util {
return splitString; return splitString;
} }
public static ExecutorService newSingleThreadedLifoExecutor() {
return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingLifoQueue<Runnable>());
}
// public static Bitmap loadScaledBitmap(InputStream src, int targetWidth, int targetHeight) { // public static Bitmap loadScaledBitmap(InputStream src, int targetWidth, int targetHeight) {
// return BitmapFactory.decodeStream(src); // return BitmapFactory.decodeStream(src);
//// BitmapFactory.Options options = new BitmapFactory.Options(); //// BitmapFactory.Options options = new BitmapFactory.Options();