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

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权限分配终极解决方案_第1张图片

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);
        }
    }

}

你可能感兴趣的:(学习笔记,技术文章,Android)