了解 android MVP 中的漏洞

understanding leaks in android MVP

我在这里阅读了一些类似的问题,但由于缺少代码,我不确定我的问题是否描述了相同的场景。

我希望以下片段和问题将帮助其他人弄清楚在这个 MVP 实现中泄露了什么以及什么时候泄露的:https://github.com/frogermcs/GithubClient/tree/1bf53a2a36c8a85435e877847b987395e482ab4a

BaseActivity.java:

public abstract class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setupActivityComponent();
    }

    protected abstract void setupActivityComponent();
}

SplashActivityModule.java:

@Module
public class SplashActivityModule {
    private SplashActivity splashActivity;

    public SplashActivityModule(SplashActivity splashActivity) {
        this.splashActivity = splashActivity;
    }

    @Provides
    @ActivityScope
    SplashActivity provideSplashActivity() {
        return splashActivity;
    }

    @Provides
    @ActivityScope
    SplashActivityPresenter
    provideSplashActivityPresenter(Validator validator, UserManager 
    userManager, HeavyLibraryWrapper heavyLibraryWrapper) {
        return new SplashActivityPresenter(splashActivity, validator, 
                                           userManager, heavyLibraryWrapper);
    }
}

SplashActivityPresenter 注入里面 SplashActivity.java:

public class SplashActivity extends BaseActivity {
    ...

    @Inject
    SplashActivityPresenter presenter;

    @Override
    protected void setupActivityComponent() {
        GithubClientApplication.get(this)
                .getAppComponent()
                .plus(new SplashActivityModule(this))
                .inject(this);
    }

SplashActivityPresenter.java:

public class SplashActivityPresenter {
    public String username;

    private SplashActivity splashActivity;
    private Validator validator;
    private UserManager userManager;
    private HeavyLibraryWrapper heavyLibraryWrapper;

    public SplashActivityPresenter(SplashActivity splashActivity, 
        Validator validator, UserManager userManager, 
        HeavyLibraryWrapper heavyLibraryWrapper) {
        this.splashActivity = splashActivity;
        this.validator = validator;
        this.userManager = userManager;
            this.heavyLibraryWrapper = heavyLibraryWrapper;

        //This calls should be delivered to ExternalLibrary right after it will be initialized
        this.heavyLibraryWrapper.callMethod();
        this.heavyLibraryWrapper.callMethod();
        this.heavyLibraryWrapper.callMethod();
        this.heavyLibraryWrapper.callMethod();
    }

    public void onShowRepositoriesClick() {
        if (validator.validUsername(username)) {
            splashActivity.showLoading(true);
            userManager.getUser(username).subscribe(new 
SimpleObserver<User>() {
                @Override
                public void onNext(User user) {
                    splashActivity.showLoading(false);
                    splashActivity.showRepositoriesListForUser(user);
                }

                @Override
                public void onError(Throwable e) {
                    splashActivity.showLoading(false);
                    splashActivity.showValidationError();
                }
            });
        } else {
            splashActivity.showValidationError();
        }
    }
}
  1. 如果用户在获取用户名时旋转屏幕,我们就会泄露 activity 实例,因为 activity 已被销毁,所以在观察者的回调中被引用。
  2. 如果用户在没有正在进行的获取的情况下旋转屏幕,activity 实例不会泄露。
  3. 为了修复此泄漏(1),我们需要在 presenter.onDestroy()(从 SplashActivity onDestroy() 调用)中存储订阅和取消订阅。
  4. 有人告诉我做 (3) 是不够的,在 onDestroy() 中我们还必须将 activity 实例设置为 null。我不同意,因为取消订阅会取消请求,从而阻止调用引用 activity 的回调(如 onNext(User))。
  5. 他还告诉我,虽然 (3) 和 (4) 防止 ACTIVITY 泄漏,但 PRESENTER 在轮换期间也会泄漏,因为 activity 引用了它。我不同意,因为每次旋转都会创建一个新的演示者,并在 BaseActivity onCreate 调用 setupActivityComponent 时初始化为注入的演示者。旧演示者作为 SplashActivity 的成员自动被垃圾收集。

有人可以回应我上面概述的要点,以便我确认我的理解或了解我可能错在哪里吗?谢谢

我会尽力尽可能准确地回答这些问题(如果不准确,欢迎修改),但这个答案很好读:

  1. If the user rotates the screen while the username is being fetched we are leaking the activity instance being referenced inside the observer's callbacks since the activity is destroyed.

A : 这是正确的,但是在处理后清除资源之前会出现短期内存泄漏。然而,这不应该被依赖,如果你有一个 Flowable/Observable 它可能永远不会处理和清除资源。重要的是要注意 Rx 链中的所有 lambda(通常像 mapfilter 等运算符)不引用(捕获)封闭的 class 是无泄漏的。

  1. If the user rotates the screen without an in-progress fetch, the activity instance is not leaked.

一个。正确,您从未有过有效订阅。

  1. In order to fix this leak(1), we need to store the subscription and unsubscribe it in presenter.onDestroy() (called from SplashActivity onDestroy())

A 这应该可以解决这个问题。然而,更好的方法是,在 MVP 中 View 应该是 abstraction/interface 并且你的 Presenter 应该有视图的入口和出口点,而不是在构造函数上,即 bind(view : View)unbind() <-- 在这里清理,演示者不应该知道特定的 android 挂钩回调。这具有巨大的优势,不仅从 OOP(程序到接口而不是实现)方面,而且在测试方面。

  1. Someone told me that doing (3) is not enough, and that inside onDestroy() we must also set the activity instance to null. I disagree because unsubscribing the subscription cancels the request, preventing the callbacks(like onNext(User)) that reference the activity from being invoked.

一个。我会首先要求澄清他们的推理。由于您的 Presenter 仅限于 Activity(它们具有相同的生命周期),取消订阅就足够了。但是,如果您的 Presenter 的生命周期比 Activity 更长,则有必要删除引用(这可能是您与之交谈的人的理由)。

  1. He also told me that while (3) and (4) prevent the ACTIVITY leaking, the PRESENTER is leaked during rotation ALSO since the activity references it. I disagree because a new presenter is created per rotation and initialized to the injected presenter when BaseActivity onCreate invokes setupActivityComponent. The old presenter is automatically garbage collected as a member of SplashActivity.

一个。 Presenter 被泄露,如果 Activity 被泄露(连同 activity 引用的所有内容!)