Android 运行时权限


(一):

        Android 6.0在我们原有的AndroidManifest.xml声明权限的基础上,又新增了运行时权限动态检测,以下权限都需要在运行时判断:

身体传感器;日历;摄像头;通讯录;地理位置;麦克风;电话;短信;存储空间


系统权限分为两类:
  • 正常权限不会直接给用户隐私权带来风险。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。
  • 危险权限会授予应用访问用户机密数据的权限。如果您列出了危险权限,则用户必须明确批准您的应用使用这些权限。

(二):一般用法 Activity当中

       onRequestPermissionsResult()方法为使用requestPermissions()方法后的回调

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Button chooseFromAlbum = (Button) findViewById(R.id.choose_from_album);
   
    chooseFromAlbum.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(MainActivity.this, new String[]{ Manifest.permission. WRITE_EXTERNAL_STORAGE }, 1);
            } else {
                openAlbum();
            }
        }
    });
}

private void openAlbum() {
    Intent intent = new Intent("android.intent.action.GET_CONTENT");
    intent.setType("image/*");
    startActivityForResult(intent, CHOOSE_PHOTO); // 打开相册
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    switch (requestCode) {
        case 1:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                openAlbum();
            } else {
                Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
            }
            break;
        default:
    }
}

(三):fragement中无法回调onRequestPermissionsResult()方法:

      方法一:我们在Fragment中申请时不是使用AcyivityCompat而是使用Fragment本身,接着类似于Activity,在Fragement中的onRequestPermissionsResult()方法中进行回调。代码如下:

MyFragment.this.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},  1);

   方法二:如果我们在Activity中重写onRequestPermissionsResult方法你会发现它会回调到Activity中的onRequestPermissionsResult方法,详见这篇博文:https://blog.csdn.net/weixin_37185329/article/details/78030574




关于一些相关知识储备可见:https://www.jianshu.com/p/d6b3e16cc1d9

需要注意的是:

  1. 在 Android 5.1(API 22)或更低版本,并且应用的 targetSdkVersion 是 22 或更低版本,则系统会在安装时要求用户授予权限。(沿用之前的权限系统)
  2. 即使在安装时已经授予应用所有权限,在Android 6.0之后依然可以通过 "Setting" 来关闭已经授予的权限。
  3. 在请求权限时,系统只告诉用户应用需要的权限组,而不告知具体权限。
  4. 如果在未检查授权的情况下,直接使用危险权限,会导致程序Crash。
  5. 使用 v4 包中的 ContextCompat 处理权限(v13 包中的FragmentCompat),不需要考虑版本问题。

相关API

  • int checkSelfPermission()

检查应用是否有指定权限。返回值为 PackageManager.PERMISSION_GRANTED 表示有权限, PackageManager.PERMISSION_DENIED 表示无权限。

  • void requestPermissions()

请求指定权限,可以是多个,以数组的方式。

  • boolean shouldShowRequestPermissionRationale()

如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。

  • void onRequestPermissionsResult()

请求权限的结果回调。

使用原生API

因为以上列举的相关API都是在 API 23 才有的,为了适配低版本,官方提供了 v4 v13 兼容包。我们可以直接使用兼容包中的方法进行权限处理。

步骤(以拨打电话为例)
  • 还是和以前一样,先在清单文件中申请所需要的权限。
<uses-permission android:name="android.permission.CALL_PHONE"/>
  • 在使用到拨打电话的地方,进行权限检查
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
        != PackageManager.PERMISSION_GRANTED) {
    // 应用没有授予拨打电话权限,请求权限
    requestCameraPermission();
} else {
    // 应用被授予拨打电话权限 PackageManager.PERMISSION_GRANTED
    makeCall();
}
  • 如果有权限,直接拨打电话,至此结束
  • 如果没有权限,则请求权限
ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.CALL_PHONE}, REQUEST_CALLPHONE);
  • 在请求权限过程中可以使用shouldShowRequestPermissionRationale()检查是否被拒绝过,如果被拒绝过,可以给用户一个详细解释。
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
    // 向用户详细解释申请该权限的原因
    new AlertDialog.Builder(this)
            .setCancelable(false)
            .setMessage("拨打电话需要使用电话权限,如果不授予权限会导致该功能无法正常使用")
            .setPositiveButton("好的", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    ActivityCompat.requestPermissions(
                            OriginalActivity.this,
                            new String[]{Manifest.permission.CALL_PHONE},
                            REQUEST_CALLPHONE
                    );
                }
            })
            .setNegativeButton("不给", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            })
            .show();
} 
  • 处理授权结果回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                   @NonNull int[] grantResults) {

    if (requestCode == REQUEST_CALLPHONE) {
        if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 授予权限,拨打电话
            makeCall();
        } else {
            Toast.makeText(this, "请求权限被拒绝", Toast.LENGTH_SHORT).show();
        }
    } else {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

使用轮子

在处理运行时权限的时候,虽然官方提供了兼容包不再需要做版本检查,但处理起来依然使代码很杂乱。现在已经出现了很多处理运行时权限的开源库,这里给大家推荐 PermissionsDispatcher。该库在GitHub同比获得 star 最多。而且使用 apt 技术,在编译时期动态生成xxxxPermissionsDispatcher模板代码,效率很高!

API 简介

该库使用 apt 技术,自然使用的就是注解。

注解 是否必须 作用
@RuntimePermissions 标记Activity/Fragment,则注解解释器会生成对应类的代码
@NeedsPermission 标记需要授权才能执行的方法
@OnShowRationale   对应shouldShowRequestPermissionRationale(),当应用之前请求过此权限但用户拒绝了请求,再次请求时调用
@OnPermissionDenied   当请求权限遭拒绝时调用
@OnNeverAskAgain   当用户勾选不再提示,并拒绝权限时,再次请求时调用
步骤(以使用相机为例)
  • 还是在清单文件中声明使用的权限
<uses-permission android:name="android.permission.CAMERA" />
  • 配置依赖 PermissionsDispatcher,这里不再赘述
  • 代码示例
@RuntimePermissions
public class PermissionsDispatcherActivity extends AppCompatActivity {

    private ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imageView = (ImageView) findViewById(R.id.imageView);

        findViewById(R.id.btn_camera).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PermissionsDispatcherActivityPermissionsDispatcher.takePhotoWithCheck(PermissionsDispatcherActivity.this);
            }
        });

    }

    @NeedsPermission(Manifest.permission.CAMERA)
    void takePhoto() {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);// 启动系统相机
        startActivityForResult(intent, 100);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) { // 如果返回数据
            if (requestCode == 100) { // 判断请求码是否为REQUEST_CAMERA,如果是代表是这个页面传过去的,需要进行获取
                Bundle bundle = data.getExtras(); // 从data中取出传递回来缩略图的信息,图片质量差,适合传递小图片
                Bitmap bitmap = (Bitmap) bundle.get("data"); // 将data中的信息流解析为Bitmap类型
                imageView.setImageBitmap(bitmap);// 显示图片
            }
        }
    }

    @OnShowRationale(Manifest.permission.CAMERA)
    void showRationaleForRecord(final PermissionRequest request) {
        new AlertDialog.Builder(this)
                .setPositiveButton("好的", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.proceed();
                    }
                })
                .setNegativeButton("不给", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.cancel();
                    }
                })
                .setCancelable(false)
                .setMessage("拍照需要相机权限,应用将要申请使用相机权限")
                .show();
    }

    @OnPermissionDenied(Manifest.permission.CAMERA)
    void showCameraDenied() {
        Toast.makeText(getApplicationContext(), "权限被拒绝", Toast.LENGTH_LONG).show();
    }

    @OnNeverAskAgain(Manifest.permission.CAMERA)
    void onRCameraNeverAskAgain() {
        new AlertDialog.Builder(this)
                .setPositiveButton("好的", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // 打开系统应用设置
                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                        intent.setData(Uri.parse("package:" + getPackageName()));
                        intent.addCategory(Intent.CATEGORY_DEFAULT);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(intent);
                        dialog.cancel();
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                    }
                })
                .setCancelable(false)
                .setMessage("您已经禁止了相机权限,是否现在去开启")
                .show();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        PermissionsDispatcherActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
    }
}
使用注意
  • 注解的方法不能是private
  • 在同一 Activity/Fragment 中可以多次使用以上注解,但是同一组权限处理中注解的value的值应该相同。
  • AS 中可以配合 PermissionsDispatcher plugin 插件一起使用。

总结与建议

  1. 请求权限显示的是标准Android对话框,我们不能自定义。
  2. targetSdkVersion 设置为 22 或更低版本只是权宜之计。作为App开发者,需要尽快适配新权限机制。
  3. 在某个功能模块严重依赖某些权限的情况下,为了减少程序中出现过多权限检查,可以在该模块入口处统一检查,如果没有授予相应权限,则不提供该模块使用。

猜你喜欢

转载自blog.csdn.net/LucasXu01/article/details/80860070