运行时权限

在Android开发中经常需要各种各样的权限,通常我们只需要在AndroidManifest.xml中声明一下需要的权限即可。但是自从Android6.0(即 API Level 23)开始,Android系统将权限的授予从安装时,变成了运行时。这样可以提高APP的安装速度和提高用户对于权限的控制。

Normal And Dangerous

现在我们来看一下这段话:

System permissions are divided into two categories, normal and dangerous:

  • Normal permissions do not directly risk the user’s privacy. If your app lists a normal permission in its manifest, the system grants the permission automatically.

  • Dangerous permissions can give the app access to the user’s confidential data. If your app lists a normal permission in its manifest, the system grants the permission automatically. If you list a dangerous permission, the user has to explicitly give approval to your app.

简而言之,就是normal的就是不会涉及用户隐私的权限,它是在你需要用的时候,系统自动授权的,dangerous的是会设计用户隐私的权限,他需要用户显示的授权给你的APP。

那接下来让我们来看一下dangerous的权限都是哪些:

Permission GroupPermissions
CALENDAR
CAMERA
CONTACTS
LOCATION
MICROPHONE
PHONE
SENSORS
SMS
STORAGE

这些分别对应着日历、相机、通讯录、位置、麦克风、电话、传感器、短信和外部存储。

由于这些信息都是可以直接或者间接的去获取到用户的隐私所以google将它设置为dangerous不是没有道理的,相信这个可以在一定程度上改善国内应用获取权限过多的情况。

获取dangerous权限

我们讲了那么多关于dangerous权限,现在就让我们来看看如何去获取dangerous权限。
现在我们来尝试获取一下Google Galaxy Nexus Android 4.4.2 API Level 17的设备的通讯录。

图片名称


里面只有一个联系人,现在我们先要来获取一下该联系人,然后插入另一个联系人

private void readContacts() {

    Cursor cursor = getContentResolver().query(ContactsContract.Data.CONTENT_URI, new String[]{ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
                ContactsContract.Data.DATA1}, null, null, null);
    cursor.moveToFirst();
    Log.d(TAG, "readContacts: name: " + cursor.getString(0) + " ,number: " + cursor.getString(1));

}

private void writeContacts() {                                                              

    ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();    
    int rawContactInsertIndex = ops.size();                                                 

    ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)    
            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)                     
            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null).build());           

    //Phone Number                                                                          
    ops.add(ContentProviderOperation                                                        
            .newInsert(ContactsContract.Data.CONTENT_URI)                                   
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID,                   
                    rawContactInsertIndex)                                                  
            .withValue(ContactsContract.Data.MIMETYPE,                                      
                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)               
            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, "987654321")          
            .withValue(ContactsContract.Data.MIMETYPE,                                      
                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)               
            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, "1").build());          

    //Display name/Contact name                                                             
    ops.add(ContentProviderOperation                                                        
            .newInsert(ContactsContract.Data.CONTENT_URI)                                   
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID,                   
                    rawContactInsertIndex)                                                  
            .withValue(ContactsContract.Data.MIMETYPE,                                      
                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)      
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, "Jack")
            .build());                                                                      
    try {                                                                                   
        ContentProviderResult[] res = getContentResolver().applyBatch(                      
                ContactsContract.AUTHORITY, ops);                                           
    } catch (RemoteException e) {                                                           
        // TODO Auto-generated catch block                                                  
        e.printStackTrace();                                                                
    } catch (OperationApplicationException e) {                                             
        // TODO Auto-generated catch block                                                  
        e.printStackTrace();                                                                
    }                                                                                       
} 

现在我们来看一下运行的结果

MainActivity: readContacts: name: charles ,number: 1 234-567-8
图片名称


图片名称


从控制台打印的log和这两张图片我们可以看出来,我们可以成功的获取和插入通讯录。

现在我们来看看 Google Nexus 5X Android 7.0 API level 24 的设备的运行结果。

图片名称


看一下控制台发现原来是抛出了一个异常

java.lang.SecurityException: Permission Denial: opening provider com.android.providers.contacts.ContactsProvider2 
requires android.permission.READ_CONTACTS or android.permission.WRITE_CONTACTS

这个是一行,只是我把它截取成两行,方便大家看。这边说权限被拒绝,访问ContactsProvider 需要这两个权限android.permission.READ_CONTACTS android.permission.WRITE_CONTACTS。可是我明明在AndroidManifest.xml中注册了

<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission> 
<uses-permission android:name="android.permission.WRITE_CONTACTS"></uses-permission>

为什么还会这样呢?我们来看看文档是怎么说的:

If the device is running Android 6.0 or higher, and your app’s target SDK is 23 or higher: The app has to list the permissions in the manifest, and it must request each dangerous permission it needs while the app is running. The user can grant or deny each permission, and the app can continue to run with limited capabilities even if the user denies a permission request.

这样看一下就明了了,在Android 6.0 以上的版本,我们除了要在AndroidManifest.xml中罗列出需要的权限以外还需要在运行的时候显示的申请每一个危险的权限。

我们先来看一下,在这台手机当中只有这个号码

图片名称


现在我们运行一下程序,

在程序的第一个页面是这样的:

图片名称


我们来看看onCreate()的方法

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

    findViewById(R.id.read).setOnClickListener(this);       
    findViewById(R.id.write).setOnClickListener(this);      
    if (checkPermission(READ_CONTACTS,CONTACTS_READ_CODE))  
        readContacts();                                     
    if (checkPermission(WRITE_CONTACTS,CONTACTS_WRITE_CODE))
        writeContacts();                                    

}                                                           
我们可以看到简单的这个改进,我们的程序没有crash了,在运行时跳出了权限的请求。原因在于`if`语句中的那个`checkPermission()`方法,现在我们来看看这个方法:
@TargetApi(Build.VERSION_CODES.M) //因为requestPermission()这个方法要求的api level是23,所以需要这行的annotation来标注           
public boolean checkPermission(final String permission,final int requestCode){                              
    //判断,如果SDK版本小于23就直接返回TRUE,因为在低于23的版本是不需要运行时的申请权限的                                                       
    if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){                                                      
        //如果权限已经得到批准就返回TRUE                                                                                 
        if (this.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {                    
            //这个if语句必须要是第二次才会运行的,就是被DENY一次以后                                                                
            if (this.shouldShowRequestPermissionRationale(permission)) {                                    

                Log.i(TAG,                                                                                  
                        "Displaying permission rationale to provide additional context.");                  
                Snackbar.make(findViewById(R.id.activity_main),"we need to manage your contacts" ,          
                        Snackbar.LENGTH_INDEFINITE)                                                         
                        .setAction("OK", new View.OnClickListener() {                                       
                            @Override                                                                       
                            public void onClick(View view) {                                                
                                MainActivity.this.requestPermissions(new String[]{permission}, requestCode);
                            }                                                                               
                        })                                                                                  
                        .show();                                                                            

            } else {                                                                                        
                this.requestPermissions(new String[]{permission}, requestCode);                             
            }                                                                                               
            return false;                                                                                   
        }                                                                                                   
        return true;                                                                                        
    }                                                                                                       
    return true;                                                                                            
}                                                                                                                                                                                                                      

现在我们来看一下控制台的log打印情况:什么也没有。所以在这个时候,我们还访问不到Contacts Provider

现在我们选择DENY选项,我们再选择READ CONTACT这个button,可以看到在屏幕的底部弹出了这样的一个snackbar

图片名称


图片名称


现在我们点ALLOW,可以看到在控制台打印出了这样的一条log

MainActivity: readContacts: name: charles ,number: 1 234-567-89

说明我们成功的获取到了Contact Provider,我们再来点一下WRITE CONTACTS这个button,会发现我们不再需要申请权限了。而通讯录中也多出了一个Jack

图片名称


你可能有疑问,为什么点击WRITE CONTACTS这个button不需要申请权限呢?让我们接着来看看Android的文档:

If an app requests a dangerous permission listed in its manifest, and the app already has another dangerous permission in the same permission group, the system immediately grants the permission without any interaction with the user. For example, if an app had previously requested and been granted the READ_CONTACTS permission, and it then requests WRITE_CONTACTS, the system immediately grants that permission.

这段话的意思就是,只要你取得了一个dangerous group中的一个permission,那么你再申请同一个group的其他permission就可以立即被granted这个权限。

但是如果你点了Don't ask again这个选项,那么你后面的申请都是无效的了。

其他注意事项:

如果你使用运行时权限,你还需要实现这个接口ActivityCompat.OnRequestPermissionsResultCallback,然后实现它的方法

    @Override                                                                                                            
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == CONTACTS_READ_CODE)                                                                           
            if (grantResults.length > 0 && grantResults[0]==PackageManager.PERMISSION_GRANTED)                           
                 readContacts();                                                                                          
        if (grantResults.length > 0 && requestCode == CONTACTS_WRITE_CODE)                                               
             if (grantResults[0] == PackageManager.PERMISSION_GRANTED)                                                    
                 writeContacts();                                                                                         
}                                                                                                                    

因为在checkPermission()中的requestPermission()会去回调ActivityCompat.OnRequestPermissionsResultCallback的方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值