-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);
|
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)
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue