Android 6.0新特性之运行时权限

转载请注明出处:http://blog.csdn.net/demokui/article/details/54376634

一、Android 6.0 (API_23)简介

首先先给出一张相关文档截图:

可以看的出Google在6.0上的新增功能还是不少的,但对于咱们开发小伙伴来说最为关注的就是文档中列举的“更完整的应用权限管理”,那么今天就记录一下之前学习Android6.0运行时权限的重要实现过程。(高手勿喷,欢迎指正,谢谢。。。)

运行时权限

做Android的伙伴应该都知道Google将android的权限分为三种

  • 普通权限
    这种权限是程序中常用的且对用户造成不了任何隐私泄露的权限,我们不用太在意。
  • 危险权限
    这种权限见名知意,是在用户使用app时,要明确提示用户,让用户有知情权(算一种android的安全机制)。今天的主角。
  • 特殊权限
    这种权限几乎用不到,不用管。

这里说一下:Android app在6.0之前使用危险权限时,直接在Manifest文件中配置即可,完了安装程序时系统会将该app中使用到的所有危险权限罗列出来告知用户,此处只能同意所有权限才能安装程序,否则程序无法安装,后来可能Google觉得这样不妥,应该让用户先安装程序,然后自己管理自己的权限,然而就在2015-05-28的Google I/O大会上就将该功能正式发布了,只有用户在执行某个操作涉及到危险权限时系统才会给出相关授权提示,从而达到更好的用户体验。

二、运行时权限详解

1、Android 6.0权限的使用

放在最前面:6.0 权限使用步骤,第一步:在Manifest文件中配置;第二步:执行以下操作。

(1)相关API

  • ContextCompat.checkSelfPermission
    这个API用来检测该权限是否被授权,返回结果如下:
    授权:PackageManager.PERMISSION_GRANTED
    未授权:PackageManager.PERMISSION_DENIED
  • ActivityCompat.requestPermissions
    申请权限API,当权限未授权时会调用该API。
  • ActivityCompat.shouldShowRequestPermissionRationale
    这个API是给用户解释再次弹出提示框的原因,只有当用户第一次点了拒绝授权时,再次执行那个操作时就会调用这个API。
  • onRequestPermissionsResult
    这个是申请权限的回调方法(Activity和Fragment),申请权限结果的信息都在这个方法里面,也就是我们申请完权限后下一步要执行的操作逻辑就在此编写。

以上是运行时权限涉及到的所有API,相关请求参数以及回调参数详解在下文中的代码会详细给出。可以看得的出API很少,那么实现起来也不会很吃力。

(2)判断权限是否授权以及申请权限

 if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
                        != PackageManager.PERMISSION_GRANTED) {
      Log.e(TAG, "检查出电话权限没有授权");
      //申请权限,参数1:当前上下文对象  参数2:要申请的权限数组集  参数3:请求码
      ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, 0);
 } else {
      Log.e(TAG, "检查出电话权限已经授权");
      // 打电话
      playCall();
 }

(3)处理申请结果

    /**
     * 申请权限的回调
     *
     * @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 0: // 请求码如果是刚刚设置的“0”
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 授权
               //打电话
               playCall();
            } else {        // 未授权
           // 给出用户提示   
            }
              break;
        }

}

(4)拒绝权限解释

if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.CALL_PHONE)) {
    // 不用做什么处理
     Log.e(TAG, "点击了拒绝按钮");
} else {  // 打钩不再提示并点击了拒绝授权
     if (isFirst) { // 第一次点击拒绝授权
          Log.e(TAG, "点击了不在提示按钮");
          isFirst = !isFirst;
     } else {  // 非第一次点击拒绝授权
          Log.e(TAG, "去设置里面手动开启权限");
     }
}

(5)简单的示例Demo

切记在Manifest文件中也要配置相应权限,这里使用拨打权限作为示例开发。代码如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button mCall;
    private boolean isFirst = true; // 是否是第一次点击了不再提示授权信息

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化UI
        initView();

    }

    private void initView() {
        mCall = (Button) findViewById(R.id.id_call);
        mCall.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.id_call:

                if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
                        != PackageManager.PERMISSION_GRANTED) {
                    Log.e(TAG, "检查出电话权限没有授权");
                    //申请权限,参数1:当前上下文对象  参数2:要申请的权限数组集  参数3:请求码
                    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, 0);
                } else {
                    Log.e(TAG, "检查出电话权限已经授权");
                    // 打电话
                    playCall();
                }

                break;

        }
    }

    /**
     * 申请权限的回调
     *
     * @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 0:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 授权
                    Log.e(TAG, "点击了允许按钮");
                    playCall();
                } else {        // 未授权
                    if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                            Manifest.permission.CALL_PHONE)) {
                        // 不用做什么处理
                        Log.e(TAG, "点击了拒绝按钮");
                    } else {  // 打钩不再提示并点击了拒绝授权
                        if (isFirst) { // 第一次点击拒绝授权
                            Log.e(TAG, "点击了不在提示按钮");
                            isFirst = !isFirst;
                        } else {  // 非第一次点击拒绝授权
                            Log.e(TAG, "去设置里面手动开启权限");
                        }
                    }
                }
                break;
        }

    }

    private void playCall() {
        Intent intent = new Intent(Intent.ACTION_CALL);
        Uri data = Uri.parse("tel:" + "10086");
        intent.setData(data);
        startActivity(intent);  // 小小提示:这里IDE会报红,不过可以编译也可以运行,是google设计的。属于正常情况
    }
}

原谅我没有录屏(根本不会啊!目前还没研究怎么录屏,,,)相关截图如下:
乐视X820手机第一次申请授权:

当乐视第一次点击拒绝按钮就会显示出不再提示选项:

这里写图片描述

这里给出测试时log日志:

这里写图片描述

同时我也拿华为mate8手机试了一下,由于手机系统定制的原因UI长的不太一样,但功能一模一样,但是我在国民手机小米MAX机型演示时出了点问题:当用户第一次点击拒绝授权后第二次申请授权时不再弹出提示框,后来经过log日志分析出来,原因是小米MAX这款手机拒绝 = 不再提示,也就是说当点击了拒绝按钮后,只能去设置里面手动开启权限了。(本人就测试了这三款机型,均是真机测试,并且出厂时就是6.0以上的系统,而不是从低版本升级上来的系统,郭神说升级上来的系统都已经默认授权了,我没试过。。。有兴趣的朋友可以试一下)

2、Android 6.0权限的封装

基于郭霖大神2016年12月27日在csdn直播Android 6.0运行时权限思路封装(纯属搬运工,不过学习这种封装思路很重要,并且动手实现也很重要。。。)

猛戳郭霖csdn直播视频
封装思路如下:

1.请求申请授权的第一个参数:当前视图的上下文对象,那么我们必须在工程的任何地方都应该能获取到当前的上下文对象。(Activity任务栈可以获取最顶层的视图,而最顶层的视图肯定是当前的Activity实例对象)所以代码编写如下:

    public class ActivityCollector {
    private static List<Activity> activityList = new ArrayList<>();

    public static void addActivity(Activity activity) {
        activityList.add(activity);
    }

    public static void removeActivity(Activity activity) {
        activityList.remove(activity);
    }

    public static Activity getTopActivity() {
        if (activityList.isEmpty()) {
            return null;
        } else {
            return activityList.get(activityList.size() - 1);
        }
    }
 }

2.请求申请授权的第二个参数:需要申请的权限数组集,这个只有子类自己才知道自己想要申请什么权限,那么这个参数必须由子类传入,第三个参数可自行定义。因此我们可以在我们项目中的BaseActivity中对外公开一个这样的静态方法(静态是为了在非视图下直接调用该方法申请权限),如果申请时用户已经申请了该权限,那么我们还要对此逻辑做出处理,而这一块的逻辑也只有子类自己知道要做什么,所以我们可以定义一个接口,让子类去实现该接口完成相应逻辑处理,代码如下:

  • 定义的接口 PermissionListener:
public interface PermissionListener {
    // 已授权
    void onGranted(); 
    // 未授权    参数说明:用来提示用户某个权限授权失败的信息
    void onDenied(List<String> deniedPermission);   
}
  • BaseActivity中对外公布的静态方法 requestRuntimePermission:
public static void requestRuntimePermission(String[] permissions, PermissionListener listener) {
    // 获取Activity任务栈中最顶层视图
        Activity topActivity = ActivityCollector.getTopActivity();
        if (topActivity == null) {
            return;
        }
        // 定义全局的请求结果监听接口,下文请求结果回调中也要用到
        mListener = listener;
        // 创建一个集合用来存储未授权的权限
        List<String> permissionList = new ArrayList<>();
        // 遍历子类请求的所有权限判断是否已经授权
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(topActivity, permission) != PackageManager.PERMISSION_GRANTED) {
                // 如果没有授权则加入 permissionList 集合中
                permissionList.add(permission);
            }
        }
        // 如果permissionList不为空,说明有某个权限没被授权,那么去做申请权限处理逻辑
        if (!permissionList.isEmpty()) {
            ActivityCompat.requestPermissions(topActivity, permissionList.toArray(new String[permissionList.size()]), 1);
        } else {
        // 如果都已经授权,交由子类去处理
            mListener.onGranted();
        }
    }
  • BaseActivity中对申请权限结果的回调 onRequestPermissionsResult:
@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) {
                // 创建一个deniedPermission 集合,用来封装未被授权成功的权限
                    List<String> deniedPermission = new ArrayList<>();
                    // 遍历授权结果获取每个申请的权限以及每个权限的申请结果值
                    for (int i = 0; i < grantResults.length; i++) {
                // 获取每个权限的申请结果值
                        int grantResult = grantResults[i];
                        // 获取申请的每个权限
                        String permission = permissions[i];
                        if (grantResult != PackageManager.PERMISSION_GRANTED) {
                            // 如果没有授权则加入deniedPermission集合
                            deniedPermission.add(permission);
                        }
                    }
                    // deniedPermission集合为空,则说明所有权限均被授权
                    if (deniedPermission.isEmpty()) {
                        // 全部授权,交由子类实现下一步操作逻辑
                        mListener.onGranted();
                    } else {
                    // 某个权限未被授权,交由子类实现下一步操作逻辑
                    mListener.onDenied(deniedPermission);
                    }
                }
            default:
                break;
        }

    }

然后在BaseActivity中的onCreate()和onDestroy()添加任务栈和移除任务栈:

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }

封装到这里就结束。下面给出调用实现实例:

  • Activity中调用:
public class MainActivity extends BaseActivity {

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

        Button mButton= (Button) findViewById(R.id.id_call);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
        // 这里直接调用父类的方法即可
                requestRuntimePermission(new String[]{Manifest.permission.CALL_PHONE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_CONTACTS}, new PermissionListener() {
                    @Override
                    public void onGranted() {
                        Toast.makeText(MainActivity.this,"所有权限都同意了",Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onDenied(List<String> deniedPermission) {
                        for(String persmission:deniedPermission){
                            Toast.makeText(MainActivity.this,"被拒绝的权限:"+persmission,Toast.LENGTH_SHORT).show();
                        }

                    }
                });
            }
        });
    }
}
  • 非Activity中调用:
public class Test {

    public void text(){
    // 调用BaseActivity的静态方法(现在知道为什么要写成静态了吧)
        BaseActivity.requestRuntimePermission(new String[]{},new PermissionListener(){
            @Override
            public void onGranted() {

            }

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

            }
        });
    }
}

好了,封装结束。哦,不对,有同学在郭神直播完后的问答环节问了一个重要的问题:在Test中这样使用会不会导致内存泄漏?,郭神的回答是很有可能。因为这里一直获取的我们保存的Activity的实例对象的最顶层的一个。这里会存在软引用,导致内存泄漏,这位提问的同学很是牛逼啊,,,不过郭神重要讲的是这种封装思路,简单易懂。大家可以以后遇到封装可以套用该思路,举一反三。(如果在非视图下申请权限那就不用这个方法了啊,直接弹出一个透明的Activity就好啦。。。。)

三、开源库Android 6.0 运行时权限列举

1、RxPermission (基于RxJava思想封装)

https://github.com/tbruyelle/RxPermissions

2、PermissionGen(具体讲解可以查看鸿洋博客)

https://github.com/lovedise/PermissionGen

鸿神对PermissionGen的讲解以及MPermissions的封装

3、MPermissions(张鸿洋)

https://github.com/hongyangAndroid/MPermissions

欢迎大家点赞/评论,更欢迎高手指正,谢谢!!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值