-Changed global Rx exception handling to no longer trigger error activity if the exception is undeliverable.

-Added debug settings to force reporting of undeliverable Rx exceptions.
-Changed back MediaSourceManager to use serial disposable for syncing.
This commit is contained in:
John Zhen Mo 2018-02-20 22:35:25 -08:00
parent cc7f27fb53
commit 1a92dfb019
6 changed files with 66 additions and 13 deletions

View File

@ -58,6 +58,12 @@ public class DebugApp extends App {
Stetho.initialize(initializer); Stetho.initialize(initializer);
} }
@Override
protected boolean isDisposedRxExceptionsReported() {
return PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(getString(R.string.allow_disposed_exceptions_key), true);
}
@Override @Override
protected RefWatcher installLeakCanary() { protected RefWatcher installLeakCanary() {
return LeakCanary.refWatcher(this) return LeakCanary.refWatcher(this)

View File

@ -30,9 +30,13 @@ import org.schabi.newpipe.util.StateSaver;
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.net.SocketException; import java.net.SocketException;
import java.util.Collections;
import java.util.List;
import io.reactivex.annotations.NonNull; import io.reactivex.annotations.NonNull;
import io.reactivex.exceptions.CompositeException; import io.reactivex.exceptions.CompositeException;
import io.reactivex.exceptions.MissingBackpressureException;
import io.reactivex.exceptions.OnErrorNotImplementedException;
import io.reactivex.exceptions.UndeliverableException; import io.reactivex.exceptions.UndeliverableException;
import io.reactivex.functions.Consumer; import io.reactivex.functions.Consumer;
import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.plugins.RxJavaPlugins;
@ -99,31 +103,58 @@ public class App extends Application {
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() { RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
@Override @Override
public void accept(@NonNull Throwable throwable) throws Exception { public void accept(@NonNull Throwable throwable) throws Exception {
Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : throwable = [" + throwable.getClass().getName() + "]"); Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : " +
"throwable = [" + throwable.getClass().getName() + "]");
if (throwable instanceof UndeliverableException) { if (throwable instanceof UndeliverableException) {
// As UndeliverableException is a wrapper, get the cause of it to get the "real" exception // As UndeliverableException is a wrapper, get the cause of it to get the "real" exception
throwable = throwable.getCause(); throwable = throwable.getCause();
} }
final List<Throwable> errors;
if (throwable instanceof CompositeException) { if (throwable instanceof CompositeException) {
for (Throwable element : ((CompositeException) throwable).getExceptions()) { errors = ((CompositeException) throwable).getExceptions();
if (checkThrowable(element)) return; } else {
errors = Collections.singletonList(throwable);
}
for (final Throwable error : errors) {
if (isThrowableIgnored(error)) return;
if (isThrowableCritical(error)) {
reportException(error);
return;
} }
} }
if (checkThrowable(throwable)) return; // Out-of-lifecycle exceptions should only be reported if a debug user wishes so,
// When exception is not reported, log it
if (isDisposedRxExceptionsReported()) {
reportException(throwable);
} else {
Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", throwable);
}
}
private boolean isThrowableIgnored(@NonNull final Throwable throwable) {
// Don't crash the application over a simple network problem
return ExtractorHelper.hasAssignableCauseThrowable(throwable,
IOException.class, SocketException.class, // network api cancellation
InterruptedException.class, InterruptedIOException.class); // blocking code disposed
}
private boolean isThrowableCritical(@NonNull final Throwable throwable) {
// Though these exceptions cannot be ignored
return ExtractorHelper.hasAssignableCauseThrowable(throwable,
NullPointerException.class, IllegalArgumentException.class, // bug in app
OnErrorNotImplementedException.class, MissingBackpressureException.class,
IllegalStateException.class); // bug in operator
}
private void reportException(@NonNull final Throwable throwable) {
// Throw uncaught exception that will trigger the report system // Throw uncaught exception that will trigger the report system
Thread.currentThread().getUncaughtExceptionHandler() Thread.currentThread().getUncaughtExceptionHandler()
.uncaughtException(Thread.currentThread(), throwable); .uncaughtException(Thread.currentThread(), throwable);
} }
private boolean checkThrowable(@NonNull Throwable throwable) {
// Don't crash the application over a simple network problem
return ExtractorHelper.hasAssignableCauseThrowable(throwable,
IOException.class, SocketException.class, InterruptedException.class, InterruptedIOException.class);
}
}); });
} }
@ -177,4 +208,8 @@ public class App extends Application {
protected RefWatcher installLeakCanary() { protected RefWatcher installLeakCanary() {
return RefWatcher.DISABLED; return RefWatcher.DISABLED;
} }
protected boolean isDisposedRxExceptionsReported() {
return true;
}
} }

View File

@ -22,6 +22,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull; import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.SerialDisposable;
import io.reactivex.functions.Consumer; import io.reactivex.functions.Consumer;
import io.reactivex.subjects.PublishSubject; import io.reactivex.subjects.PublishSubject;
@ -45,7 +46,7 @@ public class MediaSourceManager {
private DynamicConcatenatingMediaSource sources; private DynamicConcatenatingMediaSource sources;
private Subscription playQueueReactor; private Subscription playQueueReactor;
private CompositeDisposable syncReactor; private SerialDisposable syncReactor;
private PlayQueueItem syncedItem; private PlayQueueItem syncedItem;
@ -69,7 +70,7 @@ public class MediaSourceManager {
this.windowSize = windowSize; this.windowSize = windowSize;
this.loadDebounceMillis = loadDebounceMillis; this.loadDebounceMillis = loadDebounceMillis;
this.syncReactor = new CompositeDisposable(); this.syncReactor = new SerialDisposable();
this.debouncedLoadSignal = PublishSubject.create(); this.debouncedLoadSignal = PublishSubject.create();
this.debouncedLoader = getDebouncedLoader(); this.debouncedLoader = getDebouncedLoader();
@ -251,7 +252,7 @@ public class MediaSourceManager {
final Disposable sync = currentItem.getStream() final Disposable sync = currentItem.getStream()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(onSuccess, onError); .subscribe(onSuccess, onError);
syncReactor.add(sync); syncReactor.set(sync);
} }
} }

View File

@ -87,6 +87,8 @@
<string name="debug_pref_screen_key" translatable="false">debug_pref_screen_key</string> <string name="debug_pref_screen_key" translatable="false">debug_pref_screen_key</string>
<string name="allow_heap_dumping_key" translatable="false">allow_heap_dumping_key</string> <string name="allow_heap_dumping_key" translatable="false">allow_heap_dumping_key</string>
<string name="allow_disposed_exceptions_key" translatable="false">allow_disposed_exceptions_key</string>
<!-- THEMES --> <!-- THEMES -->
<string name="theme_key" translatable="false">theme</string> <string name="theme_key" translatable="false">theme</string>
<string name="light_theme_key" translatable="false">light_theme</string> <string name="light_theme_key" translatable="false">light_theme</string>

View File

@ -416,4 +416,8 @@
<!-- Debug Settings --> <!-- Debug Settings -->
<string name="enable_leak_canary_title">Enable LeakCanary</string> <string name="enable_leak_canary_title">Enable LeakCanary</string>
<string name="enable_leak_canary_summary">Memory leak monitoring may cause app to become unresponsive when heap dumping</string> <string name="enable_leak_canary_summary">Memory leak monitoring may cause app to become unresponsive when heap dumping</string>
<string name="enable_disposed_exceptions_title">Report Out-of-Lifecycle Errors</string>
<string name="enable_disposed_exceptions_summary">Force reporting of undeliverable Rx exceptions occurring outside of fragment or activity lifecycle after dispose</string>
</resources> </resources>

View File

@ -10,4 +10,9 @@
android:title="@string/enable_leak_canary_title" android:title="@string/enable_leak_canary_title"
android:summary="@string/enable_leak_canary_summary"/> android:summary="@string/enable_leak_canary_summary"/>
<SwitchPreference
android:defaultValue="true"
android:key="@string/allow_disposed_exceptions_key"
android:title="@string/enable_disposed_exceptions_title"
android:summary="@string/enable_disposed_exceptions_summary"/>
</PreferenceScreen> </PreferenceScreen>