I got a weird issue in one of my activities. When coming back from taking a picture / video, in my onActivityResult
I am showing a dialog that lets the user name the camera. Once the user presses OK, I send onNext()
to a subject with the requested file name that copies the file (and shows progress dialog).
For some reason the map()
function that does the copy is always called on the main thread, even though I call subscribeOn(Schedulers.io())
.
@Override protected void onActivityResult(final int requestCode, int resultCode, Intent intent) { ... final PublishSubject<String> subject = PublishSubject.create();` mSubscription = subject .subscribeOn(Schedulers.io()) .map(new Func1<String, String>() { @Override public String call(String fileName) { Log.I.d(TAG,"map"); return doSomeIOHeavyFuncition(); } }) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<String>() { @Override public void call(final String fullPath) { Log.d(TAG,"onNext"); doSomethingOnUI(fullPath); subject.onCompleted(); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { ... } }, new Action0() { @Override public void call() { ... } }); final AlertDialog dialog = new AlertDialog.Builder .... .create() .show(); dialog.getButton(DialogInterface.BUTTON_POSITIVE) .setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String someString = getStringFromDialog(dialog); dialog.dismiss(); InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(input.getWindowToken(), 0); showProgressDialog(); subject.onNext(someString); } }); }
Changing the subscribeOn(Schedulers.io())
call to observeOn(Schedulers.io())
solved the issue. Still I would like to know why it didn't work...
observeOn() simply changes the thread of all operators further Downstream. People usually have this misconception that observeOn also acts as upstream, but it doesn't. subscribeOn() only influences the thread which is going to be used when Observable is going to get subscribed to and it will stay on it downstream.
Service runs in the main thread of its hosting process; the service does not create its own thread and does not run in a separate process unless you specify otherwise.
When an application component starts and the application does not have any other components running, the Android system starts a new Linux process for the application with a single thread of execution. By default, all components of the same application run in the same process and thread (called the "main" thread).
subscribeOn
and observeOn
is the mostly confused operators there are. The former makes sure that subscription side effects happen on the specified scheduler (thread), but that doesn't mean that values will pop up on that thread as well.
For example, if your Observer opens a network connection when one subscribes to it, you don't want that to run on the main thread, therefore, you need subscribeOn to specify where that subscription and thus the network connection will be created.
When data finally arrives, the emitting thread can be anything, one of the schedulers or a background plain old thread. Since we don't know or don't like that thread, we want to move the observation of the data to another thread. This is what observeOn does: makes sure operators after it will execute their onNext logic on the specified scheduler. Android devs use it already to move the observation of values back to the main thread.
What's rarely explained though is what happens when you want some extra computation off the main thread before the final result lands on the main thread again: use multiple observeOn
operators:
source .observeOn(Schedulers.computation()) .map(v -> heavyCalculation(v)) .observeOn(Schedulers.io()) .doOnNext(v -> { saveToDB(v); }) .observeOn(AndroidSchedulers.mainThread()) ...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With