Android MVP架构策略

6
我正在将我的应用程序迁移到MVP。从这个konmik中获取了静态Presenter模式的提示。
这是我简要的MVP策略。为了简洁起见,删除了大部分样板代码和MVP监听器。这种策略帮助我使后台进程适应屏幕方向的变化。与结束活动的暂停相比,活动可以正确地从正常暂停中恢复。此外,Presenter仅具有应用程序上下文,因此不会保留活动上下文。
我不是Java专家,这是我第一次尝试MVP并使用静态Presenter让我感到不舒服。我错过了什么吗?我的应用程序运行良好,并且响应速度更快。
视图
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
    }

}

我已经思考了一段时间,并开始撰写有关此主题的博客,如果您仍然感兴趣,请访问:http://cj65535.blogspot.com.au/2017/03/a-simple-mvp-framework-for-android.html - C B J
3个回答

6
我建议两件事情。
1. 将 Model、View 和 Presenter 转换成接口。
- MVP-View(Activity、Fragment 或 View)应该非常简单,不需要进行测试。 - MVP-Presenter 不直接与 Activity/Fragment/View 交互,因此可以使用 JUnit 进行测试。如果依赖于 Android 框架,则不利于测试,因为您需要模拟 Android 对象、使用模拟器或使用像 Roboelectric 这样的测试框架,这可能会非常缓慢。
2. 接口示例:
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类改为保留的Fragment而不是静态类。静态对象需要仔细跟踪并在不需要时手动删除,否则会被视为内存泄漏。使用保留Fragment可以更轻松地控制Presenter的生命周期。当拥有保留Fragment的片段完成时,保留Fragment也会被销毁,内存可以进行垃圾回收。可以参考此处获取示例。

这指引了我正确的方向,我已经接受它作为答案。移除了所有静态引用。最终使用了保留的Fragment作为MVP工厂。现在保留的Fragment正确地创建、保留和提供所有所需的实例。如何避免在我的Presenter或Model中依赖于Android框架对象,比如HandlerThreads或Context? - Lakshman Chilukuri
我通常使用 rxJava 或 EventBus 来处理线程。它们是 Java 库,不仅限于 Android。在我看来,它们也更强大,可以产生更清晰、更简洁的代码。 - bcorso

4
  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 实现 View,实例化 Presenter。

建议修改我的理解。 :)

例子胜于言表,对吧? https://github.com/antoniolg


2
你走在正确的道路上,询问关于static的内容是正确的 - 每当你注意到自己写了这个关键字,就该停下来反思一下。
Presenter的生命周期应该直接与Activity/Fragment相关联。因此,如果Activity被GC清理掉了,Presenter也应该被清理掉。这意味着在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;
    }
}

那么你可以这样通知Activity生命周期事件的Presenter:

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

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

onResume/onPause中,尽量保持对称性。

最终你只需要三个文件:

(我从另一个解释这里复制了一些代码,但思路是相同的.)

GamePresenter:

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 context,但如果没有特殊原因,那么你可以将void start()方法更改为void start(Context context)并只使用Activity的context。对我来说,这样做更有意义,也消除了在Application类中创建单例的必要性。

GameView

是一个接口。

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

这种方法的关键在于清楚地了解每个文件的角色。 片段控制与视图相关的任何内容(按钮,TextView等)。它向Presenter通报用户交互。 Presenter是引擎,它获取来自视图的信息(在这种情况下,它是Fragment,但请注意,此模式非常适合依赖注入?这不是巧合。Presenter不知道视图是片段 - 它不关心),并将其与从“下方”(通讯、数据库等)接收到的信息相结合,然后按照需要命令视图。 View只是Presenter与View通信的一个接口。请注意,这些方法看作命令,而不是问题(例如getViewState()),也不是通报(例如onPlayerPositionUpdated())- 命令(例如movePlayerHere(int position))。

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