【问题标题】:What's the best way to check for permissions at runtime using MVP architecture?使用 MVP 架构在运行时检查权限的最佳方法是什么?
【发布时间】:2017-04-21 11:51:35
【问题描述】:

我正在开发一个 Android 应用程序,我必须在运行时请求权限。我想知道使用 Model-View-Presenter 架构实现它的最佳方式。

我最初的想法是让演示者调用负责权限的组件(比如PermissionHandler),并相应地更新视图。

问题在于检查权限的代码与 Activity 类紧密耦合。以下是一些需要 Activity 或 Context 的方法:

  • ContextCompat.checkSelfPermission()
  • ActivityCompat.shouldShowRequestPermissionRationale()
  • ActivityCompat.requestPermissions()
  • onRequestPermissionsResult()(回调)

这意味着我必须将一个活动对象传递给演示者,我不太喜欢这样做,因为我听说让演示者远离 Android 代码对测试很有好处。

因此,我随后考虑在视图级别(在活动中)处理权限,但我想这会损害让视图只负责 UI 更新而不负责业务逻辑的目的。

我不确定解决这个问题的最佳方法是什么,使代码尽可能地解耦和可维护。有什么想法吗?

【问题讨论】:

    标签: android android-permissions mvp


    【解决方案1】:

    我会做的是:

    视图将实现:

    public Activity getViewActivity();
    

    演示者将实现:

    public void requestPermissions();
    public void onPermissionsResult();
    

    requestPermissions 内,演示者会做:getViewActivity().checkSelfPermission; getViewActivity.requestPermissions(); etc.

    视图将在onRequestPermissionsResult 回调中调用presenter.onPermissionsResult();

    所有的逻辑都将在presenter内部实现。

    在我看来,您的 Presenter 是解耦的:它不依赖于任何视图实现(它只依赖于视图接口)。

    “我听说让您的演示者不使用 Android 代码对测试很有好处。”我不明白这部分。如果代码好,可以测试没有问题。

    【讨论】:

    • 这种方法安全吗?它不应该产生内存泄漏吗?
    • 这种方法如何与 MVP 架构配合使用?演示者的重点是让您的业务逻辑代码基于纯 java 或 kotlin,并且独立于任何 android 框架组件,以便可以轻松进行单元测试。这不应该是公认的答案。
    • 这就是这个解决方案的作用。演示者依赖于视图和自身之间的契约,不依赖于任何其他框架。你知道,模拟这个来做测试很简单。
    • @mromer 实际上尝试对此进行编码。您会看到您的演示者有一个 Activity 的导入语句。
    【解决方案2】:

    如果您仍然希望能够模拟权限访问/请求,您仍然可以创建类似 PermissionHandler 的内容,但只能在您的视图类中引用它。比如——

    界面:

    public interface PermissionsHandler {
        boolean checkHasPermission(AppCompatActivity activity, String permission);
        void requestPermission(AppCompatActivity activity, String[] permissions, int requestCode);
    }
    

    生产实施:

    public class PermissionsHandlerAndroid implements PermissionsHandler {
        @Override
        public boolean checkHasPermission(AppCompatActivity activity, String permission) {
            return ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED;
        }
    
        @Override
        public void requestPermission(AppCompatActivity activity, String[] permissions, int requestCode){
            ActivityCompat.requestPermissions(activity, permissions, requestCode);
        }
    }
    

    模拟类(例如,测试并确保您的活动正确处理onRequestPermissionsResult

    public class PermissionsHandlerMocked implements PermissionsHandler {
        @Override
        public boolean checkHasPermission(AppCompatActivity activity, String permission) {
            return false;
        }
    
        @Override
        public void requestPermission(AppCompatActivity activity, String[] permissions, int requestCode){
            int[] grantResults = new int[permissions.length];
            for (int i = 0; i < permissions.length; i++) {
                grantResults[i] = PackageManager.PERMISSION_GRANTED
            }
            activity.onRequestPermissionResult(requestCode, permissions, grantResults);
        }
    }
    

    然后在你的活动中:

    PermissionsHandler permissionsHandler;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        permissionsHandler = Injection.providePermissionsHandler();
        //or however you choose to inject your production vs mocked handler. 
    }
    
    //method from your view interface, to be called by your presenter
    @Override
    void requestLocationPermission() {
        permissionsHandler.requestPermision((AppCompatActivity) this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, REQUEST_CODE_LOCATION};
    }
    

    fobo66,你总是可以让视图实现更通用的方法,比如 checkLocationPermissionGranted()requestLocationPermission()。然后您的视图实现可以根据需要引用活动,并且您的演示者永远不必触摸活动引用。

    【讨论】:

      【解决方案3】:

      权限请求和状态是视图(片段或活动)的责任,因为取决于用户操作来发出请求或授予权限。我使用 MVP 管理权限如下(阅读外部存储示例):

      我的合同

      interface View {
          ...
          void requestReadPermission();
          boolean areReadPermissionGranted();
          void showPermissionError();
          void hidePermissionError();
          ...
      }
      
      interface Presenter {
          ...
          void setReadPermissions(boolean grantedPermissions);
          ...
      }
      
      interface Model {
          ...
      }
      

      我的视图实现。 (在这种情况下是片段,但它可以是 Activity 或其他任何东西,Presenter 只会期待响应)。

      public class MyView extends Fragment implements Contract.View {
      
      ...
      Contract.Presenter presenter;
      
      @Override
      public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
          super.onRequestPermissionsResult(requestCode, permissions, grantResults);
          boolean grantedPermissions =  (grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED);
          presenter.setReadPermissions(grantedPermissions);
      }
      
      
      
      @Override
      public void showPermissionError() {
          // Show not permission message
      }
      
      @Override
      public void hidePermissionError() {
          // Hide not permission message
      }
      
      @Override
      public void requestReadPermission() {
          this.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
      }
      @Override
      public boolean areReadPermissionGranted() {
          return ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
      }
      ...
      

      Presenter 实现

      public class MyPresenter implements Contract.Presenter {
      
      ...
      Contract.View view;
      
      public void doSomethingThatRequiresPermissions() {
      
          ...
              if ( !view.areReadPermissionGranted() ) {
                  view.requestReadPermission();
                  view.showPermissionError();
              } else {
                  view.hidePermissionError();
                  doSomethingWithPermissionsGranted();
              }
          ...
      }
      
      @Override
      public void setReadPermissions(boolean grantedPermissions) {
          if( grantedPermissions ){
              view.hidePermissionError();
              doSomethingThatRequiresPermissions();
      
          } else {
              view.showPermissionError();
          }
      }
      
      public void doSomethingWithPermissionsGranted(){
          ...
      }
      

      然后你可以像这样进行单元测试

      Contract.View mockedView;
      @Test
      public void requestAlbumListWithoutPermissions() {
          when(mockedView.areReadPermissionGranted()).thenReturn(false);
          presenter.doSomethingWithPermissionsGranted();
          verify(mockedView).showPermissionError();
          verify(mockedView).requestReadPermission();
      }
      

      【讨论】:

        猜你喜欢
        • 2018-03-19
        • 2011-11-15
        • 2012-12-09
        • 2011-10-28
        • 2014-03-06
        • 1970-01-01
        • 2011-02-17
        • 2012-03-22
        • 1970-01-01
        相关资源
        最近更新 更多