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.
|
* simpler for now.) Maybe we can just use SimpleCursorAdapter.
|
||||||
*/
|
*/
|
||||||
public class AccountSelectorAdapter extends CursorAdapter {
|
public class AccountSelectorAdapter extends CursorAdapter {
|
||||||
private static final int REFRESH_INTERVAL = 3000; // in ms
|
|
||||||
|
|
||||||
private static final String[] PROJECTION = new String[] {
|
private static final String[] PROJECTION = new String[] {
|
||||||
EmailContent.RECORD_ID,
|
EmailContent.RECORD_ID,
|
||||||
EmailContent.Account.DISPLAY_NAME,
|
EmailContent.Account.DISPLAY_NAME,
|
||||||
@ -58,7 +56,7 @@ public class AccountSelectorAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
public static Loader<Cursor> createLoader(Context context) {
|
public static Loader<Cursor> createLoader(Context context) {
|
||||||
return new ThrottlingCursorLoader(context, EmailContent.Account.CONTENT_URI, PROJECTION,
|
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) {
|
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_NORMAL = 0;
|
||||||
public static final int MODE_MOVE_TO_TARGET = 1;
|
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,
|
private static final String[] PROJECTION = new String[] { MailboxColumns.ID,
|
||||||
MailboxColumns.DISPLAY_NAME, MailboxColumns.TYPE, MailboxColumns.UNREAD_COUNT,
|
MailboxColumns.DISPLAY_NAME, MailboxColumns.TYPE, MailboxColumns.UNREAD_COUNT,
|
||||||
MailboxColumns.MESSAGE_COUNT};
|
MailboxColumns.MESSAGE_COUNT};
|
||||||
@ -177,8 +175,7 @@ import android.widget.TextView;
|
|||||||
public MailboxesLoader(Context context, long accountId, int mode) {
|
public MailboxesLoader(Context context, long accountId, int mode) {
|
||||||
super(context, EmailContent.Mailbox.CONTENT_URI,
|
super(context, EmailContent.Mailbox.CONTENT_URI,
|
||||||
MailboxesAdapter.PROJECTION, getSelection(mode),
|
MailboxesAdapter.PROJECTION, getSelection(mode),
|
||||||
new String[] { String.valueOf(accountId) },
|
new String[] { String.valueOf(accountId) }, MAILBOX_ORDER_BY);
|
||||||
MAILBOX_ORDER_BY, AUTO_REQUERY_TIMEOUT);
|
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mMode = mode;
|
mMode = mode;
|
||||||
}
|
}
|
||||||
|
@ -87,10 +87,6 @@ import java.util.Set;
|
|||||||
private final ColorStateList mTextColorPrimary;
|
private final ColorStateList mTextColorPrimary;
|
||||||
private final ColorStateList mTextColorSecondary;
|
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 mDateFormat;
|
||||||
private final java.text.DateFormat mTimeFormat;
|
private final java.text.DateFormat mTimeFormat;
|
||||||
|
|
||||||
@ -309,7 +305,7 @@ import java.util.Set;
|
|||||||
// Initialize with no where clause. We'll set it later.
|
// Initialize with no where clause. We'll set it later.
|
||||||
super(context, EmailContent.Message.CONTENT_URI,
|
super(context, EmailContent.Message.CONTENT_URI,
|
||||||
MESSAGE_PROJECTION, null, null,
|
MESSAGE_PROJECTION, null, null,
|
||||||
EmailContent.MessageColumns.TIMESTAMP + " DESC", REFRESH_INTERVAL_MS);
|
EmailContent.MessageColumns.TIMESTAMP + " DESC");
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mMailboxId = mailboxId;
|
mMailboxId = mailboxId;
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,15 @@
|
|||||||
|
|
||||||
package com.android.email.data;
|
package com.android.email.data;
|
||||||
|
|
||||||
|
import com.android.email.Clock;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.CursorLoader;
|
import android.content.CursorLoader;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.security.InvalidParameterException;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
|
||||||
@ -30,29 +33,66 @@ import java.util.TimerTask;
|
|||||||
*
|
*
|
||||||
* This class overrides {@link android.content.Loader#onContentChanged}, and instead of immediately
|
* This class overrides {@link android.content.Loader#onContentChanged}, and instead of immediately
|
||||||
* requerying, it waits until the specified timeout before doing so.
|
* 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 {
|
public class ThrottlingCursorLoader extends CursorLoader {
|
||||||
private static final boolean DEBUG = false; // Don't submit with true
|
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 static Timer sTimer = new Timer();
|
||||||
|
|
||||||
|
private final Clock mClock;
|
||||||
|
|
||||||
/** Handler for the UI thread. */
|
/** Handler for the UI thread. */
|
||||||
private final Handler mHandler = new Handler();
|
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. */
|
/** 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;
|
private ForceLoadTimerTask mRunningForceLoadTimerTask;
|
||||||
|
|
||||||
/**
|
/** Constructor with default timeout */
|
||||||
* Constructor. Same as the one of {@link CursorLoader}, but takes {@code timeoutSecond}.
|
|
||||||
*
|
|
||||||
* @param timeout Content change auto-requery timeout in milliseconds.
|
|
||||||
*/
|
|
||||||
public ThrottlingCursorLoader(Context context, Uri uri, String[] projection, String selection,
|
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);
|
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) {
|
private void debugLog(String message) {
|
||||||
@ -98,19 +138,34 @@ public class ThrottlingCursorLoader extends CursorLoader {
|
|||||||
super.stopLoading();
|
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
|
@Override
|
||||||
public void onContentChanged() {
|
public void onContentChanged() {
|
||||||
if (DEBUG) debugLog("onContentChanged");
|
if (DEBUG) debugLog("onContentChanged");
|
||||||
if (mTimeout <= 0) {
|
|
||||||
forceLoad();
|
updateTimeout();
|
||||||
|
|
||||||
|
if (isForceLoadScheduled()) {
|
||||||
|
if (DEBUG) debugLog(" forceLoad already scheduled.");
|
||||||
} else {
|
} else {
|
||||||
if (isForceLoadScheduled()) {
|
if (DEBUG) debugLog(" scheduling forceLoad.");
|
||||||
if (DEBUG) debugLog(" forceLoad already scheduled.");
|
mRunningForceLoadTimerTask = new ForceLoadTimerTask();
|
||||||
} else {
|
sTimer.schedule(mRunningForceLoadTimerTask, mTimeout);
|
||||||
if (DEBUG) debugLog(" scheduling forceLoad.");
|
|
||||||
mRunningForceLoadTimerTask = new ForceLoadTimerTask();
|
|
||||||
sTimer.schedule(mRunningForceLoadTimerTask, mTimeout);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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() {
|
public void advance() {
|
||||||
mTime++;
|
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