-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:
parent
cc7f27fb53
commit
1a92dfb019
|
@ -58,6 +58,12 @@ public class DebugApp extends App {
|
|||
Stetho.initialize(initializer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDisposedRxExceptionsReported() {
|
||||
return PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getBoolean(getString(R.string.allow_disposed_exceptions_key), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RefWatcher installLeakCanary() {
|
||||
return LeakCanary.refWatcher(this)
|
||||
|
|
|
@ -30,9 +30,13 @@ import org.schabi.newpipe.util.StateSaver;
|
|||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.SocketException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.exceptions.CompositeException;
|
||||
import io.reactivex.exceptions.MissingBackpressureException;
|
||||
import io.reactivex.exceptions.OnErrorNotImplementedException;
|
||||
import io.reactivex.exceptions.UndeliverableException;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.plugins.RxJavaPlugins;
|
||||
|
@ -99,31 +103,58 @@ public class App extends Application {
|
|||
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
|
||||
@Override
|
||||
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) {
|
||||
// As UndeliverableException is a wrapper, get the cause of it to get the "real" exception
|
||||
throwable = throwable.getCause();
|
||||
}
|
||||
|
||||
final List<Throwable> errors;
|
||||
if (throwable instanceof CompositeException) {
|
||||
for (Throwable element : ((CompositeException) throwable).getExceptions()) {
|
||||
if (checkThrowable(element)) return;
|
||||
errors = ((CompositeException) throwable).getExceptions();
|
||||
} 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
|
||||
Thread.currentThread().getUncaughtExceptionHandler()
|
||||
.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() {
|
||||
return RefWatcher.DISABLED;
|
||||
}
|
||||
|
||||
protected boolean isDisposedRxExceptionsReported() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
|||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.disposables.SerialDisposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
|
||||
|
@ -45,7 +46,7 @@ public class MediaSourceManager {
|
|||
private DynamicConcatenatingMediaSource sources;
|
||||
|
||||
private Subscription playQueueReactor;
|
||||
private CompositeDisposable syncReactor;
|
||||
private SerialDisposable syncReactor;
|
||||
|
||||
private PlayQueueItem syncedItem;
|
||||
|
||||
|
@ -69,7 +70,7 @@ public class MediaSourceManager {
|
|||
this.windowSize = windowSize;
|
||||
this.loadDebounceMillis = loadDebounceMillis;
|
||||
|
||||
this.syncReactor = new CompositeDisposable();
|
||||
this.syncReactor = new SerialDisposable();
|
||||
this.debouncedLoadSignal = PublishSubject.create();
|
||||
this.debouncedLoader = getDebouncedLoader();
|
||||
|
||||
|
@ -251,7 +252,7 @@ public class MediaSourceManager {
|
|||
final Disposable sync = currentItem.getStream()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(onSuccess, onError);
|
||||
syncReactor.add(sync);
|
||||
syncReactor.set(sync);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -87,6 +87,8 @@
|
|||
<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_disposed_exceptions_key" translatable="false">allow_disposed_exceptions_key</string>
|
||||
|
||||
<!-- THEMES -->
|
||||
<string name="theme_key" translatable="false">theme</string>
|
||||
<string name="light_theme_key" translatable="false">light_theme</string>
|
||||
|
|
|
@ -416,4 +416,8 @@
|
|||
<!-- Debug Settings -->
|
||||
<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_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>
|
||||
|
|
|
@ -10,4 +10,9 @@
|
|||
android:title="@string/enable_leak_canary_title"
|
||||
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>
|
||||
|
|
Loading…
Reference in New Issue