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

How to avoid writing duplicate boilerplate code for requesting permissions?

我正在将我的应用更新为 Android 6 兼容。权限模型在理论上并不是很复杂,但现在我正在实施它,我发现自己在我的每一个需要权限的活动中都编写了同样丑陋的样板代码。

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

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

然后在 onRequestPermissionsResult 中,我必须 check/filter 每个请求的结果并将其转换为我 activity 理解的内容。

我现在正在更新我的第二个 activity,权限代码与第一个非常相似,看起来就像是复制粘贴的。行长,代码大同小异,简直丑爆了

我不想使用第三方解决方案,我已经尝试了一些,但我更愿意完全控制代码。例如,一些库不支持 Java 8,我在我的项目中使用的是

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

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

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

它的工作原理是您的 activity 电话

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

来自parentclass。如果已授予权限,则代码可以继续。如果没有,parent class 将请求权限并将结果发送到 child class 使用抽象方法 and/or 一个或多个可覆盖的方法。


parentclass

这个class可以保持不变,除了messageForRationale()requestCodeForPermission()中的开关块。更新您的应用所需的权限。

/**
 * 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
     * 
     * -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 expressions.

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 的数字。如果这样做,将抛出异常

You can only use the lower 8 bits for the request code.


在需要权限的活动中,可以这样使用(仅举个例子)。一定要有 activity extend 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 中列出的权限将被自动阻止而不向用户显示对话框,因此请务必在清单中添加您必须请求的任何权限。


限制

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

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

父级 Class 权限

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

当你想检查权限时调用这个class像这样

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

的代码
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;