美文网首页
Runtime Permissions(郭霖CSDN公开课)

Runtime Permissions(郭霖CSDN公开课)

作者: 狮_子歌歌 | 来源:发表于2017-01-04 21:51 被阅读1254次

    运行时权限

    Api23开始,Android权限机制更改,有一部分权限不再是简单的在AndroidManifest.xml中声明即可。而是需要在运行时让用户去选择是否允许该项权限的操作。

    那么哪些权限需要在运行时申请呢?危险权限需要这么做,而普通权限仍然和以前一样。具体的分类可以看之前的文章Runtime Permissions

    普通权限一半时不会威胁安全、隐私;而危险权限一般涉及用户隐私,设备安全问题。

    AndroidManifest声明权限

    无论是危险权限还是普通权限都必须在AndroidManifest.xml中声明,这一步的作用是什么?主要有两方面:

    1. 程序安装时告知用户需要的权限,由用户决定是否安装。
    2. 在设置的应用选项中,点击应用查看信息可以看到应用获取的权限。由用户决定是否保留应用。

    在build.gradle(app)中targetSdkVersion的值低于23时,应用运行在Android6.0及以上系统时,会默认打开在AndroidManifest.xml声明的权限。

    如果升级应用,修改了targetSdkVersion为23及以上,再次运行时,依旧默认允许所有AndroidManifest.xml中所用声明的权限。

    危险权限导致Crash

    如果在Android6.0及以上运行程序执行危险权限相关操作,没有运行时检查权限获取情况,如果没有获取会导致程序crash。例如Console输出:Caused by: java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.CALL dat=tel:xxxxxxxxxxxx cmp=com.android.server.telecom/.components.UserCallActivity } from ProcessRecord{cae02cf 30814:android.example.com.permissionusage/u0a154} (pid=30814, uid=10154) with revoked permission android.permission.CALL_PHONE

    有时候需要对执行危险权限操作进行封装,例如打电话操作:

    Intent intent = new Intent(Intent.ACTION_CALL);
    intent.setData(Uri.parse("tel://1234567890"));
    startActivity(intent);
    

    直接写成一个方法编译器会报错,即便不去处理也可以运行。可以通过捕获上面Console输出的异常来解决编译器报错:

        private void makeCall() {
            try{
                Intent intent = new Intent(Intent.ACTION_CALL);
                intent.setData(Uri.parse("tel://1234567890"));
                startActivity(intent);
            }catch (SecurityException e) {
                e.printStackTrace();
            }
        }
    

    运行时权限基础写法

    单个运行时权限申请

        /**
         * 单个权限授权
         * @param view
         */
        public void btnClick(View view) {
            if(ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
                    != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(
                        this, new String[]{Manifest.permission.CALL_PHONE}, CALL_REQUEST);
            }else {
                makeCall();
            }
        }
    

    权限申请回调

    public void onRequestPermissionsResult(
                int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            switch(requestCode) {
                case CALL_REQUEST:
                    if(grantResults.length > 0
                            && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                        makeCall();
                    }else {
                        Snackbar.make(mContainer, "权限被拒绝了", Snackbar.LENGTH_SHORT).show();
                    }
                    break;
                default:
                    break;
            }
        }
    

    其实当grantResults数组长度为0时,程序某个地方一定出现问题。

    多个运行时权限申请

        /**
         * 多个权限同时授权
         * @param v
         */
        public void btnMorePermissions(View v) {
            List<String> permissions = new ArrayList<>();
            //安全权限,无需运行时检查
            if(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_NETWORK_STATE)
                    != PackageManager.PERMISSION_GRANTED) {
                permissions.add(Manifest.permission.ACCESS_NETWORK_STATE);
            }
            if(ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
                    != PackageManager.PERMISSION_GRANTED) {
                permissions.add(Manifest.permission.CALL_PHONE);
            }
            if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    != PackageManager.PERMISSION_GRANTED) {
                permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
            }
            if(!permissions.isEmpty()) {
                ActivityCompat.requestPermissions(
                        this,
                        permissions.toArray(new String[permissions.size()]),
                        MORE_PERMISSIONS_REQUEST);
            }else {
                doSomething();
            }
        }
    

    权限申请回调

    public void onRequestPermissionsResult(
                int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            switch(requestCode) {
                case MORE_PERMISSIONS_REQUEST:
                    if(grantResults.length > 0) {
                        for(int i : grantResults) {
                            if(i != PackageManager.PERMISSION_GRANTED) {
                                Snackbar.make(mContainer, "某个权限没有授权", Snackbar.LENGTH_SHORT).show();
                                return;
                            }
                        }
                        doSomething();
                    }else {
    
                    }
                default:
                    break;
            }
        }
    

    为什么读写外部存储属于危险权限

    在Android6.0以前,读写外部存储只需要在AndroidManifest.xml中声明即可,但是由于应用的强制行为,导致用户的外部存储中文件杂乱,权限被滥用。所以将其制定为危险权限。

    在外部存储目录Android/data/packgae_name属于应用私有的目录,不需要运行时申请读写外部存储危险权限,甚至不需要在AndroidManifest.xml中声明就可以读写。应用可以随意支配自身的文件存储(cache目录经常会被清理软件清理,主要文件放入files目录下)。

    这样既保证了外部存储目录的整洁,又不会给应用开发者带来不必要的麻烦。具体操作可以看文章Android数据存储之File总结。而且应用卸载后,相应包名的目录也会删除。

    封装

    由于运行时权限申请的繁琐,所以封装饰必须的。但是申请权限的操作必须建立在Activity之上。只有在Activity中才可以弹出申请权限对话框。而且申请回调函数属于Activity的方法,所以申请权限的操作和Activity藕合度非常高。可以通过以下办法:

    1. 自定义一个PermissionActivity,专门用于处理申请运行时权限操作。该Activity背景透明,用户无法察觉。执行完后finish掉。
    2. 参照RxPermissions第三方库的实现。
    3. 创建一个BaseActivity去实现运行时权限申请方法,然后所有Activity继承BaseActivity,需要时调用方法即可。BaseActivity对于一个项目可以提高Activity类的扩展性,在里面实现自己的方法供子类使用。

    BaseActivity

    public class BaseActivity extends AppCompatActivity{
    
        private static final int REQUEST_CODE = 1;
        private PermissionListener mListener;
    
        public void requestRuntimePermissions(String[] permissions, PermissionListener listener) {
            mListener = listener;
            List<String> permissionList = new ArrayList<>();
            for(String permission : permissions) {
                if(ContextCompat.checkSelfPermission(this, permission)
                        != PackageManager.PERMISSION_GRANTED) {
                    permissionList.add(permission);
                }
            }
            if(!permissionList.isEmpty()) {
                ActivityCompat.requestPermissions(
                        this,
                        permissionList.toArray(new String[permissionList.size()]),
                        REQUEST_CODE);
            }else {
                mListener.onGranted();
            }
        }
    
    
        @Override
        public void onRequestPermissionsResult(
                int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            switch (requestCode) {
                case REQUEST_CODE:
                    if(grantResults.length > 0) {
                        List<String> deniedPermission = new ArrayList<>();
                        for(int i = 0; i < grantResults.length; i++) {
                            int grantResult = grantResults[i];
                            if(grantResult == PackageManager.PERMISSION_DENIED) {
                                deniedPermission.add(permissions[i]);
                            }
                        }
                        if(deniedPermission.isEmpty()) {
                            mListener.onGranted();
                        }else {
                            mListener.onDenied(deniedPermission);
                        }
                    }
                   break;
                default:
                    break;
            }
        }
    
    }
    

    PermissionListener用于将申请结果返回给调用的Activity。让Activity去实现权限申请结果相应的操作。

    public interface PermissionListener {
        void onGranted();
        void onDenied(List<String> deniedPermissions);
    }
    

    最后在Activity中使用:

    public class SecondActivity extends BaseActivity{
        private LinearLayout mContainer;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_second);
            initView();
        }
    
        private void initView() {
            mContainer = (LinearLayout) findViewById(R.id.id_second_container);
        }
    
        public void btnRuntimePermission(View view) {
            requestRuntimePermissions(new String[]{
                    Manifest.permission.CALL_PHONE,
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.CAMERA,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE}, new PermissionListener() {
                @Override
                public void onGranted() {
                    Snackbar.make(mContainer, "All Permissions Granted!", Snackbar.LENGTH_SHORT).show();
                }
    
                @Override
                public void onDenied(List<String> deniedPermissions) {
                    StringBuilder builder = new StringBuilder(32);
                    int deniedCount = deniedPermissions.size();
                    for(int i = 0; i < deniedCount; i++) {
                        String[] strArray = deniedPermissions.get(i).split("\\.");
                        builder.append(strArray[strArray.length - 1]);
                        if(i == (deniedCount - 1)) {
                            builder.append(".");
                        }else {
                            builder.append(",");
                        }
                    }
                    Snackbar.make(
                            mContainer,
                            "Denied Permissions:" + builder.toString(),
                            Snackbar.LENGTH_SHORT
                    ).show();
                }
            });
        }
    }
    

    参考

    Android 6.0运行时权限讲解

    运行效果

    RuntimePermission.gif

    相关文章

      网友评论

          本文标题:Runtime Permissions(郭霖CSDN公开课)

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