【问题标题】:Android MVP StrategyAndroid MVP 策略
【发布时间】:2015-08-16 04:06:15
【问题描述】:

我正在将我的应用程序迁移到 MVP。从konmik

中获得了关于静态演示者模式的提示

这是我简要的 MVP 策略。为简洁起见,删除了大部分样板和 MVP 侦听器。这个策略帮助我改变了方向,证明了我的后台进程。与正在完成活动的暂停相比,活动正确地从正常暂停中恢复。此外,Presenter 只有应用程序上下文,因此它不保留活动上下文。

我不是 Java 专家,这是我第一次尝试 MVP,使用静态演示器让我感到不舒服。我错过了什么吗?我的应用运行良好,并且响应速度更快。

查看

public class MainActivity extends Activity{
    private static Presenter presenter;

    protected void onResume() {
        if (presenter == null)
            presenter = new Presenter(this.getApplicationContext());
        presenter.onSetView(this);
        presenter.onResume();
    }

    protected void onPause() {
        presenter.onSetView(null);
        if(isFinishing())presenter.onPause();
    }    
}

演讲者

public class Presenter {
    private MainActivity view;
    Context context;
    public Model model;

    public Presenter(Context context) {
        this.context = context;
        model = new Model(context);
    }

    public void onSetView(MainActivity view) {
        this.view = view;
    }

    public void onResume(){
        model.resume();
    }
    public void onPause(){
        model.pause();
    }

}

型号

public class Model {

    public Model(Context context){
        this.context = context;
    }
    public void resume(){
        //start data acquisition HandlerThreads
    }
    public void pause(){
        //stop HandlerThreads
    }

}

【问题讨论】:

标签: android mvp


【解决方案1】:

我建议两件事。

  1. ModelViewPresenter 制作成接口。
    • 您的 MVP 视图(ActivityFragmentView)应该非常简单,无需测试。
    • 您的 MVP-Presenter 永远不会直接与 Activity/Fragment/View 交互,因此可以使用 JUnit 对其进行测试。如果您依赖 Android 框架不利于测试,因为您需要模拟 Android 对象、使用模拟器或使用像 Roboelectric 这样的测试框架,这可能会非常慢。

作为接口示例:

interface MVPView {
    void setText(String str);
}

interface MVPPresenter {
    void onButtonClicked();
    void onBind(MVPView view);
    void onUnbind();
}

MVPPresenter 类现在不依赖于 Android 框架:

class MyPresenter implements MVPPresenter{
    MVPView view;

    @Override void bind(MVPView view){ this.view = view; }
    @Override void unbind() {this.view = null; }
    @Override void onButtonClicked(){
        view.setText("Button is Clicked!");
    }
}
  1. 我不会将Presenter 设为静态类,而是将其设为保留片段。静态对象需要仔细跟踪并在不需要时手动删除以进行 GC(否则会被视为内存泄漏)。通过使用保留片段,可以更轻松地控制演示者的生命周期。当拥有保留片段的片段完成时,保留片段也被销毁并且内存可以被 GC'd。 See here for an example

【讨论】:

  • 这为我指明了正确的方向,我已接受它作为答案。删除了所有静态引用。最终使用 Retained Fragment 作为 MVP 工厂。现在,保留的片段根据需要正确创建、保留和提供所有实例。如何在我的 Presenter 或 Model 中避免对 Android 框架对象的依赖,比如说 HandlerThreads 或说 Context?
  • 我通常使用 rxJava 或 EventBus 来处理线程。这些是 Java 库,而不是专门的 Android。它们也更强大,并导致更清晰、更简洁的代码恕我直言。
【解决方案2】:
  1. Activity、Fragment 应该只有 View 接口的重写方法和其他 Android Activity、Fragment 的方法。
  2. View 有诸如 navigateToHome、setError、showProgress 等方法
  3. Presenter 与 View 和 Interactor 交互(具有 onResume、onItemClicked 等方法)
  4. Interactor 拥有所有的逻辑和计算,执行时​​间密集型任务,例如数据库、网络等。
  5. Interactor 是 android 免费的,可以使用 jUnit 进行测试。
  6. Activity/fragment 实现视图,实例化presenter。

根据我的理解提出修改建议。 :)

例子总比文字好,对吧? https://github.com/antoniolg

【讨论】:

    【解决方案3】:

    你走在正确的轨道上,你问static是正确的 - 每当你注意到你写了那个关键字时,是时候停下来反思一下了。

    Presenter 的生命应该直接与 Activity/Fragment 相关联。所以如果 Activity 被 GC 清理了,presenter 也应该如此。这意味着您不应该在演示者中持有对 ApplicationContext 的引用。在 Presenter 中使用 ApplicationContext 是可以的,但是当 Activity 被销毁时,切断这个引用很重要。

    Presenter 还应该将 View 作为构造函数参数:

    public class MainActivity extends Activity implements GameView{
        public void onCreate(){
            presenter = new GamePresenter(this);
        }
    }
    

    演示者看起来像:

    public class GamePresenter {
        private final GameView view;
    
        public GamePresenter(GameView view){
            this.view = view;
        }
    }
    

    然后您可以像这样通知演示者活动生命周期事件:

    public void onCreate(){
        presenter.start();
    }
    
    public void onDestroy(){
        presenter.stop();
    }
    

    onResume/onPause - 尽量保持对称。

    最后你只有 3 个文件:

    (我从here 给出的另一个解释中提取了一些代码,但想法是一样的。)

    游戏演示者:

    public class GamePresenter {
        private final GameView view;
    
        public GamePresenter(GameView view){
            this.view = view;
            NetworkController.addObserver(this);//listen for events coming from the other player for example. 
        }
    
        public void start(){
            applicationContext = GameApplication.getInstance();
        }
    
        public void stop(){
            applicationContext = null;
        }
    
        public void onSwipeRight(){
            // blah blah do some logic etc etc
            view.moveRight(100);
            NetworkController.userMovedRight();
        }
    
        public void onNetworkEvent(UserLeftGameEvent event){
            // blah blah do some logic etc etc
            view.stopGame()
        }
    }
    

    我不确定你为什么想要 ApplicationContext 而不是 Activity 上下文,但如果没有特殊原因,那么你可以将 void start() 方法更改为 void start(Context context) 并只使用 Activity 的上下文。对我来说,这更有意义,也排除了在您的 Application 类中创建单例的需要。

    游戏视图

    是一个接口

    public interface GameView {
        void stopGame();
        void moveRight(int pixels);
    }
    

    GameFragment 是一个扩展 Fragment 并实现 GameView 并且有一个 GamePresenter 作为成员的类。

    public class GameFragment extends Fragment implements GameView {
        private GamePresenter presenter;
    
        @Override
        public void onCreate(Bundle savedInstanceState){
            presenter = new GamePresenter(this);
        }
    }
    

    这种方法的关键是清楚地了解每个文件的作用。

    Fragment 可以控制任何与视图相关的内容(按钮、TextView 等)。它通知用户交互的演示者。

    Presenter 是引擎,它从 View 获取信息(在本例中是 Fragment,但请注意这种模式很适合依赖注入?这绝非巧合。Presenter不知道视图是一个片段 - 它不在乎)并将它与它从“下面”(通讯、数据库等)接收的信息相结合,然后相应地命令视图。

    视图只是一个界面,Presenter 通过它与视图进行通信。请注意,这些方法读作 commandsnot 读作 questions(例如 getViewState()),而 not 读作 inform (例如 onPlayerPositionUpdated()) - 命令 (例如 movePlayerHere(int position))。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-12-18
      • 2012-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多