MVP,在销毁时将演示者的视图设置为空?

MVP, set the view of the presenter to null on destroy?

我目前正在尝试在 Android 上实施 MVP 模式。但是,我开始考虑内存泄漏(因为演示者持有对 activity - 视图的引用)。我的问题是,我是否应该将演示者的视图设置为空,比如 activity 的 onDestroy?

这是我的主activity:

public class MainActivity extends AppCompatActivity implements MainView {
private Button loadUser;
private TextView mTextView;
@Inject
IMainPresenter mPresenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    setUpViews();

    ((MyApp) getApplication()).getAppComponent().inject(this);
    mPresenter.setView(this);
}

private void setUpViews() {
    loadUser = (Button) findViewById(R.id.getUserBtn);
    loadUser.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            mPresenter.loadUserName(2);
        }
    });
    mTextView = (TextView) findViewById(R.id.userNameTextView);
}

@Override
public void setUserName(String userName) {
    mTextView.setText(userName);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    mPresenter.setView(null);
}
}

我假设您正在使用注入库(可能是 Dagger 查看您的代码)并且 Presenter 注释为 @Singleton?如果是这样,那么设置为 null 是一种选择(是的,您不应该在配置更改时保留 Activity 实例)。

另一种选择是在您的 Presenter 中使用 WeakReference,这样就不需要设置为 null,尽管设置为 null 更为明确。

您可能会考虑将接口与您的 Presenter 一起使用,而不是将整个 Activity 暴露给 Presenter 实例 - 您可能已经在做类似的事情,但不是 100% 从提供的代码中清除。

我总是那样做,我的意思是,我总是在 onDestroy() 方法上将视图设置为 null。我尝试使用LightCycle library to avoid having to code all that boilerplate code. Here's an article that explains Fernando Ceja's Clean Architecture。这是一个广泛使用的MVP架构(我在几个使用这种模式的公司工作过),你可以看到他还在onDestroy()方法中将视图设置为null。让我知道是否可以进一步帮助您。

更新:

这个答案有点过时了,现在您可以使用 Android 的 Jetpack 工具中的 LifecycleOwner class。这基本上是相同的交易,但 API.

不同

你能不能使用 https://developer.android.com/topic/libraries/architecture/lifecycle.html 让演示者生命周期感知?当在视图上调用 onDestroy() 时,演示者自己可以负责将视图设置为 null。

如果您没有将视图 (Activity) 设置为 null,那么您的演示者将继续持有对 Activity 的引用。 我将使用 Activity 作为示例,但同样适用于 Fragments。

这是个问题吗?为什么?

这取决于演示者的生命周期。您需要了解您的演示者在内存中存在多长时间。

案例一:

如果您的 Presenter 被定义为 Singleton(我认为您不应该这样做),那么只要应用程序本身,它就会保留在内存中。

这是一个问题,因为这样一个单例 class 将持有对 Activity 的引用,从而阻止它被垃圾收集。当你的 Activity 被销毁时,它对用户就没有用了,但仍然会占用内存(RAM)。除了占用宝贵的内存之外,演示者可能会收到成功的 API 响应之类的事件,这将触发 view.doSomething 方法,这可能导致崩溃(使用 destroyed Activity 的 UI 不是你应该做的事情,Android 框架会抛出异常)。

一般来说,Presenter 的范围并不广泛,因此这不是问题。如果您的演示者的寿命超过 Activity,您需要手动将视图引用设置为 null 来解决问题。

案例二:

您的 Presenter 可能未定义为 Singleton,但其他一些 Singleton 或类似的广泛范围的组件可能持有对您的 Presenter 的引用,使其寿命超过 Activity。

您可能有类似 EventbusUserRepositoryUserController 单例 class 的内容。您的 Presenter 可能持有对此类对象的引用并订阅它。当你订阅时,你基本上是在设置你自己对另一个 class 的引用。在 Presenter 中,当您调用 eventbus.subscribe(this)userRepository.setListener(this) 时,您是在为 Singleton 提供对您自己的引用。只要 Singleton 对象存在,持有对您的对象的引用的 Singleton 对象就会使您的对象在内存中,这是永远的。 如果您使用的是 lambda、匿名 classes 或嵌套 classes,此类结构持有对其封闭 class 的引用,可能会导致以下引用链: Singleton->Anonymous class->Presenter->Activity

在这种情况下,Singleton 间接引用一个 Activity,防止它被垃圾收集!

而不是 userRepository.setListener(this),您可能会使用这样的东西:

userRepository.updateUser(object: Listener {
   onSuccess() {
      // do something
   }
   onFailure() {}
})

假设 updateUser 方法执行一个长操作(发送一个 API 请求)。在这种情况下,仅当更新用户请求正在进行时才会保留引用。当调用 onSuccess 或 onFailure 时,引用被通常(实现可能不同)清除。所以在这里,内存泄漏问题不是什么大问题,因为引用将被清除,只是稍有延迟。但是,您仍然有在视图被销毁后更新视图的问题(如案例 1 中所述)- 您可以在请求进行时通过退出 activity 来重现它。

要解决所描述的问题,您需要使用 userRepository.clear()eventbus.unsubscribe(this) 之类的东西。此类方法的实现应取消所有进行中的请求并删除对 subscriber/listener(演示者)的引用。 如果操作正确,您甚至不需要将演示者的视图引用设置为 null。垃圾收集器足够聪明,可以找出您的 Activity 和 Presenter 对象相互持有引用,但没有其他组件持有对它们的引用,并将它们都从内存中删除。 但是,我的建议是无论如何都要这样做,因为这是一个简单的操作,不必在任何地方重复(可以在 BasePresenter class 中完成)并且如果您忘记手动清除某些引用可能会节省您的时间。

场景一

如果 Presenter 的生命周期不长于 Activity 的生命周期,则无需将视图设置为 null。重要的部分是停止演示者执行其工作的后台线程。如果您在 onDestroy 中停止 Presenter 的后台线程,则该线程将终止并且不再将 link 保留到 Activity。所以Activity和Presenter都可以很快被GC回收

这种场景经常出现在使用 MVP 且不处理方向变化的项目中。

场景二

如果 Presenter 的寿命可以超过 Activity(例如,单例),那么您应该将 View 设置为 null。 被描述。

如果您希望演示者的线程在配置更改后继续存在,您很可能会让演示者的生命周期比 activity 更长。


A 建议您改用 MVVM 而不是 MVP。如果您以正确的方式使用 Google 架构组件,生命周期感知组件将为您取消订阅视图并停止线程。