美文网首页android 集结号android技术收藏高级Android
Android 6.0 - 动态权限管理的解决方案

Android 6.0 - 动态权限管理的解决方案

作者: SpikeKing | 来源:发表于2016-01-26 14:29 被阅读42346次

    欢迎Follow我的GitHub, 关注我的简书. 其余参考Android目录.

    Android 6.0版本(Api 23)推出了很多新的特性, 大幅提升了用户体验, 同时也为程序员带来新的负担. 动态权限管理就是这样, 一方面让用户更加容易的控制自己的隐私, 一方面需要重新适配应用权限. 时代总是不断发展, 程序总是以人为本, 让我们为应用添加动态权限管理吧! 这里提供了一个非常不错的解决方案, 提供源码, 项目可以直接使用.

    Permissions

    Android系统包含默认的授权提示框, 但是我们仍需要设置自己的页面. 原因是系统提供的授权框, 会有不再提示的选项. 如果用户选择, 则无法触发授权提示. 使用自定义的提示页面, 可以给予用户手动修改授权的指导.

    本文示例的GitHub下载地址

    在Api 23中, 权限需要动态获取, 核心权限必须满足. 标准流程:

    流程图

    如果用户点击, 不再提示, 则系统授权弹窗将不会弹出. 流程变为:

    流程图

    流程就这些, 让我们看看代码吧.


    1. 权限

    在AndroidManifest中, 添加两个权限, 录音修改音量.

        <!--危险权限-->
        <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    
        <!--一般权限-->
        <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
    

    危险权限必须要授权, 一般权限不需要.

    检测权限类

    /**
     * 检查权限的工具类
     * <p/>
     * Created by wangchenlong on 16/1/26.
     */
    public class PermissionsChecker {
        private final Context mContext;
    
        public PermissionsChecker(Context context) {
            mContext = context.getApplicationContext();
        }
    
        // 判断权限集合
        public boolean lacksPermissions(String... permissions) {
            for (String permission : permissions) {
                if (lacksPermission(permission)) {
                    return true;
                }
            }
            return false;
        }
    
        // 判断是否缺少权限
        private boolean lacksPermission(String permission) {
            return ContextCompat.checkSelfPermission(mContext, permission) ==
                    PackageManager.PERMISSION_DENIED;
        }
    }
    

    2. 首页

    假设首页需要使用权限, 在页面显示前, 即onResume时, 检测权限,
    如果缺少, 则进入权限获取页面; 接收返回值, 拒绝权限时, 直接关闭.

    public class MainActivity extends AppCompatActivity {
    
        private static final int REQUEST_CODE = 0; // 请求码
    
        // 所需的全部权限
        static final String[] PERMISSIONS = new String[]{
                Manifest.permission.RECORD_AUDIO,
                Manifest.permission.MODIFY_AUDIO_SETTINGS
        };
    
        @Bind(R.id.main_t_toolbar) Toolbar mTToolbar;
    
        private PermissionsChecker mPermissionsChecker; // 权限检测器
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ButterKnife.bind(this);
    
            setSupportActionBar(mTToolbar);
    
            mPermissionsChecker = new PermissionsChecker(this);
        }
    
        @Override protected void onResume() {
            super.onResume();
    
            // 缺少权限时, 进入权限配置页面
            if (mPermissionsChecker.lacksPermissions(PERMISSIONS)) {
                startPermissionsActivity();
            }
        }
    
        private void startPermissionsActivity() {
            PermissionsActivity.startActivityForResult(this, REQUEST_CODE, PERMISSIONS);
        }
    
        @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            // 拒绝时, 关闭页面, 缺少主要权限, 无法运行
            if (requestCode == REQUEST_CODE && resultCode == PermissionsActivity.PERMISSIONS_DENIED) {
                finish();
            }
        }
    }
    

    核心权限必须满足, 如摄像应用, 摄像头权限就是必须的, 如果用户不予授权, 则直接关闭.


    3. 授权页

    授权页, 首先使用系统默认的授权页, 当用户拒绝时, 指导用户手动设置, 当用户再次操作失败后, 返回继续提示. 用户手动退出授权页时, 给使用页发送授权失败的通知.

    /**
     * 权限获取页面
     * <p/>
     * Created by wangchenlong on 16/1/26.
     */
    public class PermissionsActivity extends AppCompatActivity {
    
        public static final int PERMISSIONS_GRANTED = 0; // 权限授权
        public static final int PERMISSIONS_DENIED = 1; // 权限拒绝
    
        private static final int PERMISSION_REQUEST_CODE = 0; // 系统权限管理页面的参数
        private static final String EXTRA_PERMISSIONS =
                "me.chunyu.clwang.permission.extra_permission"; // 权限参数
        private static final String PACKAGE_URL_SCHEME = "package:"; // 方案
    
        private PermissionsChecker mChecker; // 权限检测器
        private boolean isRequireCheck; // 是否需要系统权限检测
    
        // 启动当前权限页面的公开接口
        public static void startActivityForResult(Activity activity, int requestCode, String... permissions) {
            Intent intent = new Intent(activity, PermissionsActivity.class);
            intent.putExtra(EXTRA_PERMISSIONS, permissions);
            ActivityCompat.startActivityForResult(activity, intent, requestCode, null);
        }
    
        @Override protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            if (getIntent() == null || !getIntent().hasExtra(EXTRA_PERMISSIONS)) {
                throw new RuntimeException("PermissionsActivity需要使用静态startActivityForResult方法启动!");
            }
            setContentView(R.layout.activity_permissions);
    
            mChecker = new PermissionsChecker(this);
            isRequireCheck = true;
        }
    
        @Override protected void onResume() {
            super.onResume();
            if (isRequireCheck) {
                String[] permissions = getPermissions();
                if (mChecker.lacksPermissions(permissions)) {
                    requestPermissions(permissions); // 请求权限
                } else {
                    allPermissionsGranted(); // 全部权限都已获取
                }
            } else {
                isRequireCheck = true;
            }
        }
    
        // 返回传递的权限参数
        private String[] getPermissions() {
            return getIntent().getStringArrayExtra(EXTRA_PERMISSIONS);
        }
    
        // 请求权限兼容低版本
        private void requestPermissions(String... permissions) {
            ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE);
        }
    
        // 全部权限均已获取
        private void allPermissionsGranted() {
            setResult(PERMISSIONS_GRANTED);
            finish();
        }
    
        /**
         * 用户权限处理,
         * 如果全部获取, 则直接过.
         * 如果权限缺失, 则提示Dialog.
         *
         * @param requestCode  请求码
         * @param permissions  权限
         * @param grantResults 结果
         */
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            if (requestCode == PERMISSION_REQUEST_CODE && hasAllPermissionsGranted(grantResults)) {
                isRequireCheck = true;
                allPermissionsGranted();
            } else {
                isRequireCheck = false;
                showMissingPermissionDialog();
            }
        }
    
        // 含有全部的权限
        private boolean hasAllPermissionsGranted(@NonNull int[] grantResults) {
            for (int grantResult : grantResults) {
                if (grantResult == PackageManager.PERMISSION_DENIED) {
                    return false;
                }
            }
            return true;
        }
    
        // 显示缺失权限提示
        private void showMissingPermissionDialog() {
            AlertDialog.Builder builder = new AlertDialog.Builder(PermissionsActivity.this);
            builder.setTitle(R.string.help);
            builder.setMessage(R.string.string_help_text);
    
            // 拒绝, 退出应用
            builder.setNegativeButton(R.string.quit, new DialogInterface.OnClickListener() {
                @Override public void onClick(DialogInterface dialog, int which) {
                    setResult(PERMISSIONS_DENIED);
                    finish();
                }
            });
    
            builder.setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() {
                @Override public void onClick(DialogInterface dialog, int which) {
                    startAppSettings();
                }
            });
    
            builder.show();
        }
    
        // 启动应用的设置
        private void startAppSettings() {
            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
            intent.setData(Uri.parse(PACKAGE_URL_SCHEME + getPackageName()));
            startActivity(intent);
        }
    }
    

    注意isRequireCheck参数的使用, 防止和系统提示框重叠.
    系统授权提示: ActivityCompat.requestPermissions, ActivityCompat兼容低版本.

    效果


    自定义授权

    关键部分就这些了, 动态权限授权虽然给程序员带来了一些麻烦, 但是对用户还是很有必要的, 我们也应该欢迎, 毕竟每个程序员都是半个产品经理.

    危险权限列表

    危险权限列表

    参考1, 参考2.

    OK, that's all! Enjoy it.

    相关文章

      网友评论

      • XBaron:https://www.jianshu.com/p/2324a2bdb3d4
        基于AOP的权限申请,一个注解搞定哦
      • Android轮子哥:写得不错,另外推荐一篇,一句代码搞定权限请求,从未如此简单:https://www.jianshu.com/p/c69ff8a445ed
      • 逗你玩222:楼主你那个检测权限工具在小米4a上,没有开启权限。。检测出来开启权限了,在vivo上也是这样的啊
      • 逗你玩222:楼主是设置以后再onResume()再次调用检测权限的方法,来知道用户是否手动设置成功吧!
      • 逗你玩222:楼主你这个如果用户第一个进来拒绝权限,然后进入那个设置界面。。用户手动退出授权页时, 给使用页发送授权失败的通知.你这里是怎么通知的手动设置是否成功的。我没看的懂啊!
      • 相互交流:楼主我想问一下,通过这个方式我直接去检测手机是否获取到了定位权限,事实上就只是测试,private final static String authBaseArr[] =
        {Manifest.permission.ACCESS_FINE_LOCATION };里面只有一个定位权限,app实际上并没有用到定位,系统并不会弹出对话框,让用户去授权,直接就是没有任何反应,检查SD卡就会弹出,楼主知道是什么原因吗?还望解答啊
      • 老实李:很赞~
      • baby_double:文中说假设首页需要使用权限, 在页面显示前, 即onResume时, 检测权限,可不可以在onCreate中检测呢,在这两个地方检测有什么影响吗?
      • 一根愤怒的黄瓜:魅族note5 6.0android系统 设置之后 没效果啊。。权限一直被开放 也会弹出提示
        王神仙: @87fee3cdadb2 设置一下targrtversioncode
      • crafttang:不错的教程
      • amydyf:在demo里 // 所需的全部权限
        static final String[] PERMISSIONS = new String[]{
        Manifest.permission.READ_PHONE_STATE
        }权限 获取设备ID,会报
        java.lang.SecurityException: Requires READ_PHONE_STATE: Neither user 10214 nor current process has android.permission.READ_PHONE_STATE.
      • d5ba366035a2:项目里的drawable文件夹下的ic_mic_off是没有用到的吧
      • fendovy: :disappointed_relieved: 不是很实用,android机型很多,oppo 手机 点击设置按钮跳的设置界面里面没有权限管理选项。6.0之后的动态权限管理,还是应该在用户使用该功能的时候弹出提示比较好。
      • 不会飞的扫把:当权限是拒绝状态的时候,alertdialog点击取消无法退出应用。
      • ed7da7104a16:楼主请问一下,AS编译Manifest.permission报错怎么回事
        一盏孤灯守着孤城:是要导入 Android的包 没看清楚 。不好意思
        一盏孤灯守着孤城:@android_carson 我也是的。并没有生成Mainfest.class文件 咋弄。楼主 求回复
      • Jrking:站在巨人肩膀上的 项目:https://github.com/forJrking/HeiPermission 很轻量,定制度还可以
        d5ba366035a2:@XiaowuSS pro6还没升级到6.0,所以是默认已授权
        一洼世界:您好。 我用的魅族pro6. 下载了apk ,好像不太行啊。明明已经关掉了相机权限。仍提示可以使用,
        SpikeKing:@Jrking 赞!
      • 碎念枫子:谢谢楼主的分享,但是我注意到启动该apk的时候会白屏很久,不知道是上面原因呢
      • coolzpw:还有个问题,我看了下你推荐的参考1,是不是启动应用的设置是时候调用startforesulf?
      • coolzpw:楼主,是不是如果危险权限不在AndroidManifest注册,是不是调用requestpermission,在请求回调里面直接走的是拒绝啊?我看晚上有一篇写的是如果不申明权限,调用请求方法会蹦,感觉是有错误的说法
        SpikeKing:@coolzpw 恩, 没事的, 多看看代码, 代码是不会骗人的.
        coolzpw:@SpikeKing 恩恩 了解到了 之前看了一篇文章有点误导 现在弄清楚了 感谢感谢 !
        SpikeKing:@coolzpw 使用的权限都需要Manifest注册, 但是危险权限还需要用户再次确认.
      • leo62:想请教一下进入设置页面怎么授权的?测试机型是小米手机
      • 7549683e9513:
        PACKAGE_URL_SCHEME 是什么值
      • 丸子不爱吃丸子:发现一个bug 如果选择了不在提示 然后拒绝 当用户back的时候会变成死循环 永远退不出去
        SpikeKing:@3d4a6027380c 谢谢提醒! 我又发现了一个问题.
        SpikeKing:@3d4a6027380c 是正确的. 用户可以点击退出按钮退出, 因为缺少权限, 所以提示用户无法运行, 要么退出要么授予关键权限, 并非死循环.
        SpikeKing:@3d4a6027380c 3Q, 我来看看.
      • 繁体字遇上简体字:很好的,虽然现在还没有大量的6.0机器,不过提前准备有备无患!
        SpikeKing:@繁体字遇上简体字 恩, 6.0或早或晚的事.
      • i卓:权限问题困扰我好久,一直没有很好的解决,谢谢分享。android有你们才更精彩👍
        SpikeKing:@9a584795fe0f 这个方案还蛮不错的。
      • jasonkxs:给你推荐到gank.io里面去
        SpikeKing:@jasonkxs 好的!
      • jasonkxs:写的很详细,学习了~
        jasonkxs:@SpikeKing 好的
      • chaozhung:楼主的办法的确不错,谢谢分享☺
      • HanlyJiang:谢谢分享
        SpikeKing:@蒋航 共同进步

      本文标题:Android 6.0 - 动态权限管理的解决方案

      本文链接:https://www.haomeiwen.com/subject/ocddkttx.html