Android 动态权限管理

本文介绍了Android 6.0及以上版本的动态权限管理机制,包括普通权限与许可权限的区别,以及如何使用原生方法和RxJava+RxPermissions方案实现运行时权限申请。提供了具体的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android 动态权限管理

Android 6.0(Marshmallow, 软棉花糖,Api 23),权限分为普通权限和许可权限,许可权限分类归组,该组一个权限被许可后,其他的权限均可使用。

基本介绍

  • 普通权限

    只需在xml申请即可,使用方法和6.0之前的一样。应用安装后会默认获得许可。

  • 许可权限

    具体权限分组情况可以使用以下shell命令查看

    adb shell pm list permissions -d -g

    同一组的任何一个权限被授权了,其他权限也自动被授权。例如,一旦WRITE_CONTACTS被授权了,app也有READ_CONTACTS和GET_ACCOUNTS了

  • 相关方法

    1. ContextCompat.checkSelfPermission()

      检查应用是否拥有该权限,若已授权,返回值为PERMISSION_GRANTED,否则返回PERMISSION_DENIED。

    2. ActivityCompat.requestPermissions()

      该方法在M之前版本调用,OnRequestPermissionsResultCallback 直接被调用,带着正确的 PERMISSION_GRANTED或者 PERMISSION_DENIED。

    3. AppCompatActivity.onRequestPermissionsResult()

      该方法类似于Activity的OnActivityResult()的回调方法,主要接收请求授权的返回值。

  • 需运行时动态申请的权限组

    日历 android.permission-group.CALENDAR

    相机 android.permission-group.CAMERA

    联系人 android.permission-group.CONTACTS

    定位 android.permission-group.LOCATION

    耳机 android.permission-group.MICROPHONE

    电话 android.permission-group.PHONE

    传感器 android.permission-group.SENSORS

    短信 android.permission-group.SMS

    存储 android.permission-group.STORAGE

权限适配解决方案

基本方案(原生方法)

原生方案就是采用android系统提供的权限相关的方法来动态的做权限适配处理,是其他解决方案的基础

我这里来测试读取联系人和打电话这两个6.0之后要在运行时赋予的权限。

MainActivity.java文件内容如下:

package com.rainmonth.myapplication;

import android.Manifest;
import android.content.ContentResolver;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Build;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.util.HashMap;
import java.util.Map;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    // 权限允许对应的Map
    private Map<Integer, Runnable> allowPermissionRunnableMap = new HashMap<>();
    // 权限拒绝对应的Map
    private Map<Integer, Runnable> denyPermissionRunnableMap = new HashMap<>();

    private Button btCallPhone;
    private Button btContact;

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

        btCallPhone = (Button) findViewById(R.id.btn_call_phones);
        btContact = (Button) findViewById(R.id.btn_contacts);

        btCallPhone.setOnClickListener(this);
        btContact.setOnClickListener(this);
    }

    /**
     * 请求权限
     *
     * @param requestCode       请求授权的id 唯一标识即可
     * @param requestPermission 请求的权限
     * @param allowRunnable     同意授权后的操作
     * @param denyRunnable      禁止权限后的操作
     */
    protected void requestPermission(int requestCode, String requestPermission, Runnable allowRunnable, Runnable denyRunnable) {
        if (allowRunnable == null) {
            throw new IllegalArgumentException("allowableRunnable == null");
        }

        allowPermissionRunnableMap.put(requestCode, allowRunnable);
        if (denyRunnable != null) {
            denyPermissionRunnableMap.put(requestCode, denyRunnable);
        }

        //版本判断,6.0以上才予以处理
        if (Build.VERSION.SDK_INT >= 23) {
            //检查是否拥有权限
            int checkCallPhonePermission = ContextCompat.checkSelfPermission(getApplicationContext(), requestPermission);
            if (checkCallPhonePermission != PackageManager.PERMISSION_GRANTED) {
                //弹出对话框提示用户赋予该权限
                ActivityCompat.requestPermissions(this, new String[]{requestPermission}, requestCode);
            } else {
                allowRunnable.run();
            }
        } else {
            allowRunnable.run();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        // 其实还可以根据requestCode进行区分处理(不同的权限申请,采用不同的处理方式)
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            Runnable allowRun = allowPermissionRunnableMap.get(requestCode);
            allowRun.run();
        } else {
            Runnable disallowRun = denyPermissionRunnableMap.get(requestCode);
            disallowRun.run();
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_call_phones:
                requestPermission(1, Manifest.permission.CALL_PHONE, new Runnable() {
                    @Override
                    public void run() {
                        callPhone();
                    }
                }, new Runnable() {
                    @Override
                    public void run() {
                        callPhoneDenied();
                    }
                });
                break;
            case R.id.btn_contacts:
                requestPermission(2, Manifest.permission.READ_CONTACTS, new Runnable() {
                    @Override
                    public void run() {
                        readContact();
                    }
                }, new Runnable() {
                    @Override
                    public void run() {
                        readContactDenied();
                    }
                });
                break;
        }
    }

    /**
     * 模拟赋予电话权限后打电话的方法
     */
    private void callPhone() {
        Toast.makeText(MainActivity.this, "CALL_PHONE OK", Toast.LENGTH_SHORT).show();
    }

    /**
     * 模拟拒绝赋予电话权限后处理的方法,一般用来做街面上的兼容处理,以提醒用户去设置相关的权限
     */
    private void callPhoneDenied() {
        Toast.makeText(MainActivity.this, "CALL_PHONE Denied", Toast.LENGTH_SHORT).show();
    }

    /**
     * 模拟赋予联系权限后的方法
     */
    private void readContact() {
        ContentResolver cr = getContentResolver();
        String str[] = {ContactsContract.CommonDataKinds.Phone.CONTACT_ID, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
                ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.PHOTO_ID};
        Cursor cur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, str, null, null, null);
        int count = cur.getCount();
        cur.close();
        Toast.makeText(MainActivity.this, String.format("发现%s条", count), Toast.LENGTH_SHORT).show();
    }

    /**
     * 模拟拒绝赋予联系人权限后处理的方法,一般用来做街面上的兼容处理,以提醒用户去设置相关的权限
     */
    private void readContactDenied() {
        Toast.makeText(MainActivity.this, "Contact Denied", Toast.LENGTH_SHORT).show();
    }
}

activity_main.xml文件如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.rainmonth.myapplication.MainActivity">

    <Button
        android:id="@+id/btn_contacts"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Contacts" />

    <Button
        android:id="@+id/btn_call_phones"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Call PHones" />

</LinearLayout>

权限申请结果的处理主要在onRequestPermissionsResult中,而处理权限申请所用到的两个HashMap则是在requestPermissions就传递过去了。根据requestCode来在map中查找对应的Runnable对象来处理。

RxJava + RxPermissions方案

在了解了RxJava后,若果你看到上述代码就会觉得上面的基本方案肯定能有一种RxJava式的封装来解决(基本方案中其处理核心就是利用系统提供的onRequestPermissionsResult这个回调方法,RxJava可以很好的解决回调问题),没错,RxPermissions就应运而生了。

RxPermissions的源码还没时间具体分析,先看看它的使用方式。

  1. 引入

    添加依赖

    // RxJava 版本小于2.0
    dependencies {
        compile 'com.tbruyelle.rxpermissions:rxpermissions:0.9.0@aar'
    }
    
    // RxJava 版本2.0以上
    dependencies {
        compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.8.2@aar'
    }
  2. 直接申请权限(以Manifest.permission.CAMERA为例)

    RxPermissions.getInstance(this)
       .request(Manifest.permission.CAMERA)
       .subscribe(new Action1<Boolean>() {
           @Override
           public void call(Boolean granted) {
           if (granted) { // 在android 6.0之前会默认返回true
               // 已经获取权限
               String jpgPath = getCacheDir() + "test.jpg";
               takePhotoByPath(jpgPath, 2);
           } else {
               // 未获取权限
               Toast.makeText(MainActivity.this, "您没有授权该权限,请在设置中打开授权", Toast.LENGTH_SHORT).show();
           }
           }
       });
  3. 条件触发获取权限(可以结合RxBinding使用)

    RxView.clicks(findViewById(R.id.request_permission))
       .compose(RxPermissions.getInstance(this).ensure(Manifest.permission.CAMERA))
       .subscribe(new Action1<Boolean>() {
           @Override
           public void call(Boolean granted) {
    
           if (granted) { // 在android 6.0之前会默认返回true
               // 已经获取权限
               String jpgPath = getCacheDir() + "test.jpg";
               takePhotoByPath(jpgPath, 2);
           } else {
               // 未获取权限
               Toast.makeText(MainActivity.this, "您没有授权该权限,请在设置中打开授权", Toast.LENGTH_SHORT).show();
           }
           }
       });
  4. 同时请求多个权限(合并结果)

    以同时申请拍照和录音权限,代码如下:

    RxPermissions.getInstance(MainActivity.this).request(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
       .subscribe(new Action1<Boolean>() {
           @Override
           public void call(Boolean granted) {
           if (granted) { // 在android 6.0之前会默认返回true
               // 已经获取权限
               String jpgPath = getCacheDir() + "test.jpg";
               takePhotoByPath(jpgPath, 2);
           } else {
               // 未获取权限
               Toast.makeText(MainActivity.this, "您没有授权该权限,请在设置中打开授权", Toast.LENGTH_SHORT).show();
           }
           }
       });

    上面这种请求权限方式,只有所有的权限请求都同意之后,才会返回true,有一个为false,则返回false

  5. 同时获取多个权限(分别获取结果)

    RxPermissions.getInstance(MainActivity.this).requestEach(Manifest.permission.CAMERA,
           Manifest.permission.RECORD_AUDIO)
           .subscribe(new Action1<Permission>() {
               @Override
               public void call(Permission permission) {
               if (permission.name.equals(Manifest.permission.CAMERA)) {
                   if (permission.granted) {
                   String jpgPath = getCacheDir() + "test.jpg";
                   takePhotoByPath(jpgPath, 2);
                   } else {
                   //  未获取权限
                   Toast.makeText(MainActivity.this, "您没有授权该权限,请在设置中打开授权", Toast.LENGTH_SHORT).show();
                   }
    
               } else if (permission.name.equals(Manifest.permission.RECORD_AUDIO)) {
    
               }
               }
           });

RxJava的这种链式写法很好的应用在权限请求上了,哈哈!

其他与权限申请相关的项目

PermissionsDispatcher

使用标注的方式,动态生成类处理运行时权限,目前还不支持嵌套Fragment。

Grant

简化运行时权限处理

android-RuntimePermissions

Google官方的例子

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值