Android动态权限问题处理

当targetSdkVersion>=23后,android系统权限有了新的变化。不再是,像以前一样默认给了所有权限。
而是,需要使用到权限的地方,系统提供了供用户选择是否给予该权限的弹框。这样虽然保证了用户的安全隐私。但,同时也给开发带来了新的问题,如果用户拒绝了权限,没有做权限处理的应用程序则会导致程序奔溃问题。
嗯,查一下官方文档,相信大家都知道该怎么处理。那怎么处理更省时间,方便呢?
这里推荐一下PermissionsDispatcher,我们接着往下看看怎么使用吧!


背景

在Android6.0(M)之后,权限进行了分类,大致有这三种:

普通权限

也就是正常权限,是对手机的一些正常操作,对用户的隐私没有太大影响的权限,比如手机的震动,网络访问,蓝牙等权限,这些权限会在应用被安装的时候默认授予,用户不能拒绝,也不能取消。在AndroidManifest.xml声明即可。

  • 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
  • INTERNET
  • KILL_BACKGROUND_PROCESSES
  • MODIFY_AUDIO_SETTINGS
  • NFC
  • READ_SYNC_SETTINGS
  • READ_SYNC_STATS
  • RECEIVE_BOOT_COMPLETED
  • REORDER_TASKS
  • REQUEST_INSTALL_PACKAGES
  • SET_TIME_ZONE
  • SET_WALLPAPER
  • SET_WALLPAPER_HINTS
  • TRANSMIT_IR
  • USE_FINGERPRINT
  • VIBRATE
  • WAKE_LOCK
  • WRITE_SYNC_SETTINGS
  • SET_ALARM
  • INSTALL_SHORTCUT
  • UNINSTALL_SHORTCUT

危险权限

也就是我们本篇需要处理的权限,如果不想处理targetSdkVersion的版本设置为低于23就可暂时避免,但用户可以去应用设置里面关闭该权限,同样会出现没处理权限的问题;所以,还是勇敢面对吧

以下这些危险权限,谷歌还做了一个权限组,以分组的形式来呈现,就是其中一个权限允许了,整个权限组的权限都允许了:

拍照权限处理示例,非第三方库示例:

  • 判断权限及申请
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //判断当前系统是否高于或等于6.0
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    //当前系统大于等于6.0
    if (ContextCompat.checkSelfPermission(MineInforActivity.this,Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
    //具有拍照权限,直接调用相机
    //具体调用代码
    } else {
    //不具有拍照权限,需要进行权限申请
    ActivityCompat.requestPermissions(MineInforActivity.this,new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CAMERA_CODE);
    }
    } else {
    //当前系统小于6.0,直接调用拍照

    }
  • 权限结果处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_PERMISSION_CAMERA_CODE) {
    if (grantResults.length >= 1) {
    int cameraResult = grantResults[0];//相机权限
    boolean cameraGranted = cameraResult == PackageManager.PERMISSION_GRANTED;//拍照权限
    if (cameraGranted) {
    //具有拍照权限,调用相机
    } else {
    //不具有相关权限,给予用户提醒,比如Toast或者对话框,让用户去系统设置-应用管理里把相关权限开启
    }
    }
    }
    }

特殊权限

需使用startActivityForResult,启动授权界面来完成。如下示例注意事项:1.使用Action Settings.ACTION_MANAGE_WRITE_SETTINGS启动隐式Intent。2.使用”package:” + getPackageName()携带App的包名信息。3.使用Settings.System.canWrite方法检测授权结果

  • SYSTEM_ALERT_WINDOW
  • WRITE_SETTINGS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static final int REQUEST_CODE_WRITE_SETTINGS = 1;
private void requestWriteSettings() {
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQUEST_CODE_WRITE_SETTINGS );
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_WRITE_SETTINGS) {
if (Settings.System.canWrite(this)) {
Log.i(LOGTAG, "onActivityResult write settings granted" );
}
}
}

PermissionsDispatcher 使用

今天主要介绍的主角, PermissionsDispatcher 是一个简单的基于注解的API来处理运行时权限的第三分库。
减轻了编写一堆检查语句所带来的负担,不管你是否授予权限,使用起来都能让你的代码保持整洁和安全。
主要原理:通过注解Build自动生成相关处理类 例如:XxActivity 下添加注解后生成 XxActivityPermissionsDispatcher 类(主要两个方法:申请权限方法,处理权限方法)

注解介绍

注解 需要 描述
@RuntimePermissions 注册一个Activity或Fragment(两者都支持)来处理权限
@NeedsPermission 注释执行需要一个或多个权限的操作的方法,权限通过执行
@OnShowRationale 注释一个解释为什么需要权限的方法。它传入一个PermissionRequest对象,可以用来在用户输入时继续或中止当前的权限请求
@OnPermissionDenied 如果用户没有授予权限,则注释一个被调用的方法,权限未通过执行
@OnNeverAskAgain 如果用户选择让设备“再也不询问”权限,则注释一个被调用的方法

示例

这是一个很小的例子,来源于开源库提供的示例。在这个例子中你注册了一个MainActivity需要的Manifest.permission.CAMERA。

导入依赖

  • app build.gradle添加
    1
    2
    3
    4
    5
    6
    7
    dependencies {
    compile("com.github.hotchemi:permissionsdispatcher:3.1.0") {
    // if you don't use android.app.Fragment you can exclude support for them
    exclude module: "support-v13"
    }
    annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:${latest.version}"
    }
  • 主项目 build.gradle添加
    1
    2
    3
    4
    repositories {
    jcenter()
    maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local/' }
    }

权限处理

  • AndroidManifest.xml 申请权限

    1
    <uses-permission android:name="android.permission.CAMERA" />
  • 给需要的Activity加上注解,关键使用代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    @RuntimePermissions
    public class MainActivity extends AppCompatActivity {

    @NeedsPermission(Manifest.permission.CAMERA)
    void showCamera() {//拍照权限通过之后执行的代码
    getSupportFragmentManager().beginTransaction()
    .replace(R.id.sample_content_fragment, CameraPreviewFragment.newInstance())
    .addToBackStack("camera")
    .commitAllowingStateLoss();
    }

    @OnShowRationale(Manifest.permission.CAMERA)
    void showRationaleForCamera(final PermissionRequest request) {//询问是否申请权限
    new AlertDialog.Builder(this)
    .setMessage(R.string.permission_camera_rationale)
    .setPositiveButton(R.string.button_allow, (dialog, button) -> request.proceed())
    .setNegativeButton(R.string.button_deny, (dialog, button) -> request.cancel())
    .show();
    }

    @OnPermissionDenied(Manifest.permission.CAMERA)
    void showDeniedForCamera() {//拒绝权限
    Toast.makeText(this, R.string.permission_camera_denied, Toast.LENGTH_SHORT).show();
    }

    @OnNeverAskAgain(Manifest.permission.CAMERA)
    void showNeverAskForCamera() {//永久拒绝权限 可在此处提供去设置打开权限 //PermissionsUtil.startAppSettings();
    Toast.makeText(this, R.string.permission_camera_neverask, Toast.LENGTH_SHORT).show();
    }
    }
  • 打开设置手动开启权限的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class PermissionsUtil {
    // 在当前activity中 启动应用的设置 mApplication.为当前应用的Application.实现类实例化对象
    public static void startAppSettings() {
    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    intent.setData(Uri.parse("package:" + mApplication.getPackageName()));
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    mApplication.startActivity(intent);
    }
    }
  • Build生成运行时权限处理代码

    点击Build -> Make Project 执行完后会生成 MainActivityPermissionsDispatcher 类。

  • 使用,在申请权限的地方执行如下示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    findViewById(R.id.button_camera).setOnClickListener(v -> {
    // NOTE: delegate the permission handling to generated method 申请权限的地方执行下方代码
    MainActivityPermissionsDispatcher.showCameraWithPermissionCheck(this);
    });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    // NOTE: delegate the permission handling to generated method 处理权限的代码,必须写
    MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
    }

注意

  • PermissionsDispatcher可同时申请多权限
    比如@NeedsPermission(Manifest.permission.CAMERA)、

    @OnShowRationale(Manifest.permission.CAMERA)、
    @OnPermissionDenied(Manifest.permission.CAMERA)、    
    @OnNeverAskAgain(Manifest.permission.CAMERA),写法是申请拍照功能

    同时你可以写出
    @NeedsPermission(Manifest.permission.CAMERA,Manifest.permission.ACCESS_FINE_LOCATION)、

    @OnShowRationale(Manifest.permission.CAMERA,Manifest.permission.ACCESS_FINE_LOCATION)、
    @OnPermissionDenied(Manifest.permission.CAMERA,Manifest.permission.ACCESS_FINE_LOCATION)、    
    @OnNeverAskAgain(Manifest.permission.CAMERA,Manifest.permission.ACCESS_FINE_LOCATION), 同时申请拍照和定位两个权限组。同样你还可以继续加,同时申请更多权限组批量申请

    所有要提供权限的注解,权限参数必须保持一致,如还有疑问,可查询官方示例

  • 如果用户在当前应用设置中,修改了权限状态。然后再返回到当前应用,则会出现整个应用程序除了最后一个activity被重新执行一遍,其他的activity都被杀死的情况。这里着重需要处理数据状态保存恢复,否则很容易出意想不到的问题。

  • 存储权限相关需要在Manifest添加创建目录的权限

    • < uses-permission android:name=”android.permission.MOUNT_UNMOUNT_FILESYSTEMS” />