首页 > 解决方案 > 了解 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. 如果用户在获取用户名时旋转屏幕,我们将泄漏在观察者的回调中引用的活动实例,因为活动被破坏了。
  2. 如果用户在没有进行中获取的情况下旋转屏幕,则活动实例不会泄漏。
  3. 为了修复这个泄漏(1),我们需要在presenter.onDestroy()中存储订阅和取消订阅(从SplashActivity onDestroy()调用)。
  4. 有人告诉我,做(3)还不够,里面onDestroy()还要把activity instance设置为null. 我不同意,因为取消订阅会取消请求,从而阻止调用onNext(User)引用活动的回调(如 )。
  5. 他还告诉我,虽然 (3) 和 (4) 可以防止 ACTIVITY 泄漏,但 PRESENTER 在轮换期间也会泄漏,因为活动引用了它。我不同意,因为每次旋转都会创建一个新的演示者,并在 BaseActivity onCreate 调用 setupActivityComponent 时将其初始化为注入的演示者。旧的 Presenter 作为 SplashActivity 的成员自动被垃圾收集。

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

标签: javaandroidmemory-leaksdagger-2mvp

解决方案


我会尽可能准确地回答这些问题(如果不准确,欢迎编辑),但这个答案很好读:https ://stackoverflow.com/a/10968689/4252352

  1. 如果用户在获取用户名时旋转屏幕,我们将泄漏在观察者的回调中引用的活动实例,因为活动被破坏了。

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

  1. 如果用户在没有进行中获取的情况下旋转屏幕,则活动实例不会泄漏。

A. 正确,您从未有过有效订阅。

  1. 为了修复这个泄漏(1),我们需要在presenter.onDestroy()中存储订阅和取消订阅(从SplashActivity onDestroy()调用)

A 这应该会停止这个问题。然而更好的方法,在 MVP 中View应该是一个抽象/接口,你Presenter应该有视图的入口和出口点,而不是在构造函数上,即这里bind(view : View)unbind()<--cleanup,演示者不应该知道特定的 android hook 回调. 这具有巨大的优势,不仅从 OOP(程序到接口而不是实现)方面,而且在测试方面。

  1. 有人告诉我,做 (3) 是不够的,在 onDestroy() 内部我们还必须将活动实例设置为 null。我不同意,因为取消订阅会取消请求,从而阻止调用引用活动的回调(如 onNext(User))。

答:我会首先要求澄清他们的推理。由于您Presenter的范围为Activity(它们都具有相同的生命周期),因此取消订阅就足够了。但是,如果您Presenter的生命周期比您的生命周期长,Activity则有必要删除引用(这可能是您与之交谈的人的基本原理)。

  1. 他还告诉我,虽然 (3) 和 (4) 可以防止 ACTIVITY 泄漏,但 PRESENTER 在轮换期间也会泄漏,因为活动引用了它。我不同意,因为每次旋转都会创建一个新的演示者,并在 BaseActivity onCreate 调用 setupActivityComponent 时将其初始化为注入的演示者。旧的 Presenter 作为 SplashActivity 的成员自动被垃圾收集。

A.Presenter被泄露,如果Activity被泄露(连同活动引用的所有内容!)


推荐阅读