Merge pull request #649 from dosiecki/master

Android: Bugfixes and Lagfixes
This commit is contained in:
Samuel Clay 2015-01-05 14:38:22 -08:00
commit 5a783a7871
13 changed files with 245 additions and 167 deletions

View file

@ -81,16 +81,6 @@ public abstract class ItemsList extends NbActivity implements StateChangedListen
itemListFragment.hasUpdated();
}
private void getFirstStories() {
triggerRefresh(AppConstants.READING_STORY_PRELOAD, 0);
}
public void triggerRefresh(int desiredStoryCount, int totalSeen) {
boolean gotSome = NBSyncService.requestMoreForFeed(fs, desiredStoryCount, totalSeen);
if (gotSome) triggerSync();
updateStatusIndicators();
}
public void markItemListAsRead() {
FeedUtils.markFeedsRead(fs, null, null, this);
Toast.makeText(this, R.string.toast_marked_stories_as_read, Toast.LENGTH_SHORT).show();
@ -144,9 +134,6 @@ public abstract class ItemsList extends NbActivity implements StateChangedListen
private void updateStatusIndicators() {
boolean isLoading = NBSyncService.isFeedSetSyncing(this.fs);
setProgressBarIndeterminateVisibility(isLoading);
if (itemListFragment != null) {
itemListFragment.setLoading(isLoading);
}
if (overlayStatusText != null) {
String syncStatus = NBSyncService.getSyncStatusMessage();
@ -171,7 +158,6 @@ public abstract class ItemsList extends NbActivity implements StateChangedListen
itemListFragment.resetEmptyState();
itemListFragment.hasUpdated();
itemListFragment.scrollToTop();
getFirstStories();
}
public abstract void updateStoryOrderPreference(StoryOrder newValue);
@ -183,7 +169,6 @@ public abstract class ItemsList extends NbActivity implements StateChangedListen
itemListFragment.resetEmptyState();
itemListFragment.hasUpdated();
itemListFragment.scrollToTop();
getFirstStories();
}
protected abstract void updateReadFilterPreference(ReadFilter newValue);

View file

@ -73,13 +73,12 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
protected void onResume() {
super.onResume();
// clear the read-this-session flag from stories so they don't show up in the wrong place
NBSyncService.clearPendingStoryRequest();
NBSyncService.setActivationMode(NBSyncService.ActivationMode.ALL);
FeedUtils.activateAllStories();
FeedUtils.clearReadingSession();
updateStatusIndicators();
// this view doesn't show stories, it is safe to alter stories
FeedUtils.activateAllStories();
NBSyncService.setActivationMode(NBSyncService.ActivationMode.ALL);
triggerSync();
if (PrefsUtils.isLightThemeSelected(this) != isLightTheme) {

View file

@ -209,11 +209,6 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
// if this is the first time we've found a cursor, we know the onCreate chain is done
if (this.pager == null) {
int currentUnreadCount = getUnreadCount();
if (currentUnreadCount > this.startingUnreadCount ) {
this.startingUnreadCount = currentUnreadCount;
}
// set up the pager after the unread count, so the first mark-read doesn't happen too quickly
setupPager();
}
@ -328,6 +323,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
@Override
protected void handleUpdate(boolean freshData) {
enableMainProgress(NBSyncService.isFeedSetSyncing(this.fs));
updateOverlayNav();
if (freshData) updateCursor();
}
@ -430,19 +426,23 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
}
/**
* Update the left/right overlay UI after the read-state of a story changes or we navigate in any way.
* Update the next/back overlay UI after the read-state of a story changes or we navigate in any way.
*/
private void updateOverlayNav() {
int currentUnreadCount = getUnreadCount();
if (currentUnreadCount > this.startingUnreadCount ) {
this.startingUnreadCount = currentUnreadCount;
}
this.overlayLeft.setEnabled(this.getLastReadPosition(false) != -1);
this.overlayRight.setText((getUnreadCount() > 0) ? R.string.overlay_next : R.string.overlay_done);
this.overlayRight.setBackgroundResource((getUnreadCount() > 0) ? R.drawable.selector_overlay_bg_right : R.drawable.selector_overlay_bg_right_done);
this.overlayRight.setText((currentUnreadCount > 0) ? R.string.overlay_next : R.string.overlay_done);
this.overlayRight.setBackgroundResource((currentUnreadCount > 0) ? R.drawable.selector_overlay_bg_right : R.drawable.selector_overlay_bg_right_done);
if (this.startingUnreadCount == 0 ) {
// sessions with no unreads just show a full progress bar
this.overlayProgress.setMax(1);
this.overlayProgress.setProgress(1);
} else {
int unreadProgress = this.startingUnreadCount - getUnreadCount();
int unreadProgress = this.startingUnreadCount - currentUnreadCount;
this.overlayProgress.setMax(this.startingUnreadCount);
this.overlayProgress.setProgress(unreadProgress);
}
@ -488,13 +488,17 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
* load is not needed and all latches are tripped.
*/
private void checkStoryCount(int position) {
if (AppConstants.VERBOSE_LOG) {
Log.d(this.getClass().getName(), String.format("story %d of %d selected, stopLoad: %b", position, stories.getCount(), stopLoading));
}
// if the pager is at or near the number of stories loaded, check for more unless we know we are at the end of the list
if ((position + AppConstants.READING_STORY_PRELOAD) >= stories.getCount()) {
if (stories == null ) {
triggerRefresh(position + AppConstants.READING_STORY_PRELOAD);
}
} else {
if (AppConstants.VERBOSE_LOG) {
Log.d(this.getClass().getName(), String.format("story %d of %d selected, stopLoad: %b", position, stories.getCount(), stopLoading));
}
// if the pager is at or near the number of stories loaded, check for more unless we know we are at the end of the list
if ((position + AppConstants.READING_STORY_PRELOAD) >= stories.getCount()) {
triggerRefresh(position + AppConstants.READING_STORY_PRELOAD);
}
}
if (stopLoading) {
// if we terminated because we are well and truly done, break any search loops and stop progress indication

View file

@ -153,8 +153,10 @@ public class FeedProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
final SQLiteDatabase rdb = databaseHelper.getReadableDatabase();
final LoggingDatabase db = new LoggingDatabase(rdb);
final LoggingDatabase db;
synchronized (BlurDatabaseHelper.RW_MUTEX) {
db = new LoggingDatabase(databaseHelper.getReadableDatabase());
}
switch (uriMatcher.match(uri)) {
case USERS:

View file

@ -46,7 +46,6 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
protected StoryItemsAdapter adapter;
protected DefaultFeedView defaultFeedView;
protected StateFilter currentState;
private boolean isLoading = true;
private boolean cursorSeenYet = false;
private boolean firstStorySeenYet = false;
@ -83,17 +82,17 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
}
}
private void triggerRefresh(int desiredStoryCount, int totalSeen) {
boolean gotSome = NBSyncService.requestMoreForFeed(getFeedSet(), desiredStoryCount, totalSeen);
if (gotSome) triggerSync();
}
/**
* Indicate that the DB was cleared.
*/
public void resetEmptyState() {
cursorSeenYet = false;
firstStorySeenYet = false;
setLoading(true);
}
public void setLoading(boolean loading) {
isLoading = loading;
}
private void updateLoadingIndicator() {
@ -107,6 +106,7 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
}
TextView emptyView = (TextView) itemList.getEmptyView();
boolean isLoading = NBSyncService.isFeedSetSyncing(getFeedSet());
if (isLoading || (!cursorSeenYet)) {
emptyView.setText(R.string.empty_list_view_loading);
} else {
@ -129,13 +129,9 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
@Override
public synchronized void onScroll(AbsListView view, int firstVisible, int visibleCount, int totalCount) {
// if we have seen a cursor, this method means the list was updated or scrolled. now is a good
// time to see if we need more stories
if (cursorSeenYet) {
// load an extra page or two worth of stories past the viewport
int desiredStoryCount = firstVisible + (visibleCount*2) + 1;
activity.triggerRefresh(desiredStoryCount, totalCount);
}
// load an extra page or two worth of stories past the viewport
int desiredStoryCount = firstVisible + (visibleCount*2) + 1;
triggerRefresh(desiredStoryCount, totalCount);
}
@Override
@ -166,7 +162,7 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
if (cursor != null) {
cursorSeenYet = true;
if (cursor.getCount() < 1) {
activity.triggerRefresh(1, 0);
triggerRefresh(1, 0);
} else {
if (!firstStorySeenYet) {
// once we have at least a single story, we can instruct the sync service as to how to safely

View file

@ -1,5 +1,6 @@
package com.newsblur.fragment;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
@ -85,26 +86,28 @@ public class LoginProgressFragment extends Fragment {
@Override
protected void onPostExecute(NewsBlurResponse result) {
Context c = getActivity();
if (c == null) return; // we might have run past the lifecycle of the activity
if (!result.isError()) {
final Animation a = AnimationUtils.loadAnimation(getActivity(), R.anim.text_down);
final Animation a = AnimationUtils.loadAnimation(c, R.anim.text_down);
updateStatus.setText(R.string.login_logged_in);
loggingInProgress.setVisibility(View.GONE);
updateStatus.startAnimation(a);
loginProfilePicture.setVisibility(View.VISIBLE);
loginProfilePicture.setImageBitmap(UIUtils.roundCorners(PrefsUtils.getUserImage(getActivity()), 10f));
loginProfilePicture.setImageBitmap(UIUtils.roundCorners(PrefsUtils.getUserImage(c), 10f));
feedProgress.setVisibility(View.VISIBLE);
final Animation b = AnimationUtils.loadAnimation(getActivity(), R.anim.text_up);
final Animation b = AnimationUtils.loadAnimation(c, R.anim.text_up);
retrievingFeeds.setText(R.string.login_retrieving_feeds);
retrievingFeeds.startAnimation(b);
Intent startMain = new Intent(getActivity(), Main.class);
getActivity().startActivity(startMain);
c.startActivity(startMain);
} else {
UIUtils.safeToast(getActivity(), result.getErrorMessage(), Toast.LENGTH_LONG);
startActivity(new Intent(getActivity(), Login.class));
UIUtils.safeToast(c, result.getErrorMessage(), Toast.LENGTH_LONG);
startActivity(new Intent(c, Login.class));
}
}
}

View file

@ -602,57 +602,63 @@ public class ReadingItemFragment extends NbFragment implements ClassifierDialogF
@Override
public void sharedCallback(String sharedText, boolean hasAlreadyBeenShared) {
view.findViewById(R.id.reading_share_bar).setVisibility(View.VISIBLE);
view.findViewById(R.id.share_bar_underline).setVisibility(View.VISIBLE);
if (!hasAlreadyBeenShared) {
if (!TextUtils.isEmpty(sharedText)) {
View commentView = inflater.inflate(R.layout.include_comment, null);
commentView.setTag(SetupCommentSectionTask.COMMENT_VIEW_BY + user.id);
try {
view.findViewById(R.id.reading_share_bar).setVisibility(View.VISIBLE);
view.findViewById(R.id.share_bar_underline).setVisibility(View.VISIBLE);
if (!hasAlreadyBeenShared) {
if (!TextUtils.isEmpty(sharedText)) {
View commentView = inflater.inflate(R.layout.include_comment, null);
commentView.setTag(SetupCommentSectionTask.COMMENT_VIEW_BY + user.id);
TextView commentText = (TextView) commentView.findViewById(R.id.comment_text);
commentText.setTag("commentBy" + user.id);
TextView commentText = (TextView) commentView.findViewById(R.id.comment_text);
commentText.setTag("commentBy" + user.id);
commentText.setText(sharedText);
TextView commentLocation = (TextView) commentView.findViewById(R.id.comment_location);
if (!TextUtils.isEmpty(user.location)) {
commentLocation.setText(user.location.toUpperCase());
} else {
commentLocation.setVisibility(View.GONE);
}
if (PrefsUtils.getUserImage(getActivity()) != null) {
ImageView commentImage = (ImageView) commentView.findViewById(R.id.comment_user_image);
commentImage.setImageBitmap(UIUtils.roundCorners(PrefsUtils.getUserImage(getActivity()), 10f));
}
TextView commentSharedDate = (TextView) commentView.findViewById(R.id.comment_shareddate);
commentSharedDate.setText(R.string.now);
TextView commentUsername = (TextView) commentView.findViewById(R.id.comment_username);
commentUsername.setText(user.username);
((LinearLayout) view.findViewById(R.id.reading_friend_comment_container)).addView(commentView);
ViewUtils.setupCommentCount(getActivity(), view, story.commentCount + 1);
final ImageView image = ViewUtils.createSharebarImage(getActivity(), imageLoader, user.photoUrl, user.id);
((FlowLayout) view.findViewById(R.id.reading_social_commentimages)).addView(image);
} else {
ViewUtils.setupShareCount(getActivity(), view, story.sharedUserIds.length + 1);
final ImageView image = ViewUtils.createSharebarImage(getActivity(), imageLoader, user.photoUrl, user.id);
((FlowLayout) view.findViewById(R.id.reading_social_shareimages)).addView(image);
}
} else {
View commentViewForUser = view.findViewWithTag(SetupCommentSectionTask.COMMENT_VIEW_BY + user.id);
TextView commentText = (TextView) view.findViewWithTag(SetupCommentSectionTask.COMMENT_BY + user.id);
commentText.setText(sharedText);
TextView commentLocation = (TextView) commentView.findViewById(R.id.comment_location);
if (!TextUtils.isEmpty(user.location)) {
commentLocation.setText(user.location.toUpperCase());
} else {
commentLocation.setVisibility(View.GONE);
}
if (PrefsUtils.getUserImage(getActivity()) != null) {
ImageView commentImage = (ImageView) commentView.findViewById(R.id.comment_user_image);
commentImage.setImageBitmap(UIUtils.roundCorners(PrefsUtils.getUserImage(getActivity()), 10f));
}
TextView commentSharedDate = (TextView) commentView.findViewById(R.id.comment_shareddate);
commentSharedDate.setText(R.string.now);
TextView commentUsername = (TextView) commentView.findViewById(R.id.comment_username);
commentUsername.setText(user.username);
((LinearLayout) view.findViewById(R.id.reading_friend_comment_container)).addView(commentView);
ViewUtils.setupCommentCount(getActivity(), view, story.commentCount + 1);
final ImageView image = ViewUtils.createSharebarImage(getActivity(), imageLoader, user.photoUrl, user.id);
((FlowLayout) view.findViewById(R.id.reading_social_commentimages)).addView(image);
} else {
ViewUtils.setupShareCount(getActivity(), view, story.sharedUserIds.length + 1);
final ImageView image = ViewUtils.createSharebarImage(getActivity(), imageLoader, user.photoUrl, user.id);
((FlowLayout) view.findViewById(R.id.reading_social_shareimages)).addView(image);
}
} else {
View commentViewForUser = view.findViewWithTag(SetupCommentSectionTask.COMMENT_VIEW_BY + user.id);
TextView commentText = (TextView) view.findViewWithTag(SetupCommentSectionTask.COMMENT_BY + user.id);
commentText.setText(sharedText);
TextView commentDateText = (TextView) view.findViewWithTag(SetupCommentSectionTask.COMMENT_DATE_BY + user.id);
commentDateText.setText(R.string.now);
}
TextView commentDateText = (TextView) view.findViewWithTag(SetupCommentSectionTask.COMMENT_DATE_BY + user.id);
commentDateText.setText(R.string.now);
}
} catch (Exception e) {
// this entire method does not respect context state and can be triggered on stale fragments. it should
// be replaced with a proper Loader, or it will always risk crashing the application
Log.w(this.getClass().getName(), "async error in callback", e);
}
}

View file

@ -39,7 +39,8 @@ public class ImagePrefetchService extends SubService {
gotWork();
while (ImageQueue.size() > 0) {
while ((ImageQueue.size() > 0) && PrefsUtils.isImagePrefetchEnabled(parent)) {
startExpensiveCycle();
Set<String> fetchedImages = new HashSet<String>();
Set<String> batch = new HashSet<String>(AppConstants.IMAGE_PREFETCH_BATCH_SIZE);
batchloop: for (String url : ImageQueue) {

View file

@ -67,6 +67,7 @@ public class NBSyncService extends Service {
public enum ActivationMode { ALL, OLDER, NEWER };
private static final Object WAKELOCK_MUTEX = new Object();
private static final Object PENDING_FEED_MUTEX = new Object();
private volatile static boolean ActionsRunning = false;
private volatile static boolean CleanupRunning = false;
@ -87,9 +88,10 @@ public class NBSyncService extends Service {
private static long lastFeedCount = 0L;
private static long lastFFWriteMillis = 0L;
/** Feed sets that we need to sync and how many stories the UI wants for them. */
private static Map<FeedSet,Integer> PendingFeeds;
static { PendingFeeds = new HashMap<FeedSet,Integer>(); }
/** Feed set that we need to sync immediately for the UI. */
private static FeedSet PendingFeed;
private static Integer PendingFeedTarget = 0;
/** Feed sets that the API has said to have no more pages left. */
private static Set<FeedSet> ExhaustedFeeds;
static { ExhaustedFeeds = new HashSet<FeedSet>(); }
@ -195,10 +197,14 @@ public class NBSyncService extends Service {
syncActions();
// these requests are expressly enqueued by the UI/user, do them next
syncPendingFeeds();
syncPendingFeedStories();
syncMetadata(startId);
unreadsService.start(startId);
imagePrefetchService.start(startId);
finishActions();
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "finishing primary sync");
@ -285,6 +291,7 @@ public class NBSyncService extends Service {
ra.doLocal(dbHelper);
}
FollowupActions.clear();
NbActivity.updateAllActivities(false);
}
/**
@ -404,6 +411,7 @@ public class NBSyncService extends Service {
lastFeedCount = feedValues.size();
unreadsService.start(startId);
UnreadsService.doMetadata();
} finally {
FFSyncRunning = false;
@ -415,60 +423,73 @@ public class NBSyncService extends Service {
/**
* Fetch stories needed because the user is actively viewing a feed or folder.
*/
private void syncPendingFeeds() {
private void syncPendingFeedStories() {
FeedSet fs = PendingFeed;
boolean finished = false;
if (fs == null) {
return;
}
try {
Set<FeedSet> handledFeeds = new HashSet<FeedSet>();
feedloop: for (FeedSet fs : PendingFeeds.keySet()) {
if (ExhaustedFeeds.contains(fs)) {
Log.i(this.getClass().getName(), "No more stories for feed set: " + fs);
finished = true;
return;
}
if (!FeedPagesSeen.containsKey(fs)) {
FeedPagesSeen.put(fs, 0);
FeedStoriesSeen.put(fs, 0);
}
int pageNumber = FeedPagesSeen.get(fs);
int totalStoriesSeen = FeedStoriesSeen.get(fs);
if (ExhaustedFeeds.contains(fs)) {
Log.i(this.getClass().getName(), "No more stories for feed set: " + fs);
handledFeeds.add(fs);
continue feedloop;
StoryOrder order = PrefsUtils.getStoryOrder(this, fs);
ReadFilter filter = PrefsUtils.getReadFilter(this, fs);
while (totalStoriesSeen < PendingFeedTarget) {
if (stopSync()) return;
if (!fs.equals(PendingFeed)) {
// the active view has changed
if (fs == null) finished = true;
return;
}
StorySyncRunning = true;
NbActivity.updateAllActivities(false);
if (!FeedPagesSeen.containsKey(fs)) {
FeedPagesSeen.put(fs, 0);
FeedStoriesSeen.put(fs, 0);
pageNumber++;
StoriesResponse apiResponse = apiManager.getStories(fs, pageNumber, order, filter);
if (! isStoryResponseGood(apiResponse)) return;
// if any reading activities happened during the API call, the result is now stale.
// discard it and start again
if (dbHelper.getActions(false).getCount() > 0) return;
FeedPagesSeen.put(fs, pageNumber);
totalStoriesSeen += apiResponse.stories.length;
FeedStoriesSeen.put(fs, totalStoriesSeen);
insertStories(apiResponse);
NbActivity.updateAllActivities(true);
if (apiResponse.stories.length == 0) {
ExhaustedFeeds.add(fs);
finished = true;
return;
}
int pageNumber = FeedPagesSeen.get(fs);
int totalStoriesSeen = FeedStoriesSeen.get(fs);
StoryOrder order = PrefsUtils.getStoryOrder(this, fs);
ReadFilter filter = PrefsUtils.getReadFilter(this, fs);
pageloop: while (totalStoriesSeen < PendingFeeds.get(fs)) {
if (stopSync()) return;
pageNumber++;
StoriesResponse apiResponse = apiManager.getStories(fs, pageNumber, order, filter);
if (! isStoryResponseGood(apiResponse)) break feedloop;
FeedPagesSeen.put(fs, pageNumber);
totalStoriesSeen += apiResponse.stories.length;
FeedStoriesSeen.put(fs, totalStoriesSeen);
insertStories(apiResponse);
NbActivity.updateAllActivities(true);
if (apiResponse.stories.length == 0) {
ExhaustedFeeds.add(fs);
break pageloop;
}
}
handledFeeds.add(fs);
}
finished = true;
PendingFeeds.keySet().removeAll(handledFeeds);
} finally {
if (StorySyncRunning) {
StorySyncRunning = false;
NbActivity.updateAllActivities(false);
}
synchronized (PENDING_FEED_MUTEX) {
if (finished && fs.equals(PendingFeed)) PendingFeed = null;
}
}
}
@ -496,7 +517,10 @@ public class NBSyncService extends Service {
void decrementRunningChild(int startId) {
synchronized (WAKELOCK_MUTEX) {
if (wl != null) wl.release();
if (wl == null) return;
if (wl.isHeld()) {
wl.release();
}
// our wakelock reference counts. only stop the service if it is in the background and if
// we are the last thread to release the lock.
if (!wl.isHeld()) {
@ -540,7 +564,7 @@ public class NBSyncService extends Service {
* Is there a sync for a given FeedSet running?
*/
public static boolean isFeedSetSyncing(FeedSet fs) {
return (PendingFeeds.containsKey(fs) && StorySyncRunning);
return (fs.equals(PendingFeed) && StorySyncRunning);
}
public static String getSyncStatusMessage() {
@ -589,31 +613,39 @@ public class NBSyncService extends Service {
return false;
}
synchronized (PendingFeeds) {
synchronized (PENDING_FEED_MUTEX) {
Integer alreadyPending = 0;
if (fs.equals(PendingFeed)) alreadyPending = PendingFeedTarget;
Integer alreadySeen = FeedStoriesSeen.get(fs);
Integer alreadyRequested = PendingFeeds.get(fs);
if (alreadySeen == null) alreadySeen = 0;
if (alreadyRequested == null) alreadyRequested = 0;
if ((callerSeen >= 0) && (alreadySeen > callerSeen)) {
if (callerSeen < alreadySeen) {
// the caller is probably filtering and thinks they have fewer than we do, so
// update our count to agree with them, and force-allow another requet
alreadySeen = callerSeen;
FeedStoriesSeen.put(fs, callerSeen);
alreadyRequested = 0;
alreadyPending = 0;
}
if (AppConstants.VERBOSE_LOG) Log.d(NBSyncService.class.getName(), "have:" + alreadySeen + " want:" + desiredStoryCount + " pending:" + alreadyPending);
if (desiredStoryCount <= alreadySeen) {
return false;
}
if (AppConstants.VERBOSE_LOG) Log.d(NBSyncService.class.getName(), "have:" + alreadySeen + " want:" + desiredStoryCount + " requested:" + alreadyRequested);
if (desiredStoryCount <= alreadyRequested) {
if (desiredStoryCount <= alreadyPending) {
return false;
}
}
PendingFeeds.put(fs, desiredStoryCount);
PendingFeed = fs;
PendingFeedTarget = desiredStoryCount;
}
return true;
}
public static void clearPendingStoryRequest() {
synchronized (PENDING_FEED_MUTEX) {
PendingFeed = null;
}
}
public static void resetFeeds() {
ExhaustedFeeds.clear();
FeedPagesSeen.clear();

View file

@ -21,6 +21,7 @@ public abstract class SubService {
protected NBSyncService parent;
private ExecutorService executor;
protected int startId;
private long cycleStartTime = 0L;
private SubService() {
; // no default construction
@ -50,9 +51,10 @@ public abstract class SubService {
private synchronized void exec_() {
try {
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "SubService started");
//if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "SubService started");
exec();
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "SubService completed");
//if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "SubService completed");
cycleStartTime = 0;
} catch (Exception e) {
Log.e(this.getClass().getName(), "Sync error.", e);
} finally {
@ -90,5 +92,36 @@ public abstract class SubService {
protected abstract void setRunning(boolean running);
protected abstract boolean isRunning();
/**
* If called at the beginning of an expensive loop in a SubService, enforces the maximum duty cycle
* defined in AppConstants by sleeping for a short while so the SubService does not dominate system
* resources.
*/
protected void startExpensiveCycle() {
if (cycleStartTime == 0) {
cycleStartTime = System.nanoTime();
return;
}
double lastCycleTime = (System.nanoTime() - cycleStartTime);
if (lastCycleTime < 1) return;
cycleStartTime = System.nanoTime();
double cooloffTime = lastCycleTime * (1.0 - AppConstants.MAX_BG_DUTY_CYCLE);
if (cooloffTime < 1) return;
long cooloffTimeMs = Math.round(cooloffTime / 1000000.0);
if (cooloffTimeMs > AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS) cooloffTimeMs = AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS;
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "Sleeping for : " + cooloffTimeMs + "ms to enforce max duty cycle.");
try {
Thread.sleep(cooloffTimeMs);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

View file

@ -24,6 +24,8 @@ public class UnreadsService extends SubService {
private static volatile boolean Running = false;
private static volatile boolean doMetadata = false;
/** Unread story hashes the API listed that we do not appear to have locally yet. */
private static List<String> StoryHashQueue;
static { StoryHashQueue = new ArrayList<String>(); }
@ -37,12 +39,14 @@ public class UnreadsService extends SubService {
// only use the unread status API if the user is premium
if (parent.isPremium != Boolean.TRUE) return;
gotWork();
syncUnreadList();
if (doMetadata) {
gotWork();
syncUnreadList();
doMetadata = false;
}
if (StoryHashQueue.size() < 1) return;
gotWork();
getNewUnreadStories();
}
@ -94,7 +98,9 @@ public class UnreadsService extends SubService {
private void getNewUnreadStories() {
unreadsyncloop: while (StoryHashQueue.size() > 0) {
if (parent.stopSync()) return;
if(!PrefsUtils.isOfflineEnabled(parent)) return;
gotWork();
startExpensiveCycle();
List<String> hashBatch = new ArrayList(AppConstants.UNREAD_FETCH_BATCH_SIZE);
batchloop: for (String hash : StoryHashQueue) {
@ -155,6 +161,10 @@ public class UnreadsService extends SubService {
}
}
public static void doMetadata() {
doMetadata = true;
}
public static boolean running() {
return Running;
}

View file

@ -48,7 +48,7 @@ public class AppConstants {
public static final int MAX_FEED_LIST_SIZE = 250;
// when reading stories, how many stories worth of buffer to keep loaded ahead of the user
public static final int READING_STORY_PRELOAD = 5;
public static final int READING_STORY_PRELOAD = 10;
// max old stories to keep in the DB per feed before fetching new unreads
public static final int MAX_READ_STORIES_STORED = 500;
@ -70,4 +70,11 @@ public class AppConstants {
// moot to tune.
public final static long SHUTDOWN_SLACK_SECONDS = 60L;
// the maximum duty cycle for expensive background tasks. Tune to <1.0 to force sync loops
// to pause periodically and not peg the network/CPU
public final static double MAX_BG_DUTY_CYCLE = 0.9;
// cap duty cycle backoffs to prevent unnecessarily large backoffs
public final static long DUTY_CYCLE_BACKOFF_CAP_MILLIS = 5L * 1000L;
}

View file

@ -54,7 +54,7 @@ public class PrefsUtils {
Log.w(PrefsUtils.class.getName(), "could not determine app version");
return false;
}
Log.i(PrefsUtils.class.getName(), "launching version: " + version);
if (AppConstants.VERBOSE_LOG) Log.i(PrefsUtils.class.getName(), "launching version: " + version);
String oldVersion = prefs.getString(AppConstants.LAST_APP_VERSION, null);
if ( (oldVersion == null) || (!oldVersion.equals(version)) ) {