【问题标题】:Android MVP: safe use Context in PresenterAndroid MVP:在 Presenter 中安全使用上下文
【发布时间】:2015-08-11 20:40:26
【问题描述】:

在我的应用程序中,我使用 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. 演示者知道上下文 - 这很糟糕,演示者不应该 了解安卓。

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

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

【问题讨论】:

  • 应用上下文是要走的路。如果视图需要活动上下文,它可以自己存储它(在构造函数中传递),只要您的演示者在活动/片段中幸存下来,请确保您不持有对视图的强引用(不管)。
  • 另一个想法是你可以让你的活动/片段扮演演示者的角色。在我看来,您似乎已经让您的片段扮演了视图的角色,这有点奇怪,因为片段的基本功能远远超过了演示者的基本功能。您的视图位于 xml 文件和视图子类中。
  • 感谢您的反馈。当Presenter中的部分业务逻辑(最大部分)和Fragment中的另一部分(使用CursorLoader)时,我遇到了这种情况,这会产生问题。我想在 Presenter 中移动所有业务逻辑。
  • 嗨@Alexandr,您找到任何好的解决方案了吗?
  • 嗨!现在我已经更改了架构,并且在演示者中没有上下文。但如果是这样的问题,我想使用依赖注入和匕首。 Dagger 帮助您将上下文注入到任何地方,并且很容易确保这是应用程序上下文。

标签: android design-patterns mvp android-cursor


【解决方案1】:

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

例子:

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

【讨论】:

  • 您能否根据 OP 问题详细说明这如何与 Loaders 回调一起使用?谢谢
【解决方案2】:

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

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

Loader loader = new ArticleCatalogLoader(context, categoryId)

应该重构为

view.createLoaderForCategory(categoryId)

【讨论】:

    【解决方案3】:

    这样的代码

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

    【讨论】:

      【解决方案4】:
      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,并且完全可测试。

      关于您对内存泄漏的担忧,您可以通过在View 中收听onStop() 然后在Presenter 中调用相应的方法来取消任何网络请求或context 依赖任务。

      我写了一个MVP library,这对节省 MVP 所需的样板数量以及防止内存泄漏很有帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-09-18
        • 2017-05-26
        • 2023-03-27
        • 2014-07-15
        • 2017-09-27
        • 1970-01-01
        • 2018-11-18
        • 2010-12-10
        相关资源
        最近更新 更多