Android Mosby MVI 绑定到 Presenter 中的服务

Android Mosby MVI bind to service in presenter

我正在使用 Mosby 创建一个小应用程序。

该应用有一个我想绑定的服务。我想这样做的正确位置是在演示者中。但是我真的不知道该怎么做。

我想要存档的是当服务被绑定时我想调用一个方法并将该值推送到视图,以便现在的状态是正确的。

当服务在事件总线上发送更新时,我也想将其推送到视图。

我在后面的部分找到了一些例子,但是没有关于如何bind/unbind演示者中的服务。

我的尝试是在 activity:

中创建类似这样的东西
@NonNull
@Override
public MyPresenter createPresenter() {
    return new MyPresenter(new MyService.ServiceHandler() {
            @Override
            public void start(ServiceConnection connection) {
                Intent intent = new Intent(MyActivity.this, MyService.class);
                startService(intent);
                bindService(intent, connection, Context.BIND_AUTO_CREATE);
            }

            @Override
            public void stop(ServiceConnection connection) {
                unbindService(connection);
            }
        });

然后在演示者中执行如下操作:

private ServiceConnection connection;
private boolean bound;
private MyService service;

public MyPresenter(MyService.ServiceHandler serviceHandler) {
    super(new MyViewState.NotInitialiezedYet());

    this.serviceHandler = serviceHandler;

    connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
           MyService.LocalBinder binder = (MyService.LocalBinder) service;
            service = binder.getService();
            bool isInitialized = service.isInitialized();
            // how do i push isInitialized to view? 

        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };
}

@Override
public void attachView(@NonNull SplashView view) {
    super.attachView(view);
    serviceHandler.start(connection);
    bound = true;
}

@Override
public void detachView(boolean retainInstance) {
    super.detachView(retainInstance);
    if(bound) {
        serviceHandler.stop(connection);
        bound = false;
    }
}

@Override
protected void bindIntents() {
    //Not sure what this would look like?
}

public void onEventInitialized(InitializedEvent event) {
    //how do I push this to the view?
 }   

我走的路对吗?这样做的正确方法是什么?我如何将值从服务发送到 onServiceConnected 中的视图以及何时在 onEventInitialized 中的事件总线上获取事件?

在我们深入研究可能的实施之前需要注意的几点:

  1. 在 Mosby 中,Presenters 默认情况下保留屏幕方向,只有 View 被附加/分离。如果你在你的 Activity 中创建一个 ServiceHandler 你有一个内存泄漏,因为 ServiceHandler 是一个匿名的 class 在你的 Activity 中实例化,因此引用了外部 Activity 实例。为避免这种情况,您可以使用 Application class 作为上下文来调用 bindService()unbindService().
  2. 服务是业务逻辑,因此您最好不要将绑定服务的逻辑放在视图层 (Activity) 中,而是放在它自己的 "business logic" 组件中,即我们称此组件为 MyServiceInteractor.
  3. 当您在业务逻辑中移动该部分时,您可能想知道何时解除绑定/停止服务。在您的代码中,您已经在 Presenter detachView() 中完成了。虽然这有效,但 Presenter 现在对业务逻辑内部结构及其工作方式有了一些明确的了解。一个更类似于 Rx 的解决方案是将服务连接的生命周期与 Rx Observable 的 "lifecycle" 联系起来。这意味着,一旦取消订阅/处置可观察对象,就应该关闭服务连接。这也与 1 完美匹配。"Presenter survive screen orientation changes"(并在屏幕方向更改期间保持可观察订阅处于活动状态)。
  4. 任何回调/侦听器都可以很容易地 "wrapped" 通过使用 Observable.create().
  5. 进入 Rx Observable
  6. 就我个人而言,我发现服务(尤其是有界服务)使用起来很麻烦,并且会在您的代码中引入更高的复杂性。如果没有服务,您可能(或可能不能)实现相同的目标。但这实际上取决于您的具体应用/用例。

话虽如此,让我们看看可能的解决方案是什么样的(伪相似代码,可能无法编译):

public class MyServiceInteractor {

  private Context context;

  public MyServiceInteractor(Context context) {
    this.context = context.getApplicationContext();
  }

  public Observable<InitializedEvent> getObservable() {
    return Observable.create(emitter -> {
      if (!emitter.isDisposed()) {

        MyService.ServiceHandler handler = new MyService.ServiceHandler() {

          @Override public void start(ServiceConnection connection) {
            Intent intent = new Intent(context, MyService.class);
            context.startService(intent);
            context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
          }

          @Override public void stop(ServiceConnection connection) {
            context.unbindService(connection);
          }
        };

        emitter.onNext(handler);
        emitter.onComplete();
      }
    }).flatMap(handler ->
        Observable.create( emitter -> {
          ServiceConnection connection = new ServiceConnection() {
            @Override public void onServiceConnected(ComponentName name, IBinder service) {
              MyService.LocalBinder binder = (MyService.LocalBinder) service;
              MyService service = binder.getService();
              boolean isInitialized = service.isInitialized();
              if (!emitter.isDisposed())
                 emitter.onNext(new InitializedEvent(isInitialized));
            }

            @Override public void onServiceDisconnected(ComponentName name) {
              // you may want to emit an event too
            }
          };

        })
        .doOnDispose({handler.stop()})
    );
  }
}

所以基本上 MyServiceInteractor.getObservable() 创建了一个通往 Rx Observable 世界的桥梁,并在取消订阅 observable get 时停止服务连接。请注意,此代码片段可能无法编译。这只是为了说明可能的解决方案/工作流程的样子。

那么您的 Presenter 可能如下所示:

public class MyPresenter extends MviBasePresenter<MyView, InitializedEvent> {
  private MyServiceInteractor interactor;

  public MyPresenter(MyServiceInteractor interactor){
     this.interactor = interactor;
  }

  @Override
  void bindIntents(){
    Observable<InitializedEvent> o = intent(MyView::startLoadingIntent) // i.e triggered once in Activity.onStart()
        .flatMap( ignored -> interactor.getObservable() );

    subscribeViewState(o, MyView::render);
  }
}

所以这里的主要问题/问题不是 MVI 或 MVP 或 MVVM 特定的,主要是我们如何 "wrap" android 服务回调到 RxJava observable 中。 一旦我们有了这个,剩下的应该很容易。

唯一与 MVI 相关的事情是连接点:视图实际上必须触发启动服务连接的意图。这是通过 myView.startLoadingIntent()

bindIntents() 中完成的

希望对您有所帮助。