Android MVP:在Presenter中安全使用Context

16

在我的应用程序中,我使用ContentProviderLoaderManager.LoaderCallbacks<Cursor>

片段(视图)

public class ArticleCatalogFragment extends BaseFragment
        implements ArticleCatalogPresenter.View,
        LoaderManager.LoaderCallbacks<Cursor> {

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return onCreateArticleCatalogLoader(args);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {        
         data.registerContentObserver(new LoaderContentObserver(new Handler(), loader));
         updateUI(data);        
    }   

    private Loader onCreateArticleCatalogLoader(Bundle args) {
            int categoryId = args.getInt(CATEGORY_ID);
            Loader loader = new ArticleCatalogLoader(this.getActivity(), categoryId);            
            return loader;
    }

}

从MVP的角度来看,我需要:

Presenter(表示层)

public class ArticleCatalogPresenter extends BasePresenter
        implements LoaderManager.LoaderCallbacks<Cursor> {

    View view;

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return onCreateArticleCatalogLoader(args);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {        
         data.registerContentObserver(new LoaderContentObserver(new Handler(), loader));
         view.updateUI(data);        
    }               

    private Loader onCreateArticleCatalogLoader(Bundle args) {    
            int categoryId = args.getInt(CATEGORY_ID);
            Loader loader = new ArticleCatalogLoader(context, categoryId); // need Context
            return loader;
    }


    interface View {
        updateUI(Cursor data)
    }

}

因此,我需要在Presenter中了解上下文。

有一些细微差别:

  1. Presenter知道上下文-这是不好的,Presenter不应该知道关于Android的知识。

  2. 在Presenter中使用上下文可能会导致内存泄漏。

我现在担心如何避免内存泄漏等问题,以及如何最好地在Presenter中传递Context,使用Application Context还是Activity/Fragment?


1
应用程序上下文是正确的选择。如果视图需要活动上下文,则可以在其自己上存储它(通过构造函数传递),只需确保您不会对视图保持强引用(无论如何),如果您的Presenter在活动/片段中幸存下来。 - JohanShogun
另一个想法是让你的活动/片段扮演主持人的角色。在我看来,你已经让你的片段扮演了视图的角色,这有点奇怪,因为片段的基本功能几乎完全覆盖了主持人的功能。你的视图在xml文件和视图子类中。 - JohanShogun
如上所述,将您的片段设为Presenter而不是View。MVP模式之所以适用于android是因为活动/片段的设置方式。您的View是xml等文件。此外,考虑为业务逻辑创建业务规则引擎,而不是在负责将其呈现给View的类中散布它。记录一下,游标适配器不是业务逻辑,它是Presenter逻辑。 - JohanShogun
4
嗨@Alexandr,你找到任何好的解决方案了吗? - Sergey B.
2
你好! 现在我已经改变了架构,而且在Presenter中没有上下文。但是如果有这样的问题,我想使用依赖注入和Dagger。Dagger可以帮助您将上下文注入到任何地方,并且很容易确保这是应用程序上下文。 - Alexandr
显示剩余4条评论
4个回答

3

向Presenter添加上下文并不好,因为Presenter应该负责业务逻辑。要处理上下文,您需要让Fragment/Activity利用接口的回调来说明在处理视图时Activity/Fragment需要执行哪些操作。Fragment/Activity负责提供上下文。

示例:

interface BaseContract {
        interface BaseView {
            //Methods for View
            void onDoSomething();
        }

        interface BasePresenter {
            void doSomething();

        }
    }

    class BaseMainPresenter implements BaseContract.BasePresenter {
        BaseContract.BaseView view;

        BaseMainPresenter(BaseContract.BaseView view) {
            this.view = view;
        }

        @Override
        public void doSomething() {
            if (view != null)
                view.onDoSomething();
        }
    }

    class DemoClass implements BaseContract.BaseView {

        //Create object of Presenter 

        /****
         * Example :
         * BaseMainPresenter baseMainPresenter = new BaseMainPresenter(this);
         */
        @Override
        public void onDoSomething() {
            //Deal with Context here.
        }
    }

1
你能详细说明一下根据OP问题如何使用Loaders回调吗?谢谢。 - trocchietto

0
public class ArticleCatalogPresenter extends BasePresenter
        implements LoaderManager.LoaderCallbacks<Cursor> {

    View view;             
    ...
    private Loader onCreateArticleCatalogLoader(Bundle args) {    
            int categoryId = args.getInt(CATEGORY_ID);
            Loader loader = new ArticleCatalogLoader(context, categoryId); // need Context
            return loader;
    }
}

所以你想在你的Presenter中使用context来构建一个新的ArticleCatalogLoader实例,对吗?

如果是这样,请通过构造函数将实例传递给Presenter。因此,在你的Activity或DI容器中,当你想要构建Presenter对象时,可以执行以下操作:

ArticleCatalogPresenter articleCatalogPresenter=new ArticleCatalogPresenter(articleCatalogView,new ArticleCatalogLoader(context,categoryId));

这样一来,您的Presenter将不会依赖于context,可以完全进行测试。

关于您对内存泄漏的担忧,您可以通过监听View中的onStop()方法,并调用相应的方法在您的Presenter中取消任何网络请求或依赖于context的任务来轻松避免此问题。

我已经编写了一个MVP库,它可以大大减少MVP模式中所需的样板代码,并预防内存泄漏问题。


0

不要将您的Presenter注册为Android特定的回调目标(例如,BroadcastReceiverLoaderManager.LoaderCallbacks等)。在您的View(片段或活动)中处理回调方法,并将所有相关数据传递给Presenter。

如果您需要Context用于对象创建,请让您的View创建此对象(因为它具有对Context的引用)。在您的情况下,调用

Loader loader = new ArticleCatalogLoader(context, categoryId)

应该进行重构

view.createLoaderForCategory(categoryId)

0

像这样的代码

Loader loader = new ArticleCatalogLoader(context, categoryId);

这会导致代码难以测试。您应该避免在代码中创建“业务”对象,并让其他人为您完成(任何DI框架,如Dagger 2,都比自己处理更好)

话虽如此,您的问题是DI早就解决了的问题。您需要一个新的对象实例吗?使用Provider

Provider是一个“提供”对象实例的对象。因此,不要使用:

Loader loader = new ArticleCatalogLoader(context, categoryId);

你将拥有

Loader loader = loaderProvider.get(categoryId);

所以你唯一需要的就是像这样的东西:

public class ArticleCatalogPresenter ... {
    ...
    private final Provider<Loader> loaderProvider;

    public ArticleCatalogPresenter(Provider<Loader> loaderProvider, ...) {
        this.loaderProvider = loaderProvider;
        ...
    }

    private Loader onCreateArticleCatalogLoader(Bundle args) {    
        int categoryId = args.getInt(CATEGORY_ID);
        Loader loader = loaderProvider.get(categoryId); // no context needed anymore!
        return loader;
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接