android 权限申请

android6.0之后,系统对用户的安全性也越来越重视了,今天就来好好整理一下android6.0之后对权限申请的相关知识点。

一 .前言

(1).由于MIUI等部分国产定制系统也有权限管理,没有相关api,故无法判断用户是否允许获取联系人等隐私。在Android 6.0之后,新增权限管理可以通过官方api判断用户的运行状态;

(2).我们指定targetSdkVersion为23或者之后我们还需要在运行时请求这些所需的权限。这很重要,因为已经出现了很多开发者把targetSdkVersion飙到了最新,然后发现自己的app疯狂的崩溃,这是由于他们没有实现执行运行时权限请求的代码。当你已经把一个targeting API 为23或者之后的app发布到了Google Play上,这更是一个问题,你无法立即把那个apk的targeting API替换成更早的版本。

二.权限分析

从Android6.0开始,权限分为普通权限和许可权限。许可权限分类归组,一个权限授权之后,该组下的权限均可使用。

(1)普通权限

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

(2)许可权限

Permission GroupPermissions
android.permission-group.CALENDAR
  • android.permission.READ_CALENDAR
  • android.permission.WRITE_CALENDAR
android.permission-group.CAMERA
  • android.permission.CAMERA
android.permission-group.CONTACTS
  • android.permission.READ_CONTACTS
  • android.permission.WRITE_CONTACTS
  • android.permission.GET_ACCOUNTS
android.permission-group.LOCATION
  • android.permission.ACCESS_FINE_LOCATION
  • android.permission.ACCESS_COARSE_LOCATION
android.permission-group.MICROPHONE
  • android.permission.RECORD_AUDIO
android.permission-group.PHONE
  • android.permission.READ_PHONE_STATE
  • android.permission.CALL_PHONE
  • android.permission.READ_CALL_LOG
  • android.permission.WRITE_CALL_LOG
  • com.android.voicemail.permission.ADD_VOICEMAIL
  • android.permission.USE_SIP
  • android.permission.PROCESS_OUTGOING_CALLS
android.permission-group.SENSORS
  • android.permission.BODY_SENSORS
android.permission-group.SMS
  • android.permission.SEND_SMS
  • android.permission.RECEIVE_SMS
  • android.permission.READ_SMS
  • android.permission.RECEIVE_WAP_PUSH
  • android.permission.RECEIVE_MMS
  • android.permission.READ_CELL_BROADCASTS
android.permission-group.STORAGE
  • android.permission.READ_EXTERNAL_STORAGE
  • android.permission.WRITE_EXTERNAL_STORAGE

同一组的任何一个权限被授权了,其他权限也自动被授权。例如,一旦WRITE_CONTACTS被授权了,app也有READ_CONTACTS和GET_ACCOUNTS了。
源码中被用来检查和请求权限的方法分别是Activity的checkSelfPermission和requestPermissions,这些方法api23引入。

(3)相关方法

(1).ContextCompat.checkSelfPermission()

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

(2).ActivityCompat.requestPermissions()

将弹出请求授权对话框,这个方法在M之前版本调用,OnRequestPermissionsResultCallback 直接被调用,带着正确的 PERMISSION_GRANTED或者 PERMISSION_DENIED 。

(3).AppCompatActivity.onRequestPermissionsResult()

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

//版本判断  
if (Build.VERSION.SDK_INT >= 23) {  
    //减少是否拥有权限  
    int checkCallPhonePermission = ContextCompat.checkSelfPermission(getApplicationContext(), permission);  
    if (checkCallPhonePermission != PackageManager.PERMISSION_GRANTED) {  
        //弹出对话框接收权限  
        ActivityCompat.requestPermissions(BaseActivity.this, new String[]{permission}, id);  
        return;  
}  

 

@Override  
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {  
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);  
  
    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {  
        //TODO:已授权  
    } else {  
       //TODO:用户拒绝  
    }  
}  
public class BaseActivity extends AppCompatActivity {  
    private Map<Integer, Runnable> allowablePermissionRunnables = new HashMap<>();  
    private Map<Integer, Runnable> disallowablePermissionRunnables = new HashMap<>();  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
    }  
  
    /** 
     * 请求权限 
     * @param id 请求授权的id 唯一标识即可 
     * @param permission 请求的权限 
     * @param allowableRunnable 同意授权后的操作 
     * @param disallowableRunnable 禁止权限后的操作 
     */  
    protected void requestPermission(int id, String permission, Runnable allowableRunnable, Runnable disallowableRunnable) {  
        if (allowableRunnable == null) {  
            throw new IllegalArgumentException("allowableRunnable == null");  
        }  
  
        allowablePermissionRunnables.put(id, allowableRunnable);  
        if (disallowableRunnable != null) {  
            disallowablePermissionRunnables.put(id, disallowableRunnable);  
        }  
  
        //版本判断  
        if (Build.VERSION.SDK_INT >= 23) {  
            //减少是否拥有权限  
            int checkCallPhonePermission = ContextCompat.checkSelfPermission(getApplicationContext(), permission);  
            if (checkCallPhonePermission != PackageManager.PERMISSION_GRANTED) {  
                //弹出对话框接收权限  
                ActivityCompat.requestPermissions(BaseActivity.this, new String[]{permission}, id);  
                return;  
            } else {  
                allowableRunnable.run();  
            }  
        } else {  
            allowableRunnable.run();  
        }  
    }  
  
    @Override  
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {  
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);  
  
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {  
            Runnable allowRun = allowablePermissionRunnables.get(requestCode);  
            allowRun.run();  
        } else {  
            Runnable disallowRun = disallowablePermissionRunnables.get(requestCode);  
            disallowRun.run();  
        }  
    }  
}  

 

public class MainActivity extends BaseActivity implements View.OnClickListener{  
    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.call_phone);  
        btContact = (Button) findViewById(R.id.contact);  
  
        btCallPhone.setOnClickListener(this);  
        btContact.setOnClickListener(this);  
    }  
  
    @Override  
    public void onClick(View v) {  
        if(v == btCallPhone){  
            //拨打电话  
            requestPermission(1, Manifest.permission.CALL_PHONE, new Runnable() {  
                @Override  
                public void run() {  
                    callPhone();  
                }  
            }, new Runnable() {  
                @Override  
                public void run() {  
                    callPhoneDenied();  
                }  
            });  
        }else if(v == btContact){  
            //读取联系人信息  
            requestPermission(2, Manifest.permission.WRITE_CONTACTS, new Runnable() {  
                @Override  
                public void run() {  
                    readContact();  
                }  
            }, new Runnable() {  
                @Override  
                public void run() {  
                    readContactDenied();  
                }  
            });  
        }  
    }  
  
    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();  
    }  
}  

 

以上是对权限的基本用法及相关封装代码。

/*********************************************************************************************************************************/

有一些牛人对动态添加权限封装成了框架:

一、一句代码搞定权限申请问题。

框架集成:

dependencies {
    implementation 'com.hjq:xxpermissions:5.2'
}
XXPermissions.with(this)
        //.constantRequest() //可设置被拒绝后继续申请,直到用户授权或者永久拒绝
        //.permission(Permission.SYSTEM_ALERT_WINDOW, Permission.REQUEST_INSTALL_PACKAGES) //支持请求6.0悬浮窗权限8.0请求安装权限
        .permission(Permission.Group.STORAGE, Permission.Group.CALENDAR) //不指定权限则自动获取清单中的危险权限
        .request(new OnPermission() {

            @Override
            public void hasPermission(List<String> granted, boolean isAll) {
                
            }

            @Override
            public void noPermission(List<String> denied, boolean quick) {
                
            }
        });

 是否有这个权限

if (XXPermissions.isHasPermission(this, Permission.Group.STORAGE)) {
    
}

跳转到设置页面

如果用户选择永久拒绝该权限后,此时要想打开此权限,需要用户到设置页面手动打开权限。

XXPermissions.gotoPermissionSettings(this);

框架亮点

  • 简洁易用,采用链式调用的方式,使用只需一句代码

  • 支持单个权限、多个权限、单个权限组、多个权限组请求

  • 不指定权限则自动获取清单文件上的危险权限进行申请

  • 如果动态申请的权限没有在清单文件中注册会抛出异常

  • 支持大部分国产手机直接跳转到具体的权限设置页面

  • 可设置被拒绝后继续申请,直到用户授权或者永久拒绝

  • 支持请求6.0及以上的悬浮窗权限和8.0及以上的安装权限

  • 本框架不依赖AppCompatSupport库,兼容Eclipse和Studio

 混淆规则

-dontwarn com.hjq.permissions.**

 常见问题

我们都知道,如果用户全部授予只会调用hasPermission方法,如果用户全部拒绝只会调用noPermission方法。

但是还有一种情况,如果在请求多种权限的情况下,这些权限不是被全部授予或者全部拒绝了,而是部分授权部分拒绝这种情况,框架会如何处理回调呢?

框架会先调用noPermission方法,再调用hasPermission方法。其中我们可以通过hasPermission方法中的isAll参数来判断权限是否全部授予了。如果想知道回调中的某个权限是否被授权或者拒绝,可以调用List集合类中的contains(Permission.XXX)方法来判断这个集合中是否包含了这个权限。

 

这是基本的用法,如果要对此了解更加深入,请自行百度。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值