【问题标题】:Need Context in Model in MVP在 MVP 中的模型中需要上下文
【发布时间】:2016-12-30 05:15:43
【问题描述】:

我需要在模型中使用活动上下文,同时在 android 中使用 MVP 来获取所有已安装应用程序的列表。在遵循 MVP 模式的同时访问上下文的正确方法是什么或实现相同的任何替代方法.

以下是课程:

主Activity.java

public class MainActivity extends BaseActivity
    implements MainView,View.OnClickListener {

    private MainPresenter mPresenter;
    private Button sendButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       init();
       createPresenter();
    }

    private void init(){
       sendButton= (Button) findViewById(R.id.button_send);
       sendButton.setOnClickListener(this);
    }

    private void createPresenter() {
       mPresenter=new MainPresenter();
       mPresenter.addView(this);
    }

    @Override
    public void onClick(View view) {
       switch (view.getId()){
          case R.id.button_send:
             mPresenter.onSendButtonClick();
             break;
        }
    }

   @Override
   public void openOptionsActivity() {
        Intent intent=new Intent(this,OptionsActivity.class);
        startActivity(intent);
   }
}

Main Presenter.java

公共类 MainPresenter 扩展 BasePresenter {
    MainModel model;

    public void onSendButtonClick() {
       model.getListOfAllApps();
    }

    @Override
    public void addView(MainView view) {
        super.addView(view);
        model = new MainModel();
    }

}

主模型.java

public class MainModel {

    public void getListOfAllApps(){
        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        final List pkgAppsList = getPackageManager().queryIntentActivities(mainIntent, 0);
  
    }

}

getPackageManager().queryIntentActivities(mainIntent, 0) 中遇到问题。如何在此处没有任何上下文。

【问题讨论】:

  • 你没有Application class 吗?
  • 我有应用程序类。但是如何使用它。你是说在应用程序类中使用一些常量字段作为上下文吗?问题是按照我们不应该使用任何特定于android的模式模型中的类/对象。
  • 一点也不。只需在 onCreate(...) 中创建 applicationContext 并使用它
  • 我有一个 MainActivity、一个 MainPresenter 和一个 MainModel 类。现在我需要访问 Model 中的上下文。在活动的 oncreate 中 applicationcontext 有什么用?
  • @MD 你完全错过了在这里使用 MVP 来分离关注点的意义。由于 Model 包含业务逻辑,它应该与框架无关,这意味着它不应该直接依赖于 Android 特定对象,例如 Context

标签: android mvp


【解决方案1】:

我回答了一个类似的问题here,您可能也想看看。不过,我将详细说明我认为您可以如何解决这个特定问题。

使用 Application 类中的静态上下文

这种方法可行,但我不喜欢它。它使测试更加困难,并将您的代码耦合在一起。

public class App extends Application {

    private static Context context;

    public static Context getContext() {
        return context;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        context = getApplicationContext();
    }
}

然后在您的 MainModel 中:

public class MainModel {

    public List<String> getListOfAllApps(){

        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        final List<ResolveInfo> pkgAppsList = App.getContext().getPackageManager().queryIntentActivities(mainIntent, 0);

        List<String> results = new ArrayList<>();
        for (ResolveInfo app : pkgAppsList) {
            results.add(app.resolvePackageName);
        }
        return results;
    }
}

现在我们已经解决了这个问题,让我们看看一些更好的选择。

在活动中进行

所以你的 Activity 实现了你的 View。它可能也在做一些 Anroidy 的事情,比如 onActivityResult。有一个论点是将 Android 代码保留在 Activity 中并仅通过 View 界面访问它:

public interface MainView {

    List<String> getListOfAllApps();
}

活动:

public class MainActivity extends BaseActivity implements MainView {

    //..

    @Override
    public List<String> getListOfAllApps(){

        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        final List<ResolveInfo> pkgAppsList = getPackageManager().queryIntentActivities(mainIntent, 0);

        List<String> results = new ArrayList<>();
        for (ResolveInfo app : pkgAppsList) {
            results.add(app.resolvePackageName);
        }
        return results;
    }

    //..
}

还有演示者:

public class MainPresenter extends BasePresenter {

    public void onSendButtonClick(){

        view.getListOfAllApps();
    }
}

在单独的类中抽象细节

虽然最后一个选项并没有违反 MVP 的规则,但它感觉不太对劲,因为获取包列表真的不是 View 操作。我的首选选项是将 Context 的使用隐藏在接口/类后面。

创建一个类PackageModel(或任何你喜欢的名称):

public class PackageModel {

    private Context context;

    public PackageModel(Context context) {
        this.context = context;
    }

    public List<String> getListOfAllApps(){

        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        final List<ResolveInfo> pkgAppsList = context.getPackageManager().queryIntentActivities(mainIntent, 0);

        List<String> results = new ArrayList<>();
        for (ResolveInfo app : pkgAppsList) {
            results.add(app.resolvePackageName);
        }
        return results;
    }
} 

现在让你的 Presenter 需要这个作为构造函数参数:

public class MainPresenter extends BasePresenter {

    private PackageModel packageModel;

    public MainPresenter(PackageModel packageModel) {
        this.packageModel = packageModel;
    }

    public void onSendButtonClick(){

        packageModel.getListOfAllApps();
    }
}

终于在你的活动中:

public class MainActivity extends BaseActivity implements MainView {

    private MainPresenter presenter;

    private void createPresenter() {

        PackageModel packageModel = new PackageModel(this);
        presenter = new MainPresenter(packageModel);
        presenter.addView(this);
    }
}

现在 Context 的使用对 Presenter 是隐藏的,它可以在没有任何 Android 知识的情况下进行。这称为构造函数注入。如果您使用的是依赖注入框架,它可以为您构建所有依赖项。

如果你愿意,你可以为 PackageModel 制作一个接口,但我认为这并不是真正必要的,因为像 Mockito 这样的模拟框架可以在不使用接口的情况下创建存根。

【讨论】:

  • 正如this comment 所说,它只是隐藏依赖关系,这更糟糕。 The answer 在同一个线程上非常好,因为它使依赖关系非常清晰。我发现 this video 非常适合单例和隐藏依赖项。
  • 我只需对第 3 点进行小幅调整,将 PackageModel 设为接口,并将 getListOfAllApps() 设为其单一方法。
  • 你的第一种方式很糟糕。只需删除拳头方式。它绝对错误。演示者和模型应该是纯 Java 的。界面才是真道。
  • @AmirZiarati 确实这不是最好的方法,但我提到它是为了表明人们在转向更好的选择之前可能会考虑什么。
  • @Jahnold 在您的第三个解决方案中,您是否将 PackageModel 视为 MVP 模型?如果是这样,那么在 MainActivity 中创建 PackageModel 实例会在 VIEW 和 MODEL 之间创建链接,对吗?但据我所知,MVP 模式中的 VIEW 和 MODEL 之间不应该有任何联系。我是 MVP 主题的新手,我没有明白这一点。
【解决方案2】:

基本上,您有以下选择:

1) 始终将Context 传递给模型。无论 Android 中发生什么事件,您总是可以使用某种Context。 (并且您的代码仅在响应事件时才被调用。)

2) getApplicationContext() 并将其存储在静态变量中以备将来使用。

有以下陷阱:

ActivityContext,但如果您存储指向 Activity 的链接,则会发生内存泄漏。例如,当屏幕转动时会重新创建活动。 传递给 BroadcastReceivers 和其他类型的 Context 的上下文也是如此。它们都有生命周期,而Model并不是你需要的生命周期。

您的应用程序可能会被 Android 杀死并重新启动。在这种情况下,一些全局(静态)变量可能会设置为 null。也就是说,除非您的应用程序碰巧向它们写了一些东西,否则它们将为空。特别是,指向应用程序上下文的静态变量可能在重启场景之一中变为空。这样的问题很难测试。

【讨论】:

  • 您不应该在演示者和模型中使用上下文。此代码不可测试。
  • @AmirZiarati,我需要在模型中获取本地字符串 context.getString(R.string...)。我如何在不使用上下文作为模型构造函数的参数或根本不使用上下文的情况下实现这一点?
  • @Thracian 只需使用视图层中的上下文从资源中获取它,并将其作为字符串依赖项发送到模型层。
猜你喜欢
  • 2017-09-27
  • 1970-01-01
  • 2011-01-01
  • 1970-01-01
  • 2014-07-07
  • 1970-01-01
  • 1970-01-01
  • 2015-09-16
  • 1970-01-01
相关资源
最近更新 更多