了解 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();
}
}
}
- 如果用户在获取用户名时旋转屏幕,我们就会泄露 activity 实例,因为 activity 已被销毁,所以在观察者的回调中被引用。
- 如果用户在没有正在进行的获取的情况下旋转屏幕,activity 实例不会泄露。
- 为了修复此泄漏(1),我们需要在 presenter.onDestroy()(从 SplashActivity onDestroy() 调用)中存储订阅和取消订阅。
- 有人告诉我做 (3) 是不够的,在
onDestroy()
中我们还必须将 activity 实例设置为 null
。我不同意,因为取消订阅会取消请求,从而阻止调用引用 activity 的回调(如 onNext(User)
)。
- 他还告诉我,虽然 (3) 和 (4) 防止 ACTIVITY 泄漏,但 PRESENTER 在轮换期间也会泄漏,因为 activity 引用了它。我不同意,因为每次旋转都会创建一个新的演示者,并在 BaseActivity onCreate 调用 setupActivityComponent 时初始化为注入的演示者。旧演示者作为 SplashActivity 的成员自动被垃圾收集。
有人可以回应我上面概述的要点,以便我确认我的理解或了解我可能错在哪里吗?谢谢
我会尽力尽可能准确地回答这些问题(如果不准确,欢迎修改),但这个答案很好读:
- 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(通常像 map
、filter
等运算符)不引用(捕获)封闭的 class 是无泄漏的。
- If the user rotates the screen without an in-progress fetch, the activity instance is not leaked.
一个。正确,您从未有过有效订阅。
- 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(程序到接口而不是实现)方面,而且在测试方面。
- 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
更长,则有必要删除引用(这可能是您与之交谈的人的理由)。
- 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 引用的所有内容!)
我在这里阅读了一些类似的问题,但由于缺少代码,我不确定我的问题是否描述了相同的场景。
我希望以下片段和问题将帮助其他人弄清楚在这个 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();
}
}
}
- 如果用户在获取用户名时旋转屏幕,我们就会泄露 activity 实例,因为 activity 已被销毁,所以在观察者的回调中被引用。
- 如果用户在没有正在进行的获取的情况下旋转屏幕,activity 实例不会泄露。
- 为了修复此泄漏(1),我们需要在 presenter.onDestroy()(从 SplashActivity onDestroy() 调用)中存储订阅和取消订阅。
- 有人告诉我做 (3) 是不够的,在
onDestroy()
中我们还必须将 activity 实例设置为null
。我不同意,因为取消订阅会取消请求,从而阻止调用引用 activity 的回调(如onNext(User)
)。 - 他还告诉我,虽然 (3) 和 (4) 防止 ACTIVITY 泄漏,但 PRESENTER 在轮换期间也会泄漏,因为 activity 引用了它。我不同意,因为每次旋转都会创建一个新的演示者,并在 BaseActivity onCreate 调用 setupActivityComponent 时初始化为注入的演示者。旧演示者作为 SplashActivity 的成员自动被垃圾收集。
有人可以回应我上面概述的要点,以便我确认我的理解或了解我可能错在哪里吗?谢谢
我会尽力尽可能准确地回答这些问题(如果不准确,欢迎修改),但这个答案很好读:
- 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(通常像 map
、filter
等运算符)不引用(捕获)封闭的 class 是无泄漏的。
- If the user rotates the screen without an in-progress fetch, the activity instance is not leaked.
一个。正确,您从未有过有效订阅。
- 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(程序到接口而不是实现)方面,而且在测试方面。
- 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
更长,则有必要删除引用(这可能是您与之交谈的人的理由)。
- 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 引用的所有内容!)