Bunch together notifications to prevent flickering in UI.

This reverts to the previous behaviour before 8faf151.
Then, the InstalledAppProviderService would queue up a series of
changes, and only notify after 1 second of nothing being added to
the queue. This was good because CursorAdapters and LoaderManagers
would not continually requery the database several times a second (only
once at the end), but it meant there was a lag in the AppDetails screen
getting updated after installing/upgrading/deleting packages.

This restores that behaviour where general events (e.g. "some misc apps
in the database were changed") are "debounced" for 1 second. However it
also emits a more specific "package org.blah.com was changed" instantly.

In the long term, it would be good to remove any dependency on
ContentObservers and `notifyChange()` altogether, in preference of
either LocalBroadcastManager or RxJava. However this will depend on how
we go about changing the database layer in the future. The fact we now
depend on ContentProviders means that it would be a big change to move
away from LoaderManager + notifyChange().

Fixes #986.
This commit is contained in:
Peter Serwylo 2017-05-02 11:00:52 +10:00
parent e02c0cb555
commit a9d817441f

View File

@ -22,6 +22,7 @@ import java.io.FilenameFilter;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import rx.functions.Action1;
import rx.schedulers.Schedulers;
@ -54,7 +55,7 @@ public class InstalledAppProviderService extends IntentService {
* or fast, and this can trigger a lot of UI updates, the actual
* notifications are rate limited to one per second.
*/
private PublishSubject<Void> notifyEvents;
private PublishSubject<String> packageChangeNotifier;
public InstalledAppProviderService() {
super("InstalledAppProviderService");
@ -63,17 +64,38 @@ public class InstalledAppProviderService extends IntentService {
@Override
public void onCreate() {
super.onCreate();
notifyEvents = PublishSubject.create();
notifyEvents
packageChangeNotifier = PublishSubject.create();
// This "debounced" event will queue up any number of invocations within one second, and
// only emit an event to the subscriber after it has not received any new events for one second.
// This ensures that we don't constantly ask our lists of apps to update as we iterate over
// the list of installed apps and insert them to the database...
packageChangeNotifier
.subscribeOn(Schedulers.newThread())
.subscribe(new Action1<Void>() {
.debounce(1, TimeUnit.SECONDS)
.subscribe(new Action1<String>() {
@Override
public void call(Void voidArg) {
public void call(String packageName) {
Utils.debugLog(TAG, "Notifying content providers (so they can update the relevant views).");
getContentResolver().notifyChange(AppProvider.getContentUri(), null);
getContentResolver().notifyChange(ApkProvider.getContentUri(), null);
}
});
// ...alternatively, this non-debounced version will instantly emit an event about the
// particular package being updated. This is required so that our AppDetails view can update
// itself immediately in response to an app being installed/upgraded/removed.
// It does this _without_ triggering the main lists to update themselves, because they listen
// only for changes to specific URIs in the AppProvider. These are triggered when a more
// general notification (e.g. to AppProvider.getContentUri()) is fired, but not when a
// sibling such as AppProvider.getHighestPriorityMetadataUri() is fired.
packageChangeNotifier.subscribeOn(Schedulers.newThread())
.subscribe(new Action1<String>() {
@Override
public void call(String packageName) {
getContentResolver().notifyChange(AppProvider.getHighestPriorityMetadataUri(packageName), null);
}
});
}
/**
@ -209,7 +231,7 @@ public class InstalledAppProviderService extends IntentService {
Log.i(TAG, "Marking " + packageName + " as no longer installed");
deleteAppFromDb(this, packageName);
}
notifyEvents.onNext(null);
packageChangeNotifier.onNext(packageName);
}
/**