Android MVP:在Presenter中安全使用Context

时间:2015-08-11 20:40:26

标签: android design-patterns mvp android-cursor

在我的应用中,我使用ContentProvider并使用LoaderManager.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的角度来看,我需要:

演示

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中使用上下文可能会导致内存泄漏。

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

4 个答案:

答案 0 :(得分:3)

向Presenter添加上下文并不好,因为演示者负责业务逻辑。要处理上下文,您需要拥有片段/活动 在接口的帮助下使用Callbacks,它将说明在处理视图时activity / fragment需要执行哪些操作。 片段/活动负责提供上下文。

示例:

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 :(得分:0)

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

如果您需要Context来创建对象,请让您的视图创建此对象(因为它具有对Context的引用)。在你的情况下,电话

Loader loader = new ArticleCatalogLoader(context, categoryId)

应该重构为

view.createLoaderForCategory(categoryId)

答案 2 :(得分:0)

像这样的代码

Loader loader = new ArticleCatalogLoader(context, categoryId);

导致不可测试的代码。你应该避免创建&#34; business&#34;代码中的对象,让任何其他人为你做(任何DI框架,如Dagger 2比自己处理它更好)

话虽如此,你的问题是DI很久以前解决的问题。您是否需要任何对象的全新实例?使用Provider

Provider是一个&#34;提供&#34;的对象。对象的实例。所以没有

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;
    }

}

答案 3 :(得分: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 。因此,当您要构建Presenter对象时,在Activity或DI容器中,执行以下操作:

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

这样,您的Presenter将不依赖于context,并且完全可以测试。

关于您对内存泄漏的担忧,您可以通过在视图中聆听onStop()然后调用 Presenter 中的相应方法来轻松避免这种情况取消任何网络请求或context相关任务。

我写了MVP library,这有助于节省MVP所需的样板量,以防止内存泄漏。