技术联盟

Android动态获取权限

前几天在网上找了找Android动态获取权限的文章和视频,自己整理了一下。几天看一位大神说真正的程序员是有着分享精神的,我这个刚刚入行的小菜鸟,也想把自己整理的东西分享给大家。

本文参考了A_siPermission——郭霖认为最优的运行时权限方案郭霖大神的CSDN视屏

在这之前,我们需要知道什么是权限?

权限是一种安全机制。Android权限主要用于限制应用程序内部某些具有限制性特性的功能使用以及应用程序之间的组件访问。
比如网络权限

<user-permission android:name="android.permission.INTERNET"/>当我们的程序需要访问网络的时候,必须添加这个权限,不添加则无法访问网络。

这里还需要注意的是:在Android6.0之前,我们只需要在AndroidManifest.xml文件中直接添加权限即可,但是在Android6.0之后,我们只在AndroidManifest.xml文件中配置是不够的,还需要在Java代码中进行动态获取权限。当然这里不是所有的权限都需要动态获取,只需要获取危险权限就可以了。(毕竟Android权限100多种,要是每个都需要获取,那不累死开发人员啦)

Android中的危险权限分为9组24种

1454742-2c2196fd438a4ed3.png

我们只需要在用这些权限的时候,才需要去动态申请,除这些之外的权限,我们只需要在AndroidManifest.xml文件种写上即可,动态申请的权限同样需要在AndroidManifest.xml文件中写。而且每一组种的权限只要有一个授权了,其他的也会自动授权

下面这张图是我们的build.gradle文件中的版本信息

Image.png

1、targetSdkVersion版本号如果<= 21时,不需要动态处理权限 。但是有时候我们的应用程序会打不开。
2、如果是之前的老版本升级上来的,系统会默认开启之前所有的权限,比如之前的targetSdkVersion是21,后来升级之后变成了25,这个时候系统会自动授权。一定要记住是升级变成的25,否则还是需要授权。

Tips: 为什么SD卡读写权限是危险权限,能不能不申请权限但又能使用SD卡呢?
(1)把SD权限设置为危险权限,是为了防止应用随便在用户的手机上些东西,如果不加防范,用户的SD卡就会有很多乱七八糟的东西。
(2)用户可以不申请权限而使用SD卡,但是只能使用系统提供的默认的路径。
我们可以使用Android系统提供的SDK目录。路径是Android\data\程序包名。这个目录是程序可以不需要经过
用户授权就可以直接使用,甚至不需要添加WRITE_EXTERNAL_STORAGE这个权限。 这个目录会在程序删除时也会直接删除。

如何访问这个目录呢?
File file = getExternalCacheDir();
String s = file.getPath; 这个方法得到的是cache的路径,但是一般的垃圾清理软件都可以把这里面的数据清理掉
File[] files = getExternalCacheDirs(); 这个得到的是一个文件数组,要求Api最小19

如果我们不想垃圾清理软件把我们缓存的数据清理掉,则可以放在file文件下
getExternalFilesDir(“”); 这个表示所有的操作都是放在file目录下进行的
getExternalFilesDir(“mm”); 这个表示会在file目录下在见一个mm目录,然后所有的操作都会放在这个目录下。

下面我们来看如何动态获取权限!!!

先看最简单的,动态获取一个权限

比如现在要动态获取一个打电话的权限。。
首先需要在AndroidManifest.xml文件中写上这个权限

<user-permission android:name="android.premission.CALL_PHONE"/>
然后在Java代码进行动态获取
请求授权的代码

 /**
     * 请求授权
     */
    private void requestPermission(){

        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){ //表示未授权时
            //进行授权
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CALL_PHONE},1);
        }else{
            //调用打电话的方法
            makeCall();
        }
    }

请求之后,我们需要重写onRequestPermissionsResult这个方法

/**
 * 权限申请返回结果
 * @param requestCode 请求码
 * @param permissions 权限数组
 * @param grantResults  申请结果数组,里面都是int类型的数
 */
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case 1:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ //同意权限申请
                makeCall();
            }else { //拒绝权限申请
                Toast.makeText(this,"权限被拒绝了",Toast.LENGTH_SHORT).show();
            }
            break;
        default:
            break;
    }
}
/**
 * 打电话方法
 */
private void makeCall(){
    try {
        Intent intent = new Intent(Intent.ACTION_CALL);
        intent.setData(Uri.parse("tel://123456789"));
        startActivity(intent);
    }catch (SecurityException e){
        e.printStackTrace();
    }
}

上面只是对一个权限进行授权,如果我们需要对多个权限进行授权呢?难道还是这样一个一个写吗?答案是否定的,我们看一下授权申请的方法

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

我们注意到权限的参数是一个String类型的数组,这就说明我们可以直接把所有要授权的权限放在一个数组中,这样就可以对多个权限进行授权了
以打电话和SD卡权限为例

/**
 * 当有多个权限需要申请的时候
 * 这里以打电话和SD卡读写权限为例
 */
private void requestPermissions(){

    List<String> permissionList = new ArrayList<>();
    if (ContextCompat.checkSelfPermission(this,Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){
        permissionList.add(Manifest.permission.CALL_PHONE);
    }

    if (ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
        permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
    }

    if (!permissionList.isEmpty()){  //申请的集合不为空时,表示有需要申请的权限
        ActivityCompat.requestPermissio8ns(this,permissionList.toArray(new String[permissionList.size()]),1);
    }else { //所有的权限都已经授权过了

    }
}

同样需要写onRequestPermissionsResult()方法,这里和单个权限申请的写法有一点区别

/**
 * 权限申请返回结果
 * @param requestCode 请求码
 * @param permissions 权限数组
 * @param grantResults  申请结果数组,里面都是int类型的数
 */
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case 1:
            if (grantResults.length > 0){ //安全写法,如果小于0,肯定会出错了
                for (int i = 0; i < grantResults.length; i++) {

                    int grantResult = grantResults[i];
                    if (grantResult == PackageManager.PERMISSION_DENIED){ //这个是权限拒绝
                        String s = permissions[i];
                        Toast.makeText(this,s+"权限被拒绝了",Toast.LENGTH_SHORT).show();
                    }else{ //授权成功了
                        //do Something
                    }
                }
            }
            break;
        default:
            break;
    }
}

上面两种是单个和多个的权限申请,可是这还有一个问题,就是我们只能在当前类中使用。这个时候,我们需要想到一个封装,然后让所有的类都可以使用。
如何去封装呢? 我们在开发一个项目的时候,都会打一个框架,这里面可定会有各种各样的基类,这个时候我们就可以把权限的申请放在BaseActivity中,然后各个子Activity去继承BaseActivity。
然后通过接口回调的方式把授权结果传给子Activity。

下面来看代码:

首先是定义的接口

/**
 * Created by 11213 on 2017/1/4.
 * 权限回调接口
 */

public interface PermissionListener {

    //授权成功
    void onGranted();

    //授权部分
    void onGranted(List<String> grantedPermission);

    //拒绝授权
    void onDenied(List<String> deniedPermission);
}

BaseActivity的代码

public class BaseActivity1 extends AppCompatActivity {

    private PermissionListener mlistener;

    /**
     * 权限申请
     * @param permissions 待申请的权限集合
     * @param listener  申请结果监听事件
     */
    protected void requestRunTimePermission(String[] permissions,PermissionListener listener){
        this.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()]),1);
        }else{  //为空,则已经全部授权
            listener.onGranted();
        }
    }

    /**
     * 权限申请结果
     * @param requestCode  请求码
     * @param permissions  所有的权限集合
     * @param grantResults 授权结果集合
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0){
                    //被用户拒绝的权限集合
                    List<String> deniedPermissions = new ArrayList<>();
                    //用户通过的权限集合
                    List<String> grantedPermissions = new ArrayList<>();
                    for (int i = 0; i < grantResults.length; i++) {
                        //获取授权结果,这是一个int类型的值
                        int grantResult = grantResults[i];

                        if (grantResult != PackageManager.PERMISSION_GRANTED){ //用户拒绝授权的权限
                            String permission = permissions[i];
                            deniedPermissions.add(permission);
                        }else{  //用户同意的权限
                            String permission = permissions[i];
                            grantedPermissions.add(permission);
                        }
                    }

                    if (deniedPermissions.isEmpty()){  //用户拒绝权限为空
                        mlistener.onGranted();
                    }else {  //不为空
                        //回调授权成功的接口
                        mlistener.onDenied(deniedPermissions);
                        //回调授权失败的接口
                        mlistener.onGranted(grantedPermissions);
                    }
                }
                break;
            default:
                break;
        }
    }
}

子Activity中进行调用

private void requestPermission(){
    requestRunTimePermission(new String[]{Manifest.permission.CALL_PHONE, Manifest.permission.WRITE_EXTERNAL_STORAGE}
            , new PermissionListener() {
                @Override
                public void onGranted() {  //所有权限授权成功

                }

                @Override
                public void onGranted(List<String> grantedPermission) { //授权失败权限集合

                }

                @Override
                public void onDenied(List<String> deniedPermission) { //授权成功权限集合

                }
            });
}

上面的代码注释很详细,就不再解释了

上面的封装,只能用在Activity中,如果要用在非Activity中(在v4包下的Fragment也可以进行授权),比如工具类中使用时,上面的就不适用了,因为我们看到,申请权限的方法必须传递一个Activity对象,为Activity又不可以new。这个时候,我们要想到,Activity管理栈。

代码如下:

接口还是上面的接口。。。

Activity管理栈

/**
 * Created by 11213 on 2017/1/5.
 * Activity管理类
 */

public class ActivityCollector {

    private static List<Activity> activityList = new ArrayList<>();

    /** 添加一个Activity到集合中*/
    public static void addActivity(Activity activity){
        activityList.add(activity);
    }

    /** 从集合中删除一个Activity*/
    public static void removeActivity(Activity activity){
        activityList.remove(activity);
    }

    /**获取Activity栈中的栈顶的Activity
     * 需要注意的是,栈是先进后出,所以最上面的Activity是集合中的最后一个*/
    public static Activity getTopActivity(){
        if (activityList.isEmpty()){  //Activity栈为空
            return null;
        }else {  //不为空时
            return activityList.get(activityList.size() - 1);
        }
    }
}

BaseActivity的代码

public class BaseActivity extends AppCompatActivity {

    private static PermissionListener mlistener;

    /**
     * 创建Activity时加到管理栈中
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
    }

    /**
     * 销毁时从Activity管理栈中移除
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }

    /**
     * 权限申请
     * @param permissions 待申请的权限集合
     * @param listener  申请结果监听事件
     */
    protected static void requestRunTimePermission(String[] permissions,PermissionListener listener){
        mlistener = listener;

        Activity topActivity = ActivityCollector.getTopActivity();
        if (topActivity == null){
            return;
        }

        //用于存放为授权的权限
        List<String> permissionList = new ArrayList<>();
        //遍历传递过来的权限集合
        for (String permission : permissions) {
            //判断是否已经授权
            if (ContextCompat.checkSelfPermission(topActivity,permission) != PackageManager.PERMISSION_GRANTED){
                //未授权,则加入待授权的权限集合中
                permissionList.add(permission);
            }
        }

        //判断集合
        if (!permissionList.isEmpty()){  //如果集合不为空,则需要去授权
            ActivityCompat.requestPermissions(topActivity,permissionList.toArray(new String[permissionList.size()]),1);
        }else{  //为空,则已经全部授权
            listener.onGranted();
        }
    }

    /**
     * 权限申请结果
     * @param requestCode  请求码
     * @param permissions  所有的权限集合
     * @param grantResults 授权结果集合
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0){
                    //被用户拒绝的权限集合
                    List<String> deniedPermissions = new ArrayList<>();
                    //用户通过的权限集合
                    List<String> grantedPermissions = new ArrayList<>();
                    for (int i = 0; i < grantResults.length; i++) {
                        //获取授权结果,这是一个int类型的值
                        int grantResult = grantResults[i];

                        if (grantResult != PackageManager.PERMISSION_GRANTED){ //用户拒绝授权的权限
                            String permission = permissions[i];
                            deniedPermissions.add(permission);
                        }else{  //用户同意的权限
                            String permission = permissions[i];
                            grantedPermissions.add(permission);
                        }
                    }

                    if (deniedPermissions.isEmpty()){  //用户拒绝权限为空
                        mlistener.onGranted();
                    }else {  //不为空
                        //回调授权成功的接口
                        mlistener.onDenied(deniedPermissions);
                        //回调授权失败的接口
                        mlistener.onGranted(grantedPermissions);
                    }
                }
                break;
            default:
                break;
        }
    }
}

调用方法

public class Util {

    private void requestPermission(){
        BaseActivity.requestRunTimePermission(new String[]{Manifest.permission.CALL_PHONE, Manifest.permission.WRITE_EXTERNAL_STORAGE}
                , new PermissionListener() {
                    @Override
                    public void onGranted() {

                    }

                    @Override
                    public void onGranted(List<String> grantedPermission) {

                    }

                    @Override
                    public void onDenied(List<String> deniedPermission) {

                    }
                });
    }
}

后边的这些代码的注释写的很详细,大家都可以看明白。这些可以直接封装到自己的项目中,另外还有一些其他的封装思路,比如郭神说的使用一个透明的Activity。当然我们也可以使用第三方库。

 

官方文档:https://developer.android.com/guide/topics/permissions/requesting.html

Share this:

码字很辛苦,转载请注明来自技术联盟《Android动态获取权限》

评论