NetworkOnMainThreadException或OnErrorNotImplementedException RxAndroid向绑定服务发送数据/从绑定服务接收数据

时间:2019-03-07 13:05:15

标签: android retrofit rx-android synchronous okhttp3

我正在使用此answer的更新版本将活动链接(绑定)到服务。

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);
       });

       //....
   }
}

如果我使用observeOn(AndroidSchedulers.mainThread()),我得到NetworkOnMainThreadException,如果我使用observeOn(Schedulers.newThread()),我得到OnErrorNotImplementedException: Only the original thread that created a view hierarchy can touch its views.

我知道这两个错误是什么意思,在正常情况下,我可以轻松解决它们,但在这里我通常不起作用。

我需要同步执行服务中的网络请求,因为它处于循环中,并且我按顺序处理每个请求结果,异步调用对我来说不是一个选择。

我尝试了runOnUiThread(() -> updateUI()),但它会产生相同的错误。我还尝试在新线程上执行服务,但仍然是相同的错误。

1 个答案:

答案 0 :(得分:1)

Service中的第一个在主线程上运行

  

服务在其托管过程的主线程中运行;服务   不创建自己的线程,也不在单独的进程中运行   除非您另外指定。如果您的服务要执行任何   CPU密集型工作或阻止操作,例如MP3播放或   网络,您应该在服务中创建一个新线程来   完成这项工作。通过使用单独的线程,您可以减少   应用程序无响应(ANR)错误的风险,以及应用程序的   主线程可以保持专用于用户与您的交互   活动。 REFERENCE

因此,在所有情况下直接在Service中进行api调用都会导致NetworkOnMainThreadException

  1. 放置observeOn(AndroidSchedulers.mainThread())时,您肯定会拥有NetworkOnMainThreadException;上面指定的原因

  2. 当您放置observeOn(Schedulers.newThread())时,服务中的api调用会导致NetworkOnMainThreadException;但由于您使用过Rx,因此会向其订阅者返回一条错误消息;但是对于您而言,您尚未添加错误部分。

您使用过:

subscribe(responseCode -> updateUI());

为防止应用崩溃,您必须使用

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

现在要解决此问题:

  1. 在服务中,请确保在服务中的新线程上调用API;

OR

  1. 您还可以尝试通过引用另一个类(例如MVP中的Presenter)来进行API调用,在该类中您可以通过以下方式进行API调用并将响应直接发送到UI:

       service.observeResponse()
           .subscribeOn(Schedulers.io())
           .observeOn(AndroidSchedulers.mainThread())
           .subscribe(responseCode -> view.updateUI(), error -> view.displayError())