permission 新 运行时权限 6.0 总结
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了permission 新 运行时权限 6.0 总结相关的知识,希望对你有一定的参考价值。
关于运行时权限
在旧的权限管理系统中,权限仅仅在App安装时询问用户一次,用户同意了这些权限App才能被安装(某些深度定制系统另说),App一旦安装后就可以偷偷的做一些不为人知的事情了(同样,某些深度定制系统另说)。
从android6.0开始,App可以直接安装,App在运行时一个一个询问用户是否授予权限,系统会弹出一个对话框让用户选择是否授权某个权限给App(这个Dialog是系统定义的,不能由开发者定制,但必须由开发者手动调用),当App要求用户授予不恰当的权限的时候,用户可以拒绝(然而,很多APP可能会在请求权限失败后直接退出,所以你往往无法拒绝),用户也可以在设置页面对每个App的权限进行管理。
新的权限策略讲权限分为两类,第一类是不涉及用户隐私的,只需要在Manifest中声明即可,比如网络、蓝牙、NFC等;第二类是涉及到用户隐私信息的,需要用户授权后才可使用,比如SD卡读写、联系人、短信读写等。
不需要运行时申请的权限
此类权限都是正常保护的权限,只需要在AndroidManifest.xml中简单声明这些权限即可,安装即授权,不需要每次使用时都检查权限,而且用户不能取消以上授权,除非用户卸载App。
需要运行时申请的权限
所有危险的Android系统权限属于权限组,如果APP运行在Android 6.0 (API level 23)或者更高级别的设备中,并且targetSdkVersion>=23时,系统将会自动采用动态权限管理策略,如果你在涉及到特殊权限操作时没有申请权限而直接调用了相关代码,你的App可能就崩溃了。
综上所述你需要注意:
- 此类权限也必须在Manifest中申明,否则申请时不提示用户,直接回调开发者权限被拒绝。
- 同一个权限组的任何一个权限被授权了,这个权限组的其他权限也自动被授权。例如一旦WRITE_CONTACTS被授权了,App也有READ_CONTACTS和GET_ACCOUNTS了。
- 申请某一个权限的时候系统弹出的Dialog是对整个权限组的说明,而不是单个权限。例如我申请READ_EXTERNAL_STORAGE,系统会提示"允许xxx访问设备上的照片、媒体内容和文件吗?"。
其他情景:
1、targetSdkVersion小于等于22,但是设备系统版本小于等于6.0:
- app使用旧的权限管理策略
- 注册文件列出的权限将会在安装时要求用户授予权限
- 用户可以在设置列表中编辑相关权限,这对app能否正常运行有很大影响
2、targetSdkVersion大于等于23,但是设备系统版本小于6.0:
- app使用旧的权限管理策略
- 注册文件列出的权限将会在安装时要求用户授予权限
需要授权的权限组
使用adb命令可以查看这些需要授权的权限组:
adb shell pm list permissions -d -g
以下为华为手机上的数据(Android 6.0.1)
(注意:不同系统可能稍有差异,特别是某些定制系统会添加一些额外的权限,但是以下标红部分是Android系统定义的最基本的危险权限,基本上定制系统也都会有)
C:\Users\Administrator>adb shell pm list permissions -d -g
* daemon not running. starting it now at tcp:5037 *
* daemon started successfully *
Dangerous Permissions:
group:com.google.android.gms.permission.CAR_INFORMATION 汽车资料
permission:com.google.android.gms.permission.CAR_VENDOR_EXTENSION 供应商
permission:com.google.android.gms.permission.CAR_MILEAGE 里程
permission:com.google.android.gms.permission.CAR_FUEL 燃料
group:android.permission-group.CONTACTS 联系人
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
permission:android.permission.READ_CONTACTS
group:android.permission-group.PHONE 手机
permission:android.permission.READ_CALL_LOG
permission:android.permission.READ_PHONE_STATE
permission:android.permission.CALL_PHONE
permission:android.permission.WRITE_CALL_LOG
permission:android.permission.USE_SIP
permission:android.permission.PROCESS_OUTGOING_CALLS
permission:com.android.voicemail.permission.ADD_VOICEMAIL
group:com.kingroot.kinguser.permission-group.SUPERUSER 超级管理员
group:android.permission-group.CALENDAR 日历
permission:android.permission.READ_CALENDAR
permission:android.permission.WRITE_CALENDAR
group:android.permission-group.CAMERA 相机
permission:android.permission.CAMERA
group:android.permission-group.SENSORS 传感器
permission:android.permission.BODY_SENSORS
group:android.permission-group.LOCATION 位置
permission:android.permission.ACCESS_FINE_LOCATION
permission:com.google.android.gms.permission.CAR_SPEED
permission:android.permission.ACCESS_COARSE_LOCATION
group:android.permission-group.STORAGE 存储卡
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE
group:com.sina.weibo.permission-group
permission:com.sina.weibo.permission.USER
group:android.permission-group.MICROPHONE 麦克风
permission:android.permission.RECORD_AUDIO
group:android.permission-group.SMS 短信
permission:android.permission.READ_SMS
permission:android.permission.RECEIVE_WAP_PUSH
permission:android.permission.RECEIVE_MMS
permission:android.permission.RECEIVE_SMS
permission:android.permission.SEND_SMS
permission:android.permission.READ_CELL_BROADCASTS
ungrouped:未分组的
permission:com.huawei.pushagent.permission.RICHMEDIA_PROVIDER
permission:com.huawei.contacts.permission.HW_NUMBER_MARK
permission:com.huawei.motion.permission.MOTION_EX
permission:com.huawei.contacts.permission.CHOOSE_SUBSCRIPTION
使用系统API完整演示
public class MainActivity extends ListActivity {
private static final int REQUESTCODE = 20094;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] array = {"在没有申请权限的情况下在SD卡创建文件会失败",
"完整的授权过程演示",
"演示PermissionsDispatcher",};
setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new ArrayList<>(Arrays.asList(array))));
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
switch (position) {
case 0:
createFileWithoutRequestPermission(this);
break;
case 1:
requestPermissionBeforeCreateFile();
break;
case 2:
startActivity(new Intent(this, Activity1.class));
break;
}
}
/**
* 如果将targetSdkVersion改为22或以下,可以成功创建文件
* 相反,如果改为23或以上,则失败
*/
public static void createFileWithoutRequestPermission(Context context) {
String fileName = new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss", Locale.getDefault()).format(new Date());
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), fileName + ".txt");
boolean createFile = false;
try {
createFile = file.createNewFile();//没有申请权限时就在SD卡创建文件会失败(当然,如果文件已经存在,返回值也是false)
} catch (IOException e) {
e.printStackTrace();
} finally {
Toast.makeText(context, "创建文件结果:" + createFile, Toast.LENGTH_SHORT).show();
}
}
private void requestPermissionBeforeCreateFile() {
//检查权限。结果:PERMISSION_GRANTED=0,有权限;PERMISSION_DENIED=-1,没有权限
int state = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
Toast.makeText(this, "写SD卡权限状态:" + state, Toast.LENGTH_SHORT).show();
if (state != PackageManager.PERMISSION_GRANTED) {// 没有权限,申请权限
//是否应该显示请求权限的说明。
//加这个提醒的好处在于:第一次申请权限时不需要麻烦用户,但如果用户拒绝过一次权限后我们再次申请时
//可以提醒用户授予该权限的必要性,免得再次申请时用户勾选"不再提醒"并拒绝,导致下次申请权限直接失败
//返回值的特点:第一次请求权限之前调用返回false(不应该提醒);如果用户拒绝了,则下次调用返回true(应该提醒)
//如果之后再次请求权限时,用户拒绝了并选择了"不再提醒",则下次调用返回false(不应该提醒,且不弹授权对话框)
boolean state2 = ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (state2) {
// 一般是通过弹一个自定义的对话框告诉用户,我们为什么需要这个权限
new AlertDialog.Builder(this)
.setTitle("请求读写SD卡权限").setMessage("请求SD卡权限,作用是给你保存妹子图片")
.setPositiveButton("知道了", (dialog, which) -> ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUESTCODE))
.create().show();
} else {
//请求用户授权几个权限,调用后系统会显示一个请求用户授权的提示对话框,开发者不能修改这个对话框
ActivityCompat.requestPermissions(MainActivity.this,//api 23以后可以调用Activity的requestPermissions方法
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},//需要申请的权限数组
REQUESTCODE);//请求码,会在回调onRequestPermissionsResult()时返回
}
} else {// 有权限了,去放肆吧。
createFileWithoutRequestPermission(this);
}
}
@Override
//当用户处理完授权操作时,会回调Activity或Fragment的此方法,参数为:权限数组、授权结果数组
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults.length > 0) {
switch (requestCode) {
case REQUESTCODE: {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
createFileWithoutRequestPermission(this);
} else {
Toast.makeText(this, "你拒绝了权限,我们已经没法愉快的玩耍了,我将在3秒后关闭!", Toast.LENGTH_SHORT).show();
new Handler().postDelayed(this::finish, 3 * 1000);
}
}
}
}
}
}
PermissionsDispatcher 简介
Simple annotation-based API to handle runtime permissions.
PermissionsDispatcher provides a simple annotation-based API to handle runtime permissions in Android Marshmallow.
This library lifts the burden that comes with writing a bunch of check statements whether a permission has been granted or not from you, in order to keep your code clean and safe.
使用简单的基于注解的API来处理运行时权限。
PermissionsDispatcher 提供了一个简单的基于注解的API处理Android M 系统的运行时权限。
这个库解除了编写一系列检查语句时的负担,不管您是否已经被授予权限,以保持代码的整洁和安全。
使用过程
1、添加依赖
app
compile ‘com.github.hotchemi:permissionsdispatcher:2.4.0‘
annotationProcessor ‘com.github.hotchemi:permissionsdispatcher-processor:2.4.0‘
根目录
allprojects {
repositories {
jcenter()
mavenCentral()//或 maven { url ‘http://oss.jfrog.org/artifactory/oss-snapshot-local/‘ }
}
}
注意:要将targetSdkVersion设为23货以上
targetSdkVersion 25
2、给Activity、Fragment、方法添加注解
Annotation | Required | Description |
@RuntimePermissions | ? | 注解在其内部需要使用运行时权限的Activity或Fragment上 |
@NeedsPermission | ? | 注解在需要调用运行时权限的方法上,当用户给予权限时会执行该方法 |
@OnShowRationale | 注解在用于向用户解释为什么需要调用该权限的方法上:第一次请求权限之前调用返回false;如果用户拒绝了,则下次调用返回true;如果之后再次请求权限时,用户拒绝了并选择了"不再提醒",则下次调用返回false。 | |
@OnPermissionDenied | 注解在当用户拒绝了权限请求时需要调用的方法上 | |
@OnNeverAskAgain | 注解在当用户选中了授权窗口中的不再询问复选框后并拒绝了权限请求时需要调用的方法,一般可以向用户解释为何申请此权限,并根据实际需求决定是否再次弹出权限请求对话框 |
只有 @RuntimePermissions 和 @NeedsPermission 是必须的,其余注解均为可选。
注意:被注解的方法不能是私有方法。
3、当使用了@RuntimePermissions 和 @NeedsPermission 之后,点击"菜单栏→Build→Make Project"重新编译整个项目。
编译完成后,会在 app\build\intermediates\classes\debug 目录下生成一个与被注解的Activity同一个包下的辅助类,名称为"被注解的Activity名称+PermissionsDispatcher.class"。
4、调用辅助类里面的方法
在需要调用权限的位置调用辅助类里面的"xxxWithCheck"静态方法,xxx是被 @NeedsPermission 注解的方法名。如:
Activity1PermissionsDispatcher.createFileWithCheck(Activity1.this)
另外,还需要重写该Activity的onRequestPermissionsResult()方法,其方法内调用辅助类的onRequestPermissionsResult()方法,如:
Activity1PermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
PermissionsDispatcher 完整演示
@RuntimePermissions
public class Activity1 extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv = new TextView(this);
setContentView(tv);
tv.setBackgroundColor(Color.YELLOW);
tv.setOnClickListener(v -> Activity1PermissionsDispatcher.createFileWithCheck(Activity1.this));
}
@NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
void createFile() {
Toast.makeText(this, "【NeedsPermission,用户允许了该权限】", Toast.LENGTH_SHORT).show();
MainActivity.createFileWithoutRequestPermission(Activity1.this);
}
@OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
void doOnPermissionDenied() {
Toast.makeText(this, "【OnPermissionDenied,用户拒绝了该权限】", Toast.LENGTH_SHORT).show();
}
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
void doOnShowRationale(final PermissionRequest request) {
Toast.makeText(this, "【OnShowRationale,此时应该显示请求权限的说明】", Toast.LENGTH_SHORT).show();
// 一般是通过弹一个自定义的对话框告诉用户,我们为什么需要这个权限
new AlertDialog.Builder(this)
.setTitle("请求读写SD卡权限").setMessage("请求SD卡权限,这样我才能给你保存妹子图片哦")
.setPositiveButton("我知道了", (dialog, which) -> request.proceed())
.setNegativeButton("我不需要", (dialog, which) -> request.cancel())
.create().show();
}
@OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
void doOnNeverAskAgain() {
Toast.makeText(this, "你拒绝了读写SD卡权限并选择了\"不再提醒\"," +
"\n已经没法愉快的玩耍了,我将在3秒后关闭!", Toast.LENGTH_SHORT).show();
new Handler().postDelayed(this::finish, 3 * 1000);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Activity1PermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
}
生成的辅助类(注意并没有生成".java"源码)
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package permission.bqt.com.permissiontest;
import android.support.v4.app.ActivityCompat;
import java.lang.ref.WeakReference;
import permission.bqt.com.permissiontest.Activity1;
import permissions.dispatcher.PermissionRequest;
import permissions.dispatcher.PermissionUtils;
final class Activity1PermissionsDispatcher {
private static final int REQUEST_CREATEFILE = 0;
private static final String[] PERMISSION_CREATEFILE = new String[]{"android.permission.WRITE_EXTERNAL_STORAGE"};
private Activity1PermissionsDispatcher() {
}
static void createFileWithCheck(Activity1 target) {
if(PermissionUtils.hasSelfPermissions(target, PERMISSION_CREATEFILE)) {
target.createFile();
} else if(PermissionUtils.shouldShowRequestPermissionRationale(target, PERMISSION_CREATEFILE)) {
target.doOnShowRationale(new Activity1PermissionsDispatcher.CreateFilePermissionRequest(target));
} else {
ActivityCompat.requestPermissions(target, PERMISSION_CREATEFILE, 0);
}
}
static void onRequestPermissionsResult(Activity1 target, int requestCode, int[] grantResults) {
switch(requestCode) {
case 0:
if(PermissionUtils.verifyPermissions(grantResults)) {
target.createFile();
} else if(!PermissionUtils.shouldShowRequestPermissionRationale(target, PERMISSION_CREATEFILE)) {
target.doOnNeverAskAgain();
} else {
target.doOnPermissionDenied();
}
default:
}
}
private static final class CreateFilePermissionRequest implements PermissionRequest {
private final WeakReference<Activity1> weakTarget;
private CreateFilePermissionRequest(Activity1 target) {
this.weakTarget = new WeakReference(target);
}
public void proceed() {
Activity1 target = (Activity1)this.weakTarget.get();
if(target != null) {
ActivityCompat.requestPermissions(target, Activity1PermissionsDispatcher.PERMISSION_CREATEFILE, 0);
}
}
public void cancel() {
Activity1 target = (Activity1)this.weakTarget.get();
if(target != null) {
target.doOnPermissionDenied();
}
}
}
}
2017-7-4
以上是关于permission 新 运行时权限 6.0 总结的主要内容,如果未能解决你的问题,请参考以下文章
何时在运行时请求 Android Marshmallow 6.0 的权限?
Android 6.0 应用权限 -- 与系统权限一起工作(Working with System Permissions) 使用MarkDown重新整理了排版