如何避免编写请求权限的重复样板代码?

10

我正在更新我的应用以兼容Android 6。理论上权限模型并不是很复杂,但实际上在我正在实现的过程中,我发现我需要为每个需要权限的activity编写相同且不美观的样板代码。

对于我需要的每个权限,都有一个

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.PERMISSION) !=
                PackageManager.PERMISSION_GRANTED) {
} else {
}

然后在 onRequestPermissionsResult 中,我必须检查/过滤每个请求的结果,并将其转换为我的活动可以理解的内容。

我现在正在更新我的第二个活动,权限代码与第一个非常相似,几乎看起来像是复制粘贴。行很长,代码相似,看起来很丑陋。

我不想使用第三方解决方案,虽然我尝试过一些,但我更喜欢对代码有完全控制权。例如,一些库不支持我在项目中正在使用的Java 8。

我该怎么做才能避免在所有活动中出现大量重复代码?

2个回答

10

出于问题中所述的原因,我不想使用任何可用的库,因此我自己开发了一些东西。

我所有需要一个或多个权限的活动都继承自一个处理所有权限相关任务的PermissionActivity

它的工作原理是您的活动调用

if (checkHasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) { }

从父类继承。如果权限已经被授予,代码可以继续执行。如果没有被授予,父类将使用一个抽象方法和/或一个或多个可重写的方法请求权限并将结果发送给子类。


父类

除了messageForRationale()requestCodeForPermission()中的switch块以外,可以保留此类的原样。请根据您的应用程序需要更新这些内容的权限。

/**
 * An activity that can be extended to simplify handling permissions.
 * <p>
 * Deriving classes will not have to write boilerplate code and code duplication between activities
 * that share this functionality is avoided.
 */
public abstract class PermissionActivity extends AppCompatActivity {

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        // If multiple permissions were requested in one call, check if they were all granted.
        if (requestCode == RequestCode.PERMISSION_MULTIPLE) {
            boolean allPermissionsGranted = true;
            for (int grantResult : grantResults) {
                if (grantResult != PackageManager.PERMISSION_GRANTED) {
                    allPermissionsGranted = false;
                }
            }

            if (allPermissionsGranted) {
                onAllPermissionsGranted(permissions);
                return;
            }
        }

        // Else, check each one if it was granted/denied/blocked.
        for (int i = 0; i < permissions.length; i++) {
            if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                // User granted permission.
                onPermissionGranted(permissions[i]);
            } else {
                if (ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i])) {
                    // User denied permission.
                    onPermissionDenied(permissions[i]);
                } else {
                    // User denied permission and checked 'never ask again'.
                    onPermissionBlocked(permissions[i]);
                }
            }
        }
    }

    /**
     * Checks if the app has the given permission(s).
     * <p>
     * If not, it will request them.
     * <p>
     * The method is called `checkHasPermission` to avoid the linter showing a warning in the
     * child class when it's delegating permission checks to its parent class. See
     * https://dev59.com/MFoV5IYBdhLWcg3wmP3p
     * -method/36193309#36193309 for details.
     */
    public boolean checkHasPermission(int requestCode, String... permissions) {
        if (!(permissions.length > 0)) {
            throw new IllegalArgumentException("must request at least one permission");
        }

        if (requestCode == RequestCode.PERMISSION_MULTIPLE) {
            List<String> permissions_ = new ArrayList<>();

            for (String permission : permissions) {
                if (ActivityCompat.checkSelfPermission(this, permission) !=
                        PackageManager.PERMISSION_GRANTED) {
                    permissions_.add(permission);
                }
            }

            if (!permissions_.isEmpty()) {
                requestPermissions(this, permissions_.toArray(new String[permissions_.size()]), requestCode);
                return false;
            } else {
                return true;
            }
        } else {
            if (ActivityCompat.checkSelfPermission(this, permissions[0]) !=
                    PackageManager.PERMISSION_GRANTED) {
                requestPermissions(this, permissions, requestCode);
                return false;
            } else {
                return true;
            }
        }
    }

    /**
     * Requests the given permissions.
     */
    private void requestPermissions(Activity activity, String permissions[], int resultCode) {
        showRequestPermissionsDialog(activity, permissions, resultCode);
    }

    /**
     * Called when a rationale (explanation why a permission is needed) should be shown to the user.
     * <p>
     * If the user clicks the positive button, the permission is requested again, otherwise the
     * dialog is dismissed.
     */
    public void showRationaleDialog(Activity activity, String permission, String message,
                                    int resultCode) {
        new AlertDialog.Builder(activity)
                .setMessage(message)
                .setPositiveButton("ok", (dialog, which) ->
                        showRequestPermissionDialog(activity, permission, resultCode))
                .setNegativeButton("not now", (dialog, which) -> { /* Do nothing */ })
                .show();
    }

    /**
     * Requests a single permission.
     */
    private void showRequestPermissionDialog(Activity activity, String permission, int resultCode) {
        ActivityCompat.requestPermissions(activity, new String[]{permission}, resultCode);
    }

    /**
     * Requests multiple permissions in one call.
     */
    private void showRequestPermissionsDialog(Activity activity, String[] permissions,
                                              int resultCode) {
        ActivityCompat.requestPermissions(activity, permissions, resultCode);
    }

    /**
     * Returns a message to be shown to the user that explains why a specific permission is
     * required.
     */
    public String messageForRationale(String permission) {
        String s;
        switch (permission) {
            case Manifest.permission.READ_PHONE_STATE:
                s = "access this device's state";
                break;
            case Manifest.permission.ACCESS_FINE_LOCATION:
                s = "access the location of this device";
                break;
            case Manifest.permission.SEND_SMS:
                s = "send text messages";
                break;
            default:
                throw new IllegalArgumentException("Permission not handled: " + permission);
        }

        return String.format("MyApp needs permission to %s.", s);
    }

    /**
     * Get the RequestCode for the given permission.
     */
    public int requestCodeForPermission(String permission) {
        int code;
        switch (permission) {
            case Manifest.permission.READ_PHONE_STATE:
                code = RequestCode.PERMISSION_READ_PHONE_STATE;
                break;
            case Manifest.permission.ACCESS_FINE_LOCATION:
                code = RequestCode.PERMISSION_FINE_LOCATION;
                break;
            case Manifest.permission.SEND_SMS:
                code = RequestCode.PERMISSION_SEND_SMS;
                break;
            // TODO: add required permissions for your app
            default:
                throw new IllegalArgumentException("Permission not handled: " + permission);
        }

        return code;
    }

    /**
     * Called if all requested permissions were granted in the same dialog.
     * E.g. FINE_LOCATION and SEND_SMS were requested, and both were granted.
     * <p>
     * Child class can override this method if it wants to know when this happens.
     * <p>
     * Linter can show an unjust "call requires permission" warning in child class if a method that
     * requires permission(s) is called. Silence it with `@SuppressWarnings("MissingPermission")`.
     */
    protected void onAllPermissionsGranted(String[] permissions) {

    }

    /**
     * Called for all permissions that were granted in the same dialog, in case not all were
     * granted. E.g. if FINE_LOCATION, COARSE_LOCATION and SEND_SMS were requested and FINE_LOCATION
     * was not granted but COARSE_LOCATION and SEND_SMS were, it will be called for COARSE_LOCATION
     * and SEND_SMS.
     * <p>
     * Child class can override this method if it wants to know when this happens.
     * <p>
     * Linter can show an unjust "call requires permission" warning in child class if a method that
     * requires permission(s) is called. Silence it with `@SuppressWarnings("MissingPermission")`.
     */
    protected void onPermissionGranted(String permission) {

    }

    /**
     * Called for all permissions that were denied in the same dialog, handled one by one.
     * <p>
     * Child class should not override this general behavior.
     */
    protected void onPermissionDenied(String permission) {
        String message = messageForRationale(permission);
        showRationaleDialog(this, permission, message, requestCodeForPermission(permission));
    }

    /**
     * Called for all permissions that were blocked in the same dialog, handled one by one.
     * <p>
     * Blocked means a user denied a permission with the 'never ask again' checkbox checked.
     * <p>
     * Child class must override and decide what to do when a permission is blocked.
     */
    protected abstract void onPermissionBlocked(String permission);
}

-> 符号是 Lambda 表达式

RequestCode 是一个接口,专门用于抽象化数字。

public interface RequestCode {
    int PERMISSION_READ_PHONE_STATE = 0;
    int PERMISSION_FINE_LOCATION = 1;
    int PERMISSION_SEND_SMS = 2;
    int PERMISSION_MULTIPLE = 3;
}

您可以根据需要进行更改。不要使用超过256的数字,否则会抛出异常,提示:

请求代码只能使用低8位。


在需要权限的活动中,您可以像这样使用它(仅作为示例)。确保活动扩展PermissionActivity

private void callThisSomewhere() {
    if (checkHasPermission(RequestCode.PERMISSION_READ_PHONE_STATE,
            Manifest.permission.READ_PHONE_STATE)) {
        tryDoStuffWithPhoneState();
    }
}

@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
private void doStuffWithPhoneState() {
    // Do stuff.
}

@Override
public void onPermissionGranted(String permission) {
    tryDoStuffWithPhoneState();
}

@Override
public void onPermissionBlocked(String permission) {
    // Disable parts of app that require this permission.
}

如果您一次请求多个权限,应使用RequestCode.PERMISSION_MULTIPLE。否则,只会请求第一个权限。

未在AndroidManifest.xml中列出的权限将自动被阻止,而不显示给用户对话框,因此请确保在清单中添加您需要请求的任何权限。


限制

此解决方案不与片段或服务兼容。


这非常有帮助。非常感谢!如果用户完全阻止了权限,您的首选备用方案是什么?您会禁用该功能并永远不再使用它,还是有一种方式可以引导用户进入设置来授予权限? - AdamMc331
@McAdam331 是的,我禁用了该功能,如此处所建议 https://developer.android.com/training/permissions/requesting.html 在处理权限请求响应下。 - Tim

1
This is what I used for managing permissions in my app.

这是我在应用程序中使用的权限管理方式。

权限的父类

    public class PermissionManager extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback {
    private static final int REQUEST_CODE = 200;
    private Activity context;

    private String[] permissions;
    private ArrayList<String> grantedPermissions = new ArrayList<>();
    private RequestedPermissionResultCallBack callBack;

    @Override
    public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);
    }

    @Override
    protected void onStart() {
        super.onStart();
        setContentView(R.layout.actvity_permission);
        checkForRequiredPermission(getIntent().getStringArrayExtra(getString(R.string.permission)));
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        switch (requestCode) {

            case REQUEST_CODE:

                checkForGrantedPermissions(permissions, grantResults);
                break;
        }

    }


    @TargetApi(Build.VERSION_CODES.M)
    private void checkForRequiredPermission(String[] permissions) {
        ArrayList<String> requiredPermissionList = new ArrayList<>();
        for (String permission : permissions) {
            if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                requiredPermissionList.add(permission);
            } else {
                grantedPermissions.add(permission);
            }
        }

        if (requiredPermissionList.size() > 0) {
            (this).requestPermissions(requiredPermissionList.toArray(new String[requiredPermissionList.size()]), REQUEST_CODE);
        } else {

            setResult(grantedPermissions);
        }

    }


    public void checkForGrantedPermissions(String[] permissions, int[] grantResults) {

        for (int i = 0; i < permissions.length; i++) {
            if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                grantedPermissions.add(permissions[i]);
            }

        }
        setResult(grantedPermissions);

    }

    private void setResult(ArrayList<String> grantedPermissions) {
        Intent intent = new Intent();
        intent.putStringArrayListExtra(getString(R.string.granted_permission), grantedPermissions);
        setResult(Activity.RESULT_OK, intent);
        this.finish();
    }
}

当您想检查权限时,请像这样调用此类。
private void checkForPermissions() {
        Intent intent = new Intent(this, PermissionManager.class);
        intent.putExtra(getString(R.string.permission), permission);
        startActivityForResult(intent, AppConstants.PERMSION_REQUEST_CODE);
    }

这里的permission是您想要请求的权限数组,类似以下内容。
    private String permission[] = new String[]{Manifest.permission.READ_PHONE_STATE, Manifest.permission.RECEIVE_SMS, Manifest.permission.READ_SMS};

这是关于 onActivityResult 的代码:

onActivityResult

case AppConstants.PERMSION_REQUEST_CODE:
                    ArrayList<String> grantedPermissionList = data.getStringArrayListExtra(getString(R.string.granted_permission));

                    if (grantedPermissionList != null && grantedPermissionList.size() > 0 && grantedPermissionList.contains(permission[0])) {
                        createRequest();

                    } else {
                        showSettingsDialog(getString(R.string.permission_required));
                    }

                    break;

@Yvette 我总是想要澄清我对任何类型的疑问,无论是关于编程还是其他方面。 - Vivek Mishra

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