Android – after I updated to retrofit 2.0, I released the onnext call to onnext in different threads
When we used retrofit 1.9, my colleagues created the following courses
public class SomeApiCallAction {
private Subscription subscription;
private NoInternetConnectionInterface noInternetConnectionInterface;
public interface NoInternetConnectionInterface {
PublishSubject<Integer> noInternetConnection(Throwable throwable);
}
public void execute(Subscriber subscriber, NoInternetConnectionInterface noInternetConnectionInterface) {
this.noInternetConnectionInterface = noInternetConnectionInterface;
this.subscription = retrofit.someService().someApiCall()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber)
.retryWhen(retryFunction);
}
public void cancel() {
if (this.subscription != null) {
this.subscription.unsubscribe();
}
}
private Func1<Observable<? extends Throwable>, Observable<?>> retryFunction = new Func1<Observable<? extends Throwable>, Observable<?>>() {
@Override
public Observable<?> call(Observable<? extends Throwable> observable) {
return observable.flatMap(new Func1<Throwable, Observable<?>>() {
@Override
public Observable<?> call(final Throwable throwable) {
if (noInternetConnectionInterface!= null && (throwable instanceof IOException || throwable instanceof SocketTimeoutException)) {
return noInternetConnectionInterface.noInternetConnection(throwable);
}else{
return Observable.error(throwable);
}
}
});
}
}
Someapicallaction is just a simple class. It contains internal improved API calls, especially its retry function. The retry function will check whether the throwable is IOException or sockettimeoutexception. If so, it will call the interface so that we can provide users with a retry dialog box and ask them whether they want to retry the operation. Our usage is similar to the following code segment
public class SomeActivity implement NoInternetConnectionInterface {
@OnClick(R.id.button)
public void do(View v) {
new SomeApiCallAction().execute(
new Subscriber(),
this
)
}
@Override
public PublishSubject<Integer> noInternetConnection(final Throwable throwable) {
Log.i("Dev", Thread.currentThread() + " Error!");
final PublishSubject<Integer> subject = PublishSubject.create();
runOnUiThread(new Runnable() {
@Override
public void run() {
NoInternetDialogFragment dialog = NoInternetDialogFragment.newInstance();
dialog.setNoInternetDialogFragmentListener(new NoInternetDialogFragmentListener{
@Override
public void onUserChoice(boolean retry, NoInternetDialogFragment dialog) {
Log.i("Dev", Thread.currentThread() + " Button Click!");
if (retry) {
subject.onNext(1);
} else {
subject.onError(throwable);
}
dialog.dismiss();
}
});
dialog.show(getSupportFragmentManager(), NoInternetDialogFragment.TAG);
}
});
return subject;
}
}
When we use retrofit 1.9.0, this implementation works perfectly. We test by turning on the flight mode, and then press the button to execute the API call
>The first execution failed, and I got an unknownhostexception in the retry function. > therefore, I called the interface (activity) to display the retry dialog box > I still press the retry button in flight mode to repeat execution > as expected, every execution after the user presses the retry button fails, I always get unknownhostexception in the retry function. > If I keep pressing the retry button, the retry dialog box will always be displayed until I turn off flight mode
But after we update our dependencies
'com.squareup.retrofit2:retrofit:2.0.2'
'com.squareup.retrofit2:adapter-rxjava:2.0.2'
We tried again, but this time the behavior changed,
>The first execution failed, and I got the same unknownhostexception as before in the retry function. > therefore, I called the interface (activity) to display the retry dialog box > I still press the retry button in flight mode to repeat execution > but this time, in the retry function, I got networkonmainthreadexception instead of receiving unknowhostexception like it. > therefore, the conditions did not match, the interface was not called, and only one retry dialog box was presented to the user
The following is the log of the above code
Thread[android_0,5,main] Error!
Thread[main,5,main] Button Click!
Do you know what this will lead to? Any suggestions and comments will be greatly appreciated
Note: the following are other dependencies that we have been using and may be related to. However, they have not been updated recently. These versions have been used since the beginning of this project
'com.jakewharton:butterknife:8.0.1'
'io.reactivex:rxandroid:1.1.0'
'io.reactivex:rxjava:1.1.0'
'com.google.dagger:dagger-compiler:2.0'
'com.google.dagger:dagger:2.0'
'javax.annotation:jsr250-api:1.0'
More information
I just reset my code back to when we used retrofit 1.9, and I found that printing logs was different
Thread[Retrofit-Idle,5,main] Error!
Thread[main,5,main] Button Click!
I'm not sure if this is related to the problem, but it's obvious that in 1.9.0, I called the interface in different threads as 2.0.0
Final editing
After reading the answer of @ John wowus and following the link he provided, I found that in retrofit 2, network calls are synchronized by default
There are two ways to solve my problem
1.) as suggested by @ John wowus, specify the thread for retryfunction
this.subscription = retrofit.someService().someApiCall()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber)
.retryWhen(retryFunction, Schedulers.io());
2.) when creating a transformation object, specify the thread when creating rxjavacalladapterfactory
retrofit = new Retrofit.Builder()
.baseUrl(AppConfig.BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(getGson()))
.addCallAdapterFactory(
RxJavaCallAdapterFactory.createWithScheduler(
Schedulers.from(threadExecutor)
)
)
.build();
resolvent:
I think the problem is that when you re subscribe, you subscribe on the main thread because you use the default trampoline scheduler in retrywhen. Retro 1.9 handles the scheduling for you, so using subscribeon is meaningless. The problem discussion is here. In retro 2, I believe this has changed, so you should try something similar
this.subscription = retrofit.someService().someApiCall()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber)
.retryWhen(retryFunction, Schedulers.io());