Android6.0动态权限适配方案,Android 6.0 动态权限申请与 7.0 适配 File Uri 小记

写在前面

看了下上一篇文章的写作日期,转眼之间已经过去了大半个月了……一个国庆小长假下来也是放松了不少,不过学习也不能过于疏忽了。以前偷懒没有看适配6.0 和 7.0的东西,最近在下也是正式的抛弃了大三时买的ip6,入了一台小米mix2。mix2是基于Android 7.1的系统的,自己平时也喜欢用自己的手机调试应用,各种没有适配导致的崩溃自然也是免不了的。以前没看权限适配一是因为6.0以上的系统覆盖率不是很高,二也是因为那时候要看的东西很多,这东西不是很急迫,现在各个厂商的手机出厂系统都是6.0以上的了,也是时候看一下了。本文包括以下内容:

Android 6.0 动态权限申请

RxPermission

Android 7.0 File Uri 导致的崩溃以及如何适配

Android 6.0 动态权限申请

cd779a1b660f

再见

扯犊子时间到此为止,继续正文。系统权限分为几个保护级别,这里需要了解的是两个保护级别是正常权限和危险权限。正常权限只要应用声明了,系统就会自动给应用该权限。而危险权限在Android 6.0 及以上时,则需要通过动态申请来获取权限。当然,考虑到一些兼容性问题,项目的 targetSdkVersion <= 22 时,并不需要动态申请权限。但适配是早晚要去适配的。。。躲也躲不掉,还是先了解下为妙。任何权限都可以属于一个权限组,危险权限也有自己的组别,当你请求了某组权限中的某个权限成功,那么该组的其他权限系统也将授予。比如申请了 STORAGE 权限组的 READ_EXTERNAL_STORAGE 权限,那么该组的 WRITE_EXTERNAL_STORAGE 权限在使用时就无需申请了。接下来放一张危险权限组及危险权限的 截图 ,markdown 制表还是挺麻烦的……偷个懒

cd779a1b660f

危险权限组及危险权限

动态权限申请实操

前面简介写完了,下面开始实操,申请权限主要分为以下几个步骤:

检查是否拥有权限

如果以前用户拒绝过,提示

申请权限

在回调中查看是否申请成功

首先是检查和申请权限,虽然步骤是以上所述,但是代码比较简单,就不一一拆开了,代码中的注释都比较详细。

int permission = ContextCompat.checkSelfPermission(MainActivity.this,

Manifest.permission.CAMERA);

if (permission == PackageManager.PERMISSION_GRANTED) {

// 有此权限

Toast.makeText(MainActivity.this, "已经具有该权限,无需再申请", Toast.LENGTH_SHORT).show();

} else {

// 无此权限,申请权限

if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,

Manifest.permission.CAMERA)) {

// 如果之前请求过权限但用户拒绝了请求

Toast.makeText(MainActivity.this, "请求相机权限,将用于拍照", Toast.LENGTH_SHORT).show();

new Handler().postDelayed(new Runnable() {

@Override

public void run() {

ActivityCompat.requestPermissions(MainActivity.this,

new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA);

}

}, 500);

} else {

// 请求权限

ActivityCompat.requestPermissions(MainActivity.this,

new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA);

}

}

// 回调

@Override

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

switch (requestCode) {

case REQUEST_CAMERA:

// 如果请求被取消了,result数组将是空的

if (grantResults.length > 0 &&

grantResults[0] == PackageManager.PERMISSION_GRANTED) {

// 成功申请该权限

Toast.makeText(this, "成功申请相机权限", Toast.LENGTH_SHORT).show();

} else {

Toast.makeText(this, "您已拒绝该权限,无法使用相机功能 GG,请手动打开该权限", Toast.LENGTH_SHORT).show();

}

break;

default:

break;

}

}

上面的 shouldShowRequestPermissionRationale 方法会返回一个布尔值,这个方法返回值的规则如下:

如果之前请求过此权限但用户拒绝了请求,将返回 true

如果用户在过去拒绝了请求,并在权限请求系统对话框中选择了Don't ask again,此方法将返回false。

如果设备规范禁止应用具有该权限,返回false

我这个代码的基本流程和文档中是一致的,后来在测试的时候发现第一次请求也会走false那个选项,如果我拒绝了这个权限,后续就不会再有权限申请弹窗了,在申请权限的时候系统会自动拒绝。所以我觉得正确的做法应该是在回调中判断权限被拒绝时使用shouldShowRequestPermissionRationale方法,如果返回false则打开设置界面让用户去打开权限。当然,由于国产手机的各种系统定制。。。打开权限设置界面可能并不是一个非常轻松的过程……需要适配……这里就不做这件事了。

RxPermission

上面的权限申请相对来说还是比较繁琐的,接下来介绍一下三方RxPermission,从Rx这俩字你应该可以看出来,这玩意是需要依赖RxJava的。不过对于现在的应用开发来说,RxJava,Okhttp几乎都是标配了,所以说我觉得问题不大,依赖如下:

// rxpermission

compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'

// rxjava

compile 'io.reactivex.rxjava2:rxjava:2.1.2'

// rxandroid

compile 'io.reactivex.rxjava2:rxandroid:2.0.1'

申请权限代码如下:

RxPermissions rxPermissions = new RxPermissions(MainActivity.this);

rxPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)

.subscribe(new Observer() {

@Override

public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {

}

@Override

public void onNext(@io.reactivex.annotations.NonNull Boolean isGranted) {

if (isGranted) {

Toast.makeText(MainActivity.this, "权限申请成功", Toast.LENGTH_SHORT).show();

} else {

Toast.makeText(MainActivity.this, "权限申请失败,用户拒绝了此权限", Toast.LENGTH_SHORT).show();

}

}

@Override

public void onError(@io.reactivex.annotations.NonNull Throwable e) {

e.printStackTrace();

Toast.makeText(MainActivity.this, "权限申请失败,代码异常", Toast.LENGTH_SHORT).show();

}

@Override

public void onComplete() {

}

});

当然了,如果你希望在权限被拒绝时进行进一步的操作,可以使用如下代码:

rxPermissions

.requestEach(Manifest.permission.CAMERA,

Manifest.permission.READ_PHONE_STATE)

.subscribe(permission -> { // will emit 2 Permission objects

if (permission.granted) {

// `permission.name` is granted !

} else if (permission.shouldShowRequestPermissionRationale)

// Denied permission without ask never again

} else {

// Denied permission with ask never again

// Need to go to the settings

}

});

恩,是的,下面一段代码是从仓库上复制来的。。。好了,不要在意这些细节,接着看一下关于File uri 在 Android N 上引发异常的适配。

7.0 适配 File Uri

这里拿拍照作为例子,平时我们调用系统相机实现拍照功能代码如下:

if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {

Toast.makeText(MainActivity.this, "SD卡状态异常", Toast.LENGTH_SHORT).show();

return;

}

long dateTaken = System.currentTimeMillis();

// 图像名称

CharSequence fileName = DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken);

// 图像路径

String path = Environment.getExternalStorageDirectory().toString() +

File.separator + "forpermission" + File.separator + fileName + ".jpg";

File imageFile = new File(path);

if (!imageFile.getParentFile().exists()) {

imageFile.getParentFile().mkdirs();

}

if (!imageFile.exists()) try {

imageFile.createNewFile();

} catch (IOException e) {

e.printStackTrace();

}

// 根据文件解析出文件对应的Uri

Uri uri = Uri.fromFile(imageFile);

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);

// 判断是否有 Activity 能处理 intent

if (intent.resolveActivity(getPackageManager()) != null) {

startActivityForResult(intent, REQUEST_TAKE_PHOTO);

}

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

super.onActivityResult(requestCode, resultCode, data);

if(resultCode == RESULT_OK){

if(requestCode == REQUEST_TAKE_PHOTO){

Toast.makeText(this, "拍照成功", Toast.LENGTH_SHORT).show();

}

}

}

在 7.0 以前运行这段代码是没问题的,那么在 7.0 之后呢?

cd779a1b660f

异常

抛出了 FileUriExposedException 异常,恩,直接crash,心里相想必有一万头草泥马奔腾而过吧。解释官网上也有,Android 7.0 行为变更,中这段话描述了原因:

cd779a1b660f

原因

行,你说用什么就用什么。接下来先详细的记录一下操作过程,之后再解释一下细节。首先在清单文件中声明:

android:name="android.support.v4.content.FileProvider"

android:authorities="com.xiasuhuei321.studyforpermission.takePhotoN"

android:exported="false"

android:grantUriPermissions="true">

android:name="android.support.FILE_PROVIDER_PATHS"

android:resource="@xml/file_paths"/>

接下来新建xml包并新建file_paths xml文件:

name="root"

path="."/>

name="camera_photo"

path="/storage/emulated/0/forpermission/" />

拍照代码:

if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {

Toast.makeText(MainActivity.this, "SD卡状态异常", Toast.LENGTH_SHORT).show();

return;

}

long dateTaken = System.currentTimeMillis();

// 图像名称

CharSequence fileName = DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken);

// 图像路径

String path = Environment.getExternalStorageDirectory().toString() +

File.separator + "forpermission" + File.separator + fileName + ".jpg";

File imageFile = new File(path);

if (!imageFile.getParentFile().exists()) {

imageFile.getParentFile().mkdirs();

}

if (!imageFile.exists()) try {

imageFile.createNewFile();

} catch (IOException e) {

e.printStackTrace();

}

// 根据文件解析出文件对应的Uri

Uri uri = FileProvider.getUriForFile(MainActivity.this,

"com.xiasuhuei321.studyforpermission.takePhotoN", imageFile);

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);

// 判断是否有 Activity 能处理 intent

if (intent.resolveActivity(getPackageManager()) != null) {

startActivityForResult(intent, REQUEST_TAKE_PHOTO);

}

恩,留个小坑,操作流程写了,余下的具体解析待填。

好了,决定不填了,就是这么任性,哈哈哈哈哈哈哈。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值