Make ThrottlingCursorLoader smarter
Now the class initially uses smaller timeout, and expand it when detecting multiple changes in a short period. This CL makes the UI look more responsive especially on the message list + message view mode. e.g. Starring on the message view will quickly be reflected to the message list. Bug 3024799 Bug 3027832 Change-Id: Ie2d44c3769d43e3fd0f54ee526556eb3bad5e288
This commit is contained in:
parent
2dbb510657
commit
4209ea36b0
@ -38,8 +38,6 @@ import android.widget.TextView;
|
||||
* simpler for now.) Maybe we can just use SimpleCursorAdapter.
|
||||
*/
|
||||
public class AccountSelectorAdapter extends CursorAdapter {
|
||||
private static final int REFRESH_INTERVAL = 3000; // in ms
|
||||
|
||||
private static final String[] PROJECTION = new String[] {
|
||||
EmailContent.RECORD_ID,
|
||||
EmailContent.Account.DISPLAY_NAME,
|
||||
@ -58,7 +56,7 @@ public class AccountSelectorAdapter extends CursorAdapter {
|
||||
|
||||
public static Loader<Cursor> createLoader(Context context) {
|
||||
return new ThrottlingCursorLoader(context, EmailContent.Account.CONTENT_URI, PROJECTION,
|
||||
null, null, ORDER_BY, REFRESH_INTERVAL);
|
||||
null, null, ORDER_BY);
|
||||
}
|
||||
|
||||
public AccountSelectorAdapter(Context context, Cursor c) {
|
||||
|
@ -53,8 +53,6 @@ import android.widget.TextView;
|
||||
public static final int MODE_NORMAL = 0;
|
||||
public static final int MODE_MOVE_TO_TARGET = 1;
|
||||
|
||||
private static final int AUTO_REQUERY_TIMEOUT = 3 * 1000; // in ms
|
||||
|
||||
private static final String[] PROJECTION = new String[] { MailboxColumns.ID,
|
||||
MailboxColumns.DISPLAY_NAME, MailboxColumns.TYPE, MailboxColumns.UNREAD_COUNT,
|
||||
MailboxColumns.MESSAGE_COUNT};
|
||||
@ -177,8 +175,7 @@ import android.widget.TextView;
|
||||
public MailboxesLoader(Context context, long accountId, int mode) {
|
||||
super(context, EmailContent.Mailbox.CONTENT_URI,
|
||||
MailboxesAdapter.PROJECTION, getSelection(mode),
|
||||
new String[] { String.valueOf(accountId) },
|
||||
MAILBOX_ORDER_BY, AUTO_REQUERY_TIMEOUT);
|
||||
new String[] { String.valueOf(accountId) }, MAILBOX_ORDER_BY);
|
||||
mContext = context;
|
||||
mMode = mode;
|
||||
}
|
||||
|
@ -87,10 +87,6 @@ import java.util.Set;
|
||||
private final ColorStateList mTextColorPrimary;
|
||||
private final ColorStateList mTextColorSecondary;
|
||||
|
||||
// How long we want to wait for refreshes (a good starting guess)
|
||||
// I suspect this could be lowered down to even 1000 or so, but this seems ok for now
|
||||
private static final int REFRESH_INTERVAL_MS = 2500;
|
||||
|
||||
private final java.text.DateFormat mDateFormat;
|
||||
private final java.text.DateFormat mTimeFormat;
|
||||
|
||||
@ -309,7 +305,7 @@ import java.util.Set;
|
||||
// Initialize with no where clause. We'll set it later.
|
||||
super(context, EmailContent.Message.CONTENT_URI,
|
||||
MESSAGE_PROJECTION, null, null,
|
||||
EmailContent.MessageColumns.TIMESTAMP + " DESC", REFRESH_INTERVAL_MS);
|
||||
EmailContent.MessageColumns.TIMESTAMP + " DESC");
|
||||
mContext = context;
|
||||
mMailboxId = mailboxId;
|
||||
}
|
||||
|
@ -16,12 +16,15 @@
|
||||
|
||||
package com.android.email.data;
|
||||
|
||||
import com.android.email.Clock;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.CursorLoader;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
@ -30,29 +33,66 @@ import java.util.TimerTask;
|
||||
*
|
||||
* This class overrides {@link android.content.Loader#onContentChanged}, and instead of immediately
|
||||
* requerying, it waits until the specified timeout before doing so.
|
||||
*
|
||||
* There are two timeout settings: {@link #mMinTimeout} and {@link #mMaxTimeout}.
|
||||
* We normally use {@link #mMinTimeout}, but if we detect more than one change in
|
||||
* the {@link #TIMEOUT_EXTEND_INTERVAL} period, we double it, until it reaches {@link #mMaxTimeout}.
|
||||
*/
|
||||
public class ThrottlingCursorLoader extends CursorLoader {
|
||||
private static final boolean DEBUG = false; // Don't submit with true
|
||||
|
||||
/* package */ static final int TIMEOUT_EXTEND_INTERVAL = 500;
|
||||
|
||||
private static final int DEFAULT_MIN_TIMEOUT = 150;
|
||||
private static final int DEFAULT_MAX_TIMEOUT = 2500;
|
||||
|
||||
private static Timer sTimer = new Timer();
|
||||
|
||||
private final Clock mClock;
|
||||
|
||||
/** Handler for the UI thread. */
|
||||
private final Handler mHandler = new Handler();
|
||||
|
||||
/** Minimum (default) timeout */
|
||||
private final int mMinTimeout;
|
||||
|
||||
/** Max timeout */
|
||||
private final int mMaxTimeout;
|
||||
|
||||
/** Content change auto-requery timeout, in milliseconds. */
|
||||
private final int mTimeout;
|
||||
private int mTimeout;
|
||||
|
||||
/** When onChanged() was last called. */
|
||||
private long mLastOnChangedTime;
|
||||
|
||||
private ForceLoadTimerTask mRunningForceLoadTimerTask;
|
||||
|
||||
/**
|
||||
* Constructor. Same as the one of {@link CursorLoader}, but takes {@code timeoutSecond}.
|
||||
*
|
||||
* @param timeout Content change auto-requery timeout in milliseconds.
|
||||
*/
|
||||
/** Constructor with default timeout */
|
||||
public ThrottlingCursorLoader(Context context, Uri uri, String[] projection, String selection,
|
||||
String[] selectionArgs, String sortOrder, int timeout) {
|
||||
String[] selectionArgs, String sortOrder) {
|
||||
this(context, uri, projection, selection, selectionArgs, sortOrder, DEFAULT_MIN_TIMEOUT,
|
||||
DEFAULT_MAX_TIMEOUT);
|
||||
}
|
||||
|
||||
/** Constructor that takes custom timeout */
|
||||
public ThrottlingCursorLoader(Context context, Uri uri, String[] projection, String selection,
|
||||
String[] selectionArgs, String sortOrder, int minTimeout, int maxTimeout) {
|
||||
this(context, uri, projection, selection, selectionArgs, sortOrder, minTimeout, maxTimeout,
|
||||
Clock.INSTANCE);
|
||||
}
|
||||
|
||||
/** Constructor for tests. Clock is injectable. */
|
||||
/* package */ ThrottlingCursorLoader(Context context, Uri uri, String[] projection,
|
||||
String selection, String[] selectionArgs, String sortOrder,
|
||||
int minTimeout, int maxTimeout, Clock clock) {
|
||||
super(context, uri, projection, selection, selectionArgs, sortOrder);
|
||||
mTimeout = timeout;
|
||||
mClock = clock;
|
||||
if (maxTimeout < minTimeout) {
|
||||
throw new InvalidParameterException();
|
||||
}
|
||||
mMinTimeout = minTimeout;
|
||||
mMaxTimeout = maxTimeout;
|
||||
mTimeout = mMinTimeout;
|
||||
}
|
||||
|
||||
private void debugLog(String message) {
|
||||
@ -98,12 +138,28 @@ public class ThrottlingCursorLoader extends CursorLoader {
|
||||
super.stopLoading();
|
||||
}
|
||||
|
||||
/* package */ void updateTimeout() {
|
||||
final long now = mClock.getTime();
|
||||
if ((now - mLastOnChangedTime) <= TIMEOUT_EXTEND_INTERVAL) {
|
||||
if (DEBUG) debugLog("Extending timeout: " + mTimeout);
|
||||
mTimeout *= 2;
|
||||
if (mTimeout >= mMaxTimeout) {
|
||||
mTimeout = mMaxTimeout;
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) debugLog("Resetting timeout.");
|
||||
mTimeout = mMinTimeout;
|
||||
}
|
||||
|
||||
mLastOnChangedTime = now;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContentChanged() {
|
||||
if (DEBUG) debugLog("onContentChanged");
|
||||
if (mTimeout <= 0) {
|
||||
forceLoad();
|
||||
} else {
|
||||
|
||||
updateTimeout();
|
||||
|
||||
if (isForceLoadScheduled()) {
|
||||
if (DEBUG) debugLog(" forceLoad already scheduled.");
|
||||
} else {
|
||||
@ -112,7 +168,6 @@ public class ThrottlingCursorLoader extends CursorLoader {
|
||||
sTimer.schedule(mRunningForceLoadTimerTask, mTimeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link TimerTask} to call {@link #forceLoad} on the UI thread.
|
||||
@ -140,4 +195,12 @@ public class ThrottlingCursorLoader extends CursorLoader {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ int getTimeoutForTest() {
|
||||
return mTimeout;
|
||||
}
|
||||
|
||||
/* package */ long getLastOnChangedTimeForTest() {
|
||||
return mLastOnChangedTime;
|
||||
}
|
||||
}
|
||||
|
@ -29,4 +29,8 @@ public class MockClock extends Clock {
|
||||
public void advance() {
|
||||
mTime++;
|
||||
}
|
||||
|
||||
public void advance(long milliseconds) {
|
||||
mTime += milliseconds;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.email.data;
|
||||
|
||||
import com.android.email.MockClock;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
public class ThrottlingCursorLoaderTest extends AndroidTestCase {
|
||||
|
||||
public void testUpdateTimeout() {
|
||||
MockClock clock = new MockClock();
|
||||
ThrottlingCursorLoader l = new ThrottlingCursorLoader(getContext(), null, null, null, null,
|
||||
null, 100, 500, clock);
|
||||
|
||||
// Check initial value
|
||||
assertEquals(100, l.getTimeoutForTest());
|
||||
|
||||
// First call -- won't change the timeout
|
||||
l.updateTimeout();
|
||||
assertEquals(100, l.getTimeoutForTest());
|
||||
|
||||
// Call again in 10 ms -- will extend timeout.
|
||||
clock.advance(10);
|
||||
l.updateTimeout();
|
||||
assertEquals(200, l.getTimeoutForTest());
|
||||
|
||||
// Call again in TIMEOUT_EXTEND_INTERAVL ms -- will extend timeout.
|
||||
clock.advance(ThrottlingCursorLoader.TIMEOUT_EXTEND_INTERVAL);
|
||||
l.updateTimeout();
|
||||
assertEquals(400, l.getTimeoutForTest());
|
||||
|
||||
// Again -- timeout reaches max.
|
||||
clock.advance(ThrottlingCursorLoader.TIMEOUT_EXTEND_INTERVAL);
|
||||
l.updateTimeout();
|
||||
assertEquals(500, l.getTimeoutForTest());
|
||||
|
||||
// Call in TIMEOUT_EXTEND_INTERAVL + 1 ms -- timeout will get reset.
|
||||
clock.advance(ThrottlingCursorLoader.TIMEOUT_EXTEND_INTERVAL + 1);
|
||||
l.updateTimeout();
|
||||
assertEquals(100, l.getTimeoutForTest());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user