Android6.0权限分配终极解决方案

时间:2022-07-29 15:24:39

1、问题描述

如下一段代码,在Android 5.0设备运行时,可以得到正确结果,但在Android 6.0以上设备运行时,提示没有读写权限。但AndroidManifest文件中已经设置过权限了。

错误提示信息是这样的:

Caused by: java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://media/external/audio/media from pid=3508, uid=10078 requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission()

Activity代码

public class MainActivity extends AppCompatActivity {

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

Cursor cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, null);
cursor.moveToFirst();
do {
String title = cursor.getString(cursor.getColumnIndex("title"));
Log.e("TITLE",title);
}while (cursor.moveToNext());
}
}

AndroidManifest代码

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lenovo.permissiontest">


<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>

2、问题诊断

众所周知,我们在做Android开发时,经常会用到一些系统的权限,比如读写外部存储、读写联系人信息、调用电话短信、访问网络等。在Android 6.0之前,对于这些权限调用的声明,只需要在AndroidManifest文件中声明即可,就像下面这样:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

用户在安装应用时,会提示该程序总共用到哪些权限,但用户没有选择权,只有知情权,那么安全问题就随之而来。所以在android 6.0 棉花糖版本之后,系统不会在软件安装的时候就赋予该app所有其申请的权限,对于一些危险级别的权限,app需要在运行时一个一个询问用户授予权限。这就是所谓的“权限动态授权”。

那么哪些权限是危险权限呢?下表所示即为危险权限,这些权限都必须在应用内部经用户动态授权才能使用。
Android6.0权限分配终极解决方案

3、问题解决

动态权限分配,通过代码如何实现呢?请看下面代码。

Activity代码

public class MainActivity extends AppCompatActivity {

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

//检查当前权限(若没有该权限,值为-1;若有该权限,值为0)
int hasReadExternalStoragePermission = ContextCompat.checkSelfPermission(getApplication(),Manifest.permission.READ_EXTERNAL_STORAGE);
Log.e("PERMISION_CODE",hasReadExternalStoragePermission+"***");
if(hasReadExternalStoragePermission== PackageManager.PERMISSION_GRANTED){
obtainMediaInfo();
}else{
//若没有授权,会弹出一个对话框(这个对话框是系统的,开发者不能自己定制),用户选择是否授权应用使用系统权限
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},1);
}

}

//用户选择是否同意授权后,会回调这个方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if(requestCode==1){
if(permissions[0].equals(Manifest.permission.READ_EXTERNAL_STORAGE)&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
//用户同意授权,执行读取文件的代码
obtainMediaInfo();
}else{
//若用户不同意授权,直接暴力退出应用。
// 当然,这里也可以有比较温柔的操作。
finish();
}
}
}

//将之前获取音乐信息的代码单独封装到了一个方法里面
private void obtainMediaInfo() {
Cursor cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, null);
cursor.moveToFirst();
do {
String title = cursor.getString(cursor.getColumnIndex("title"));
Log.e("TITLE",title);
}while (cursor.moveToNext());
}
}

总结

这里主要用到两个方法

  • ActivityCompat.requestPermissions()
  • onRequestPermissionsResult()

ActivityCompat.requestPermissions()方法有3个参数:
第1个参数,当前上下文环境;
第2个参数,需要授权的字符串数组;
第3个参数,请求码requestCode,在回调方法中会用到。

onRequestPermissionsResult()是个回调方法,也就是说在用户点击“允许”或“拒绝”按钮后,才会调用。这个方法也有3个参数:
第1个参数,请求码,对应上述方法的第3个参数;
第2个参数,请求权限数组;
第3个参数,请求权限的结果数组。

详细使用方法请看上方代码,注释较为详细。

至此,我们已经完成了单个权限授权的代码。但是,距离完美还差那么一点点,比如:

  • 如果我们想要一次性进行多次授权呢?
  • 如果用户选择了不再弹出之后呢?

请继续往下看

4、更优雅的解决方案

Android6.0提出了如此周到、复杂的权限处理方式,对用户而言是好事,对程序员而言是极大的繁琐。
幸运的是我们有github,早已有人开发了第三方插件。

印度人写的第三方插件(点击查看)

https://github.com/pankaj89/PermissionHelper

build.gradle中编译

compile ‘com.master.android:permissionhelper:1.3’

github链接里已经写了详细的使用步骤,我也写了个例子,亲测非常好用。代码附上。

public class HelloActivity extends AppCompatActivity {

private String TAG = "HelloActivity";
private PermissionHelper permissionHelper;

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

permissionHelper = new PermissionHelper(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
permissionHelper.request(new PermissionHelper.PermissionCallback() {
@Override
public void onPermissionGranted() {
//全都授权
Log.e(TAG,"onPermissionGranted...");
}

@Override
public void onIndividualPermissionGranted(String[] grantedPermission) {
//某个授权
Log.e(TAG,"onIndividualPermissionGranted() called with: grantedPermission = [" + TextUtils.join(",",grantedPermission) + "]");
}

@Override
public void onPermissionDenied() {
//某个拒绝
Log.e(TAG,"onPermissionDenied...");
}

@Override
public void onPermissionDeniedBySystem() {
//用户选择了"不再询问"后,点击"拒绝按钮",执行此方法
Log.e(TAG,"onPermissionDeniedBySystem...");
}
});
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (permissionHelper != null) {
permissionHelper.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}

}