Android M 封装过的运行时权限处理

1,724 阅读4分钟
原文链接: www.jianshu.com

本文对Android M的运行时权限进行了代码封装,有助于项目开发业务代码中大面积的出现重复的运行时权限处理代码

1 前言

Android M 运行时权限想必大家已经不陌生了。在这还是放出一篇经典的对于运行时权限的说明和解释的说明。

Android M 新的运行时权限开发者需要知道的一切

同时Google也提供了帮助处理的第三方库EasyPermission,可以见这篇文章。

Android开源项目-Easypermissions

如果对以上2个比较清楚的可以直接跳过。

2 出现的问题

所有的封装和处理都是由开发中出现的各种问题而推动的。很明显,权限处理的代码缺点就是你不能处理一次就行了。由于用户可以在系统设置-应用设置里手动设置权限,如下图。


权限修改

所以需要在每次遇到Dangerous Permission 相关的业务处理时都要添加权限判断处理。虽然代码不多,但是需要同时在onRequestPermissionsResult、onPermissionsGranted、onPermissionsDenied等多处进行代码处理。就会显得很乱,降低代码的可读性。

最理想的方式当然时只要在需要的地方加一行代码即可以处理权限。封装权限处理应运而生。

3 封装后的处理

我们先直接看封装后的权限处理代码变成了什么样。下面是一个模拟读取文件的业务,调用业务前需要处理读取文件权限。

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.btn_read_file:
            doReadFilePermisssion();
            break;
    }
}

//动态权限申请
private void doReadFilePermisssion() {
    String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE};

    performCodeWithPermission(getString(R.string.permission_rc_storage), RC_STORAGE, perms,
            new PermissionCallback() {
                @Override
                public void hasPermission() {
                    doReadFile();
                }

                @Override
                public void noPermission(Boolean hasPermanentlyDenied) {
                    if(hasPermanentlyDenied) {
                        //只是提供跳转系统设置的提示 系统返回后不做检查处理
//                            alertAppSetPermission(getString(R.string.permission_storage_deny_again));

                        //如果需要跳转系统设置页后返回自动再次检查和执行业务
                        alertAppSetPermission(getString(R.string.permission_storage_deny_again), REQUEST_APPSET);
                    }
                }
            });
}

//开始读写文件业务
private void doReadFile() {
    File file = new File(Environment.getExternalStorageDirectory() + "/com.tsy/a.zip");
    // ...

    Toast.makeText(getApplicationContext(), "成功执行读写文件业务", Toast.LENGTH_SHORT).show();
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if(requestCode == REQUEST_APPSET) {         //如果需要跳转系统设置页后返回自动再次检查和执行业务 如果不需要则不需要重写onActivityResult
        doReadFilePermisssion();
    }
}

注意下上面代码的alertAppSetPermission方法作用是,当权限被拒绝时,判断如果点了不再询问被拒绝的情况后弹出一个alert框,提示用户前往系统设置开启权限,这样更加人性化,不然如果用户拒绝过一次后之后的该权限将全部都会被拒绝。如下图


系统提示框

再看下alertAppSetPermission方法有2个重载方法,有一个比另一个多了个requestCode参数。目的是当用户跳转系统设置返回后,有的需求可能希望返回后自动再判断用户有没有在系统设置中修改过权限从而直接继续下一步操作。这样实现onActivityResult方法,在系统页面返回时再次判断权限是否授予。

通过上面代码可以发现,封装过后只需要在需要权限处理的地方加一个performCodeWithPermission,然后再重载onActivityResult方法。如果没有页面返回后自动继续下一步的需求的话那就只需要调用performCodeWithPermission就可以了。做到了“伪一行代码”处理运行时权限。哈哈!

没有对比就没有伤害,我再把没有封装过的完整权限处理代码贴上来对比下。(使用了EasyPermission)

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.btn_read_file:
            doReadFilePermisssion();
            break;
    }
}

//动态权限申请
private void doReadFilePermisssion() {
    String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE};

    if (EasyPermissions.hasPermissions(this, perms)) {
        doReadFile();   //权限通过
    } else {
        //请求权限
        EasyPermissions.requestPermissions(this, getString(R.string.permission_rc_storage), RC_STORAGE, perms);
    }
}

//开始读写文件业务
private void doReadFile() {
    File file = new File(Environment.getExternalStorageDirectory() + "/com.tsy/a.zip");
    // ...

    Toast.makeText(getApplicationContext(), "成功执行读写文件业务", Toast.LENGTH_SHORT).show();
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if(requestCode == REQUEST_APPSET) {         //如果需要跳转系统设置页后返回自动再次检查和执行业务 如果不需要则不需要重写onActivityResult
        doReadFilePermisssion();
    }
}

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

    EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}

//权限通过
@Override
public void onPermissionsGranted(int requestCode, List perms) {
    doReadFile();
}

//权限拒绝
@Override
public void onPermissionsDenied(int requestCode, List perms) {
    if(EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
        //点了不再询问 弹出提示跳转系统设置打开权限
        new AppSettingsDialog.Builder(this, getString(R.string.permission_storage_deny_again))
                .setTitle(getString(R.string.permission_deny_again_title))
                .setPositiveButton(getString(R.string.permission_deny_again_positive))
                .setNegativeButton(getString(R.string.permission_deny_again_nagative), null)
                .setRequestCode(REQUEST_APPSET)
                .build()
                .show();
    } else {
        //拒绝权限
    }
}

发现比封装过的多了onRequestPermissionsResult、onPermissionsGranted、onPermissionsDenied三个回调的处理。就是说每次写权限处理需要写4~5个地方。而封装过的只需要写1~2个即可。

4 核心代码

所有的代码被封装在了BaseActivity中。其实很简单,不作过多解释了。贴在下面供参考。

public class BaseActivity extends Activity implements EasyPermissions.PermissionCallbacks {

    private Map mPermissonCallbacks = new HashMap<>();

    /**
     * 权限回调接口
     */
    protected interface PermissionCallback {
        /**
         * 成功获取权限
         */
        void hasPermission();

        /**
         * 没有权限
         * @param hasPermanentlyDenied 是否点击不再询问,被设置为永久拒绝权限
         */
        void noPermission(Boolean hasPermanentlyDenied);
    }

    /**
     * 请求权限操作
     * @param rationale 请求权限提示语
     * @param permissionRequestCode 权限requestCode
     * @param perms 申请的权限列表
     * @param callback 权限结果回调
     */
    protected void performCodeWithPermission(@NonNull String rationale,
                                             final int permissionRequestCode, @NonNull String[] perms, @NonNull PermissionCallback callback) {
        if (EasyPermissions.hasPermissions(this, perms)) {
            callback.hasPermission();
        } else {
            mPermissonCallbacks.put(permissionRequestCode, callback);
            EasyPermissions.requestPermissions(this, rationale, permissionRequestCode, perms);
        }
    }

    /**
     * 跳转设置弹框 建议在权限被设置为不在询问时弹出 提示用户前往设置页面打开权限
     * @param tips 提示信息
     */
    protected void alertAppSetPermission(String tips) {
        new AppSettingsDialog.Builder(this, tips)
                .setTitle(getString(R.string.permission_deny_again_title))
                .setPositiveButton(getString(R.string.permission_deny_again_positive))
                .setNegativeButton(getString(R.string.permission_deny_again_nagative), null)
                .build()
                .show();
    }

    /**
     * 跳转设置弹框 建议在权限被设置为不在询问时弹出 提示用户前往设置页面打开权限
     * @param tips 提示信息
     * @param requestCode 页面返回时onActivityResult的requestCode
     */
    protected void alertAppSetPermission(String tips, int requestCode) {
        new AppSettingsDialog.Builder(this, tips)
                .setTitle(getString(R.string.permission_deny_again_title))
                .setPositiveButton(getString(R.string.permission_deny_again_positive))
                .setNegativeButton(getString(R.string.permission_deny_again_nagative), null)
                .setRequestCode(requestCode)
                .build()
                .show();
    }

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

        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

    @Override
    public void onPermissionsGranted(int requestCode, List perms) {
        PermissionCallback callback = mPermissonCallbacks.get(requestCode);
        if(callback != null) {
            callback.hasPermission();
        }
    }

    @Override
    public void onPermissionsDenied(int requestCode, List perms) {
        PermissionCallback callback = mPermissonCallbacks.get(requestCode);
        if(callback != null) {
            if(EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
                callback.noPermission(true);
            } else {
                callback.noPermission(false);
            }
        }
    }
}

对外开放了2个接口. 分别是performCodeWithPermission和alertAppSetPermission。

5 总结

所有的代码见Github:github.com/tsy12321/An…

在项目开发中,多处需要进行权限处理,必要的封装可以大大增加代码可读性,专注于业务代码的编写。

更多文章关注我的公众号


我的公众号