Android 6.0动态权限介绍与处理

一、Android 6.0权限介绍

从 Android 6.0(API 级别 23)开始,用户开始在应用运行时向其授予权限,而不是在应用安装时授予。 Android 6.0系统6.0以前,所有的权限,访问网络的权限,读取SD卡的权限,访问通讯录,拨打电话的权限都在安装的时候系统授予了要安装的应用。在系统运行的时候不需要对权限做任何的处理。用户在安装的时候一般都不知道这些权限用在了什么地方,这样做很不安全。一个应用很容易在一个Service中读取用户所有的通讯录信息发送到服务器上。Android 6.0后的动态权限让我们的系统更加安全,牺牲了用户的方便性,得到的是安全。下图是Android 6.0系统对拨打电话和管理电话权限组的询问。

《Android 6.0动态权限介绍与处理》

Android 6.0把权限分为两种:Normal Permissions(正常权限)和Dangerous Permissions(危险权限)。其中危险权限又进行了分类。把所有的危险权限分成了几个组。正常权限不会给用户的隐私带来不安全,不需要动态申请,在应用安装的时候就已经被授予了。危险权限需要动态处理,只有用户批准了这些权限,应用才能被授予这些权限。

所有的普通权限:
– ACCESS_LOCATION_EXTRA_COMMANDS
– ACCESS_NETWORK_STATE
– ACCESS_NOTIFICATION_POLICY
– ACCESS_WIFI_STATE
– BLUETOOTH
– BLUETOOTH_ADMIN
– BROADCAST_STICKY
– CHANGE_NETWORK_STATE
– CHANGE_WIFI_MULTICAST_STATE
– CHANGE_WIFI_STATE
– DISABLE_KEYGUARD
– EXPAND_STATUS_BAR
– GET_PACKAGE_SIZE
– INSTALL_SHORTCUT
– INTERNET
– KILL_BACKGROUND_PROCESSES
– MODIFY_AUDIO_SETTINGS
– NFC
– READ_SYNC_SETTINGS
– READ_SYNC_STATS
– RECEIVE_BOOT_COMPLETED
– REORDER_TASKS
– REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
– REQUEST_INSTALL_PACKAGES
– SET_ALARM
– SET_TIME_ZONE
– SET_WALLPAPER
– SET_WALLPAPER_HINTS
– TRANSMIT_IR
– UNINSTALL_SHORTCUT
– USE_FINGERPRINT
– VIBRATE
– WAKE_LOCK
– WRITE_SYNC_SETTINGS

所有的危险权限:

《Android 6.0动态权限介绍与处理》

二、Android 6.0+危险权限的动态处理

在开发应用的时候不管是正常权限还是危险权限都必须在应用的Manifest.xml文件中声明。如果设备运行的是Android 5.1或更低的系统,或者应用的目标SDK小于23,那么在Manifest.xml文件中列出的危险权限在安装的时候用户必须接受,要不应用没法安装。如果设备运行的是Android 6.0或更高的系统,或者应用的目标SDK大于等于23,那么在Manifest.xml文件中列出的危险权限,会在应用运行的时候被用户授予或拒绝。开发者需要在代码中对危险权限进行处理。

public class MainActivity extends AppCompatActivity {

    final public static int REQUEST_CODE_ASK_CALL_PHONE = 123;
    private String mMobile;

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

        findViewById(R.id.button).setOnClickListener(new                                                   

        View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onCallWrapper("12345678912");
            }
        });

    }
    public void onCallWrapper(String mobile) {
        this.mMobile = mobile;
        int checkCallPhonePermisssion = ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE);
        if (checkCallPhonePermisssion != PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,          Manifest.permission.CALL_PHONE)) {
                Toast.makeText(this, "shouldShowRequestPermissionRationale", Toast.LENGTH_SHORT).show();
                new AlertDialog.Builder(this)
                        .setTitle("提示")
                        .setMessage("应用需要开启拍照的权限,是否继续?")
                        .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                ActivityCompat.requestPermissions(MainActivity.this, new           String[]{Manifest.permission.CALL_PHONE}, REQUEST_CODE_ASK_CALL_PHONE);
                            }
                        })
                        .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {

                            }
                        }).show();
            } else {
                ActivityCompat.requestPermissions(this, new String[]             {Manifest.permission.CALL_PHONE}, REQUEST_CODE_ASK_CALL_PHONE);
            }

        } else {
            callDirectly(mobile);
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,        
@NonNull int[] grantResults) {


        switch (requestCode) {
            case REQUEST_CODE_ASK_CALL_PHONE:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // Permission Granted 授予权限
                    callDirectly(mMobile);
                } else {
                    // Permission Denied 权限被拒绝
                    Toast.makeText(MainActivity.this, "Permission Denied", 
                  Toast.LENGTH_SHORT).show();
                }

                break;
            default:
                break;
        }


        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    private void callDirectly(String mobile) {
        Intent intent = new Intent();
        intent.setAction("android.intent.action.CALL");
        intent.setData(Uri.parse("tel:" + mobile));
        this.startActivity(intent);
    }
}

上面这个简单的例子是通过点击一个按钮拨打电话。在6.0系统之前,我们只需要直接调用方法callDirectly()方法就可以了。增加了系统的安全性,但是多了很多代码上的处理。ContextCompat和
ActivityCompat分别是Context和Activity的兼容类,在上的代码中我们就不用去判断当前系统的API是多好了,在API23之前也能用上面代码中的方法。ContextCompat.checkSelfPermission()方法是用来判断,当前应用是否拥有某个权限。如果拥有我们可以直接调用callDirectly()方法拨打电话。如果当前应用没有拨打电话的权限,会调ActivityCompat.shouldShowRequestPermissionRationale()方法,注意当前系统还没有弹出让用户去选择是允许还是拒绝,这个时候shouldShowRequestPermissionRationale()方法返回false。所以接下来会执ActivityCompat.requestPermissions()方法会向系统去请求,系统会弹出一个对话框让用户去选择。onRequestPermissionsResult()方法用来处理用户的选择。这个方法可以监听用户选择的是允许还是拒绝。当用户选择了允许,那么直接调用callDirectly()方法拨打电话。当用户选择了拒绝,上面的代码只是提示一个Toast。

当用户选择了拒绝,那么在下次点击按钮拨打电话的时候ActivityCompat.shouldShowRequestPermissionRationale()方法会返回true,在上面的代码中我们会弹出一个对话框给用户,给用户一个提示。当用户选择确定,会向系统请求权限。当用户选择取消,关闭对话框什么也不做。当我们选择了不在提示并且选择了拒绝的时候ActivityCompat.shouldShowRequestPermissionRationale()方法返回false。

三、用Easy Permissions开源库处理权限

1.配置

在app层的build.gradle中

dependencies {
    // EasyPermissions
    compile 'pub.devrel:easypermissions:0.3.0'
}

在app层的build.gradle中

dependencies {
    // EasyPermissions
    compile 'pub.devrel:easypermissions:0.3.0'
}

2.举例

public class SecondActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {

    private static final int RC_CAMERA_PERM = 123;

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

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                cameraTask();
            }
        });

    }

    @AfterPermissionGranted(RC_CAMERA_PERM)
    public void cameraTask() {
        if (EasyPermissions.hasPermissions(this, Manifest.permission.CAMERA)) {
            // 该应用已经有打电话的权限
            Toast.makeText(this, "TODO: Camera things", Toast.LENGTH_LONG).show();
        } else {
            EasyPermissions.requestPermissions(this, "需要获取系统的拍照的权限!", RC_CAMERA_PERM, Manifest.permission.CAMERA);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

    @Override
    public void onPermissionsGranted(int requestCode, List<String> perms) {
        Toast.makeText(this, "onPermissionsGranted", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onPermissionsDenied(int requestCode, List<String> perms) {
        Toast.makeText(this, "onPermissionsDenied", Toast.LENGTH_SHORT).show();

        new AppSettingsDialog.Builder(this)
                .setTitle("请求权限")
                .setRationale("需要开启启用相机的权限才能继续下去!")
                .build()
                .show();
    }
}

这个例子是点击一个按钮,处理拍照的事情,所以我们需要获取相机的权限。首先我们要复写Activity或者Fragment的onRequestPermissionsResult()方法。SecondActivity实现了EasyPermissions.PermissionCallbacks这个接口复写了onPermissionsGranted()和onPermissionsDenied()这两个接口。当用户选择了允许那么调用onPermissionsGranted()方法,当用户选择了拒绝那么调用onPermissionsDenied()方法。AppSettingsDialog是一个询问用户是否跳转到运行应用的设置界面去开启权限的对话框。

四、用Permissions Dispatcher开源库处理权限

1.配置

在app层的build.gradle中

dependencies {
    // Permissions Dispatcher
    compile 'com.github.hotchemi:permissionsdispatcher:2.3.1'
    annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:2.3.1'
}

这种配置需要有个条件:Android Gradle Plugin >= 2.2

2.举例

这个库用到了5个注解:

注解是否必须描述
@RuntimePermissions注册在Acttivity或者Fragment上
@NeedsPermission再需要权限的方法上注册
@OnShowRationale被注解的方法可以提示为什么需要这个权限
@OnPermissionDenied如果用户拒绝了权限申请那么调用该方法
@OnNeverAskAgain如果用户选择了不再询问,调用该方法

这个库用到了Annotion Processor,需要用到一个应用编译期间产生的类,这个类的命名是:类名+PermissionsDispatcher。

@RuntimePermissions
public class ThirdActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_third);

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ThirdActivityPermissionsDispatcher.showContactsWithCheck(ThirdActivity.this);
            }
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        ThirdActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
    }

    @NeedsPermission({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
    public void showContacts() {
        Toast.makeText(this, "读取通讯录", Toast.LENGTH_SHORT).show();
    }

    @OnPermissionDenied({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
    public void onContactsDenied() {
        Toast.makeText(this, "onContactsDenied", Toast.LENGTH_SHORT).show();


    }

    @OnNeverAskAgain({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
    public void onContactsNeverAskAgain() {
        Toast.makeText(this, "onContactsNeverAskAgain", Toast.LENGTH_SHORT).show();

        new AppSettingsDialog.Builder(this)
                .setTitle("请求权限")
                .setRationale("需要开启启用相机的权限才能继续下去!")
                .build()
                .show();

    }

    @OnShowRationale({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
    public void showRationaleContacts(final PermissionRequest request) {
// Toast.makeText(this, "showRationaleContacts", Toast.LENGTH_SHORT).show();

        new AlertDialog.Builder(this)
                .setPositiveButton("继续", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(@NonNull DialogInterface dialog, int which) {
                        request.proceed();
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(@NonNull DialogInterface dialog, int which) {
                        request.cancel();
                    }
                })
                .setCancelable(false)
                .setMessage("需要通讯录权限")
                .show();
    }

}

具体的过程可以参考Demo。

Demo下载

参考文献:

https://github.com/googlesamples/easypermissions

https://github.com/hotchemi/PermissionsDispatcher
http://hotchemi.github.io/PermissionsDispatcher/

https://inthecheesefactory.com/blog/things-you-need-to-know-about-android-m-permission-developer-edition/en

https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous
系统所需要的权限
https://developer.android.com/reference/android/Manifest.permission.html
普通权限
https://developer.android.com/guide/topics/permissions/normal-permissions.html
危险权限
https://developer.android.com/guide/topics/security/permissions.html#perm-groups
在运行时请求权限
https://developer.android.com/training/permissions/requesting.html

解读Android官方开发指导 – 运行时权限
http://www.jianshu.com/p/0beb6243d650

    原文作者:android2me
    原文地址: https://blog.csdn.net/android2me/article/details/69525975
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞