0

I am using the updated version of this answer to link (bind) an activity to a service.

MyService.java

public class MyService extends Service {

   private LocalBinder binder = new LocalBinder();
   private Observable<Integer> responseObservable;
   private ObservableEmitter<Integer> responseObserver;

   public static boolean isRunning = false;

   @Nullable
   @Override
   public IBinder onBind(Intent intent) {
      return binder;
   }

   @Override
   public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
       GsonConverterFactory factory = GsonConverterFactory.create(new GsonBuilder()
            .setLenient()
            .create());

      HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
      interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

      Client client = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(new OkHttpClient.Builder()
                 .addInterceptor(interceptor)
                 .build())
            .addConverterFactory(factory)
            .build()
            .create(Client.class);

      for (//some loop) {
          Response<Result> response = client.search(//some params here)
                           .execute();
          responseObserver.onNext(response.code());
      }
      return START_NOT_STICKY;
   }

   @Override
   public void onDestroy() {
      super.onDestroy();
      isRunning = false;
   }

   public Observable<Message> observeResponse() {
      if (responseObservable == null) {
         responseObservable = Observable.create(em -> responseObserver = em);
         responseObservable = responseObservable.share();
      }
      return responseObservable;
   }


   public class LocalBinder extends Binder {

      public DService getService() {
         return MyService.this;
      }
   }

}

MainActivity.java

public class MainActivityextends AppCompatActivity {

   private MyService service;
   private Disposable disposable;

   private ServiceConnection serviceConnection = new ServiceConnection() {
      @Override
      public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
         service = ((MyService.LocalBinder) iBinder).getService();
         disposable = service.observeResponse()
               .observeOn(Schedulers.newThread())
               .subscribe(responseCode -> updateUI()); //this must run on main thread
         startService(new Intent(MainActivity.this, MyService.class));
      }

      @Override
      public void onServiceDisconnected(ComponentName componentName) {
      }
   };

   @Override
   protected void onDestroy() {
      if (disposable != null)
         disposable.dispose();
      unbindService(serviceConnection);
      super.onDestroy();
   }

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       //....

       Button start = findViewById(R.id.start);
       start.setOnClickListener(v -> {
           Intent intent = new Intent(this, MyService.class);
           bindService(intent, serviceConnection, BIND_AUTO_CREATE);
       });

       //....
   }
}

If I use observeOn(AndroidSchedulers.mainThread()), I get NetworkOnMainThreadException, and if I use observeOn(Schedulers.newThread()), I get OnErrorNotImplementedException: Only the original thread that created a view hierarchy can touch its views.

I know what both errors mean, in normal cases I can resolve them easily, but here nothing I normally do work.

I need the network requests in the service to be executed synchronously because it is in a loop, and I treat each request result in order, asynchronous calls are not an option for me.

I tried runOnUiThread(() -> updateUI()), but it produces the same error. I also tried to execute the service on a new thread, but still the same error too.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
hiddeneyes02
  • 2,562
  • 1
  • 31
  • 58

1 Answers1

1

First of Service runs on Main Thread

A 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. If your service is going to perform any CPU-intensive work or blocking operations, such as MP3 playback or networking, you should create a new thread within the service to complete that work. By using a separate thread, you can reduce the risk of Application Not Responding (ANR) errors, and the application's main thread can remain dedicated to user interaction with your activities. REFERENCE

So, making api calls in Service directly will cause NetworkOnMainThreadException in all cases.

  1. When you put observeOn(AndroidSchedulers.mainThread()), you are definitly bound to have NetworkOnMainThreadException; reason specified above

  2. When you put observeOn(Schedulers.newThread()), the api call in service causes NetworkOnMainThreadException; but since you have used Rx it returns an error message to its subscriber; but in your case, you have not added error part.

You have used:

subscribe(responseCode -> updateUI());

to prevent app crash, you have to use

subscribe(responseCode -> updateUI(), error -> error.printStackTrace());

Now to fix the issue:

  1. In service, make sure to call API on new Thread in Service;

OR

  1. You can also try to make API call using reference to another class(like Presenter in MVP), where you make API calls and send response to UI directly using :

       service.observeResponse()
           .subscribeOn(Schedulers.io())
           .observeOn(AndroidSchedulers.mainThread())
           .subscribe(responseCode -> view.updateUI(), error -> view.displayError())
    
Firoz Memon
  • 4,282
  • 3
  • 22
  • 32
  • I remember trying to call api from a new thread, and it was not working. Now it is working, maybe I was doing something wrong. thank you anyway :) – hiddeneyes02 Mar 07 '19 at 14:12