Android Runtime Permission

本文详细介绍了Android 6.0中引入的运行时权限管理机制,包括如何在应用中请求和处理权限,特别是对于敏感权限如摄像头、联系人等的处理流程。同时,还提供了一个第三方库PermissionsDispatcher的使用指南,简化权限处理过程。

参考资料

Android 6.0 新特性

http://www.jianshu.com/p/d299f22dfbdb
http://blog.youkuaiyun.com/qq_34767498/article/details/51814669

Android Runtime Permission

Android 6.0(API 级别 23) 引入了一种新的权限模式,用户可以在运行时对管理应用权限。有点类似 iOS 。即使你的 APP 不去适配 Android 6.0,但是官方建议还是使用新的权限 API 去开发你的应用。

大致的使用流程如下:

  1. 在 AndroidManifest.xml 申明权限,添加 uses-permission ,这一步和以前的权限申请一致

  2. 检查权限,ContextCompat.checkSelfPermission() 方法。如果应用具有此权限,方法将返回 PackageManager.PERMISSION_GRANTED,并且应用可以继续操作。如果应用不具有此权限,方法将返回 PERMISSION_DENIED,且应用必须明确向用户要求权限。比如
    int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
    Manifest.permission.WRITE_CALENDAR);

  3. 请求权限,如果你的 APP 权限里有危险的权限(拍照,短信,麦克风),那么必须要要让用户授予权限,调用 shouldShowRequestPermissionRationale() 会弹出对话框让用户选择,这个对话框是不是能修改的! 来看下大致的代码:

// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
                Manifest.permission.READ_CONTACTS)
        != PackageManager.PERMISSION_GRANTED) {

    // Should we show an explanation?
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
            Manifest.permission.READ_CONTACTS)) {

        // Show an expanation to the user *asynchronously* -- don't block
        // this thread waiting for the user's response! After the user
        // sees the explanation, try again to request the permission.

    } else {

        // No explanation needed, we can request the permission.

        ActivityCompat.requestPermissions(thisActivity,
                new String[]{Manifest.permission.READ_CONTACTS},
                MY_PERMISSIONS_REQUEST_READ_CONTACTS);

        // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
        // app-defined int constant. The callback method gets the
        // result of the request.
    }
}

4 . 处理权限请求回调,看代码

@Override
public void onRequestPermissionsResult(int requestCode,
        String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                // permission was granted, yay! Do the
                // contacts-related task you need to do.

            } else {

                // permission denied, boo! Disable the
                // functionality that depends on this permission.
            }
            return;
        }

        // other 'case' lines to check for other
        // permissions this app might request
    }
}

大致的流程就是上面的几步,怎么区分你的权限是否需要用户授权,可以查看https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous

当然也可以使用命令 adb shell pm list permissions -d -g 查看Dangerous Permission

下面是完整的代码,检测相机的权限,原生的 permission 实现

public class PermissionActivity extends AppCompatActivity {

    private String sdPath;//SD卡的路径
    private String picPath;//图片存储路径

    public static final int REQUEST_CUST_CAMERA = 0X12;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_permission);
        findViewById(R.id.readSDCard).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                checkPermission(Manifest.permission.CAMERA);
            }
        });
    }


    // 原生的permission check
    public void checkPermission(String permission) {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {

            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
                // 这里很奇怪,基本进不到这步
                Toast.makeText(this, "show permission detail", Toast.LENGTH_SHORT).show();

            } else {
                ActivityCompat.requestPermissions(this, new String[]{permission}, REQUEST_CUST_CAMERA);

            }
        } else {
            showReadFile();
        }
    }

    void showReadFile() {
        sdPath = Environment.getExternalStorageDirectory().getPath();
        picPath = sdPath + "/" + "temp.png";

        Intent intent2 = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        Uri uri = Uri.fromFile(new File(picPath));
        //为拍摄的图片指定一个存储的路径
        intent2.putExtra(MediaStore.EXTRA_OUTPUT, uri);
        startActivityForResult(intent2, 0x12);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CUST_CAMERA) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                showReadFile();
            } else {
                //用户取消了授权
                //用户在安全中心关闭了权限(测试的小米手机)
                Toast.makeText(PermissionActivity.this, "permission denied", Toast.LENGTH_SHORT).show();
                //如果实在很重要,可以引导用户去设置界面,开启
            }
        }
    }
}

## 下面来介绍下第三方的库: PermissionsDispatcher 这个库有以下的特点:

  • 100% 自由反射
  • 支持特殊的权限
  • 支持小米
  • 支持 kotlin

首先来看下怎么使用

1. 下载

dependencies {
    // 这里注意替换掉 {latest.version}  去github 查询最新的版本号,自行替换
  compile("com.github.hotchemi:permissionsdispatcher:${latest.version}") {
      // if you don't use android.app.Fragment you can exclude support for them
      exclude module: "support-v13"
  }
  annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:${latest.version}"
}
    repositories {
        jcenter()
        // 项目根目录的 build.gradle repositories 添加 maven 支持
        maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local/' }

    }

添加好后,build 下就可以下载该库了

2. AndroidManifest.xml 申明权限,添加 uses-permission ,这一步和以前的权限申请一致

    // 比如
    <uses-permission android:name="android.permission.CAMERA" />

3. 在相应的页面添加下面的代码,看注释

// 这里注解是必须的, 表明这个Activity 要申请权限, 可以在 Activity 和 Fragment 上使用这个注解
@RuntimePermissions
public class PermissionActivity extends AppCompatActivity {

    private String sdPath;//SD卡的路径
    private String picPath;//图片存储路径
    public static final int REQUEST_CUST_CAMERA = 0X12;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_permission);
        findViewById(R.id.readSDCard).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
 // PermissionActivityPermissionsDispatcher 这个类是由库自动生成的,一个封装好权限检测的类,其实就是在当前类的后面加上PermissionsDispatcher,所以初学者千万不要照抄这里
 // showCameraWithCheck 则是根据NeedsPermission 注解生成的,可能有点不好理解,具体看 showCamera()     
        PermissionActivityPermissionsDispatcher.showCameraWithCheck(PermissionActivity.this);
            }
        });
    }

    // 这个注解也是必须的,指的是请求权限的方法,后面跟上对应的权限
    // 这个注解修饰方法会在PermissionsDispatcher 类生成 (方法名+WithCheck)的方法,看上面的showCameraWithCheck
    @NeedsPermission(Manifest.permission.CAMERA)
    void showCamera() {
        sdPath = Environment.getExternalStorageDirectory().getPath();
        picPath = sdPath + "/" + "temp.png";

        Intent intent2 = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        Uri uri = Uri.fromFile(new File(picPath));
        //为拍摄的图片指定一个存储的路径
        intent2.putExtra(MediaStore.EXTRA_OUTPUT, uri);
        startActivityForResult(intent2, 0x12);
    }

    // 这里和原生的一致
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        // 调用 PermissionActivityPermissionsDispatcher 的静态方法,然后会回调 @NeedsPermission,@OnPermissionDenied 和 @OnNeverAskAgain,分别对应为 用户通过了权限,权限不通过和 用户点了不再询问。
        PermissionActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
    }

    // 权限不通过 这个注解也是必须的
    @OnPermissionDenied(Manifest.permission.CAMERA)
    void showDeniedForRead() {
        Toast.makeText(this, "showDeniedForRead", Toast.LENGTH_SHORT).show();
    }
    // 用户点了不再询问  这个注解也是必须的
    @OnNeverAskAgain(Manifest.permission.CAMERA)
    void showNeverAskForRead() {
        Toast.makeText(this, "showNeverAskForRead", Toast.LENGTH_SHORT).show();
    }




}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值