使用MVP架构,在运行时检查权限的最佳方法是什么?

23

我正在开发一个Android应用程序,在运行时需要请求权限。我在思考使用Model-View-Presenter架构的最佳实现方式。

我的初始想法是让Presenter调用一个负责权限的组件(比如PermissionHandler),并相应地更新视图。

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

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

这意味着我必须向Presenter传递一个Activity对象,但我不太喜欢这样做的原因是,保持Presenter不受Android代码干扰有利于测试。

因此,我考虑在视图层面(在Activity中)处理权限,但这可能会损害将视图仅负责UI更新、不涉及业务逻辑的目的。

我不确定哪种方法最好来解决问题,以保持代码解耦和易于维护。有什么建议吗?

3个回答

9
我会做的是:
视图将实现以下内容:
public Activity getViewActivity();

演示者将会实现:

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

requestPermissions内部,Presenter将执行以下操作:getViewActivity().checkSelfPermission; getViewActivity.requestPermissions(); 等等。 视图将在onRequestPermissionsResult回调中调用presenter.onPermissionsResult();
通过这种方式,所有逻辑都将在Presenter中实现。
我认为你的Presenter是解耦的:它不依赖于任何视图实现(仅依赖于视图接口)。
“我听说保持Presenter不受Android代码影响对测试有好处。”我不理解这一部分。如果代码良好,可以毫无问题地进行测试。

这种方法安全吗?不会导致内存泄漏吗? - fobo66
9
这种方法如何与MVP架构配合?Presenter的整个重点在于让你的业务逻辑代码基于纯Java或Kotlin,并独立于任何Android框架组件,以便可以轻松进行单元测试。这不应该是被接受的答案。 - Abhriya Roy
1
这就是这个解决方案的作用。演示者依赖于视图和自身之间的契约,没有与任何其他框架的依赖关系。而且你知道,为了进行测试,模拟这个过程是很简单的。 - mromer
2
@mromer 实际上尝试编写这个代码。你会发现你的 presenter 有一个导入 Activity 的语句。 - tir38

4
如果您仍希望能够模拟权限访问/请求,您可以创建类似于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()。然后你的视图实现可以根据需要引用活动,并且你的Presenter永远不必触及活动引用。


0

权限请求和状态由视图(片段或活动)负责,因为用户操作依赖于请求或授予权限。我使用MVP管理权限,如下所示(读取外部存储示例):

我的合同

interface View {
    ...
    void requestReadPermission();
    boolean areReadPermissionGranted();
    void showPermissionError();
    void hidePermissionError();
    ...
}

interface Presenter {
    ...
    void setReadPermissions(boolean grantedPermissions);
    ...
}

interface Model {
    ...
}

我的视图实现。(在这种情况下是片段,但它可以是活动或任何其他类型,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();
}

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