2020年4月补充
一个简单好用的权限库 —— PermissionGrantor
—— https://github.com/dfqin/PermissionGrantor
一、运行时权限检查 (Permissions at Run Time)
从 Android 6.0(API 级别 23)开始,用户开始在应用运行时向其授予权限,而不是在应用安装时授予。
系统权限分为两类:普通权限 (Normal Permissions),危险权限(Dangerous permissions)
- 敏感权限在运行时每次调用都需要进行检查是否得到授权。
- 用户可以随时进入应用的“Settings”屏幕决定授予任一申请的权限,就是用户可以随便开随便关。
比如,用户可以选择为相机应用提供相机访问权限,而不提供设备位置的访问权限。
二、权限分类
系统权限分为两类:普通权限 (Normal Permissions),危险权限(Dangerous permissions)
正常权限:
不会直接给用户隐私权带来风险。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。危险权限:
会授予应用访问用户机密数据的权限。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。如果您列出了危险权限,则用户必须明确批准您的应用使用这些权限。
注意:不管你的权限是 正常权限 还是 危险权限,都需要在清单列表将其列出来。不过,该声明的影响因系统版本和应用的目标 SDK 级别的不同而有所差异:
—— 对于普通权限,即只要在 AndroidManifest 声明,用户安装了应用就会获取到权限
—— 如果设备运行的是 Android 5.1 或更低版本,或者应用的目标 SDK 为 22 或更低:如果您在清单中列出了危险权限,则用户必须在安装应用时授予此权限;如果他们不授予此权限,系统根本不会安装应用
。
—— 如果设备运行的是 Android 6.0 或更高版本,或者应用的目标 SDK 为 23 或更高:应用必须在清单中列出权限,并且它必须在运行时请求其需要的每项危险权限。用户可以授予或拒绝每项权限,且即使用户拒绝权限请求,应用仍可以继续运行有限的功能
。
.
.
.
运行时权限的具体使用
1、为什么危险权限每次调用都要进行申请?
第一肯定因为他是敏感的。
第二就是因为随时可以在设置里面关闭这个权限,所以每次都需要申请。
比如拍照,这是危险权限,你昨天早上获取了拍照的权限,但是用户可以在昨天下午就把这个权限给关掉,你不能在今天就想当然以为你拥有了拍照权限,所以每次运行都检查是必要的,
2、使用
2.1、怎么申请运行时动态申请权限?
要检查您是否具有某项权限,请调用 [ContextCompat.checkSelfPermission()](https://developer.android.com/reference/android/support/v4/content/ContextCompat.html?hl=zh-cn#checkSelfPermission(android.content.Context, java.lang.String))
方法。例如,以下代码段显示了如何检查 Activity 是否具有拨号的权限:
if (ContextCompat.checkSelfPermission(CallActivity.this,
Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED)
checkSelfPermission
checkSelfPermission会得到一个int类型的返回值
PackageManager.PERMISSION_GRANTED
常量表示已经授权
PackageManager.PERMISSION_DENIED
常量表示 未申请,此时可向用户进行权限申请。
2.2、如何得知申请结果?
在onRequestPermissionsResult
回调方法的得知结果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case REQUESTCODE: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
callNum();
} else {
}
return;
}
}
}
.
.
.
到此为止,我们来利用上面涉及代码做一个拨号的小demo
public class CallActivity extends AppCompatActivity {
private static final int REQUESTCODE = 6001;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_call);
findViewById(R.id.mTvCall).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 动态申请权限 ContextCompat.checkSelfPermission()
if (ContextCompat.checkSelfPermission(CallActivity.this,
Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
// 没有获取权限,那么就申请权限
ActivityCompat.requestPermissions(CallActivity.this,
new String[]{Manifest.permission.CALL_PHONE},
REQUESTCODE);
} else {
// 已经有权限,那么就拨打电话
callNum();
}
}
});
}
private void callNum() {
Intent intent = new Intent(Intent.ACTION_CALL);
Uri data = Uri.parse("tel:" + "10010");
intent.setData(data);
try {
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case REQUESTCODE: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
callNum();
} else {
}
return;
}
}
}
}
清单文件权限
<uses-permission android:name="android.permission.CALL_PHONE"/>
代码非常简单,就是每次拨号都动态检查是否具有拨号权限。
我们会发现情况如下
- 1、当我们授予过权限,那么下次再拨号的时候,就不会再弹出是否授权的提示框
- 2、当我们授予权限,然后又手动到 设置 把该app的拨号权限关闭,那么再次点击拨号的时候会再次弹出 授权提示框 。
- 3、当我们我们点击拒绝授予权限,下次再点击拨号的时候,会多出一个“不在提示(Don‘t ask again)”的选框,如果我们勾选“不在提示”,那么下次点击拨号就不会弹出是否授权的提示框
gif如下
2.3、当被拒接授权之后的再次请求申请 shouldShowRequestPermissionRationale()
我们在运行时申请权限,比如拨打电话,用户的选择很简单,要么同意,要么拒绝。
如果用户同意了,那就万事大吉,但是如果用户拒绝了,而且是“不再提示”的那种拒绝,那么问题就来了,你再点击拨号就什么反应都没有了。
对于这种情况,其实google也已经帮我们想到了,通过shouldShowRequestPermissionRationale()
方法来解决。
shouldShowRequestPermissionRationale()
。如果应用之前请求过此权限但用户拒绝
了请求,此方法将返回 true
。根据这个特点,我们将代码修改一下:
public class CallActivity extends AppCompatActivity {
private static final int REQUESTCODE = 6001;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_call);
findViewById(R.id.mTvCall).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 动态申请权限 ContextCompat.checkSelfPermission()
if (ContextCompat.checkSelfPermission(CallActivity.this,
Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
// shouldShowRequestPermissionRational 解释为什么需要这和权限
if (ActivityCompat.shouldShowRequestPermissionRationale(CallActivity.this,
Manifest.permission.CALL_PHONE)) {
// shouldShowRequestPermissionRationale 返回true,代表之前该权限已经被拒绝,那么针对拒绝做处理,解释为什么需要这个权限
new AlertDialog.Builder(CallActivity.this)
.setMessage("app需要开启权限才能使用此功能")
.setPositiveButton("设置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// 跳转到app设置
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
}
})
.setNegativeButton("取消", null)
.create()
.show();
} else {
// 弹出权限申请框框
ActivityCompat.requestPermissions(CallActivity.this,
new String[]{Manifest.permission.CALL_PHONE},
REQUESTCODE);
}
} else {
callNum();
}
}
});
}
private void callNum() {
Intent intent = new Intent(Intent.ACTION_CALL);
Uri data = Uri.parse("tel:" + "10010");
intent.setData(data);
try {
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case REQUESTCODE: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
callNum();
} else {
}
return;
}
}
}
}
运行gif如下
我们发现,当我们拒绝授权此权限之后,再次执行到申请权限的代码的时候,根据shouldShowRequestPermissionRationale()返回为true,我们弹出提示框解释为什么需要这个权限,并且提供了跳转到设置开始权限的地方,这样问题就得到了解决。
3、注意
1、抽取的callNum方法执行startActivity(intent)这句代码的时候,会报
Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException
这样的编译错误。
其实就是说这句带吗有可能因为未授权抛异常,我们可以利用系统的提示的代码处理一下,
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
startActivity(intent);
但是很显然,没必要,因为我们前面已经处理好了。直接try...catch一下就好
2、环境说明
机器
android 7.1.1
环境
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.amqr.permiss6"
minSdkVersion 14
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
三、第三方库 PermissionsDispatcher
hotchemi/PermissionsDispatcher
就此文时间当前最新版本的2.3.1
步骤1、
project 的gradle的dependencies配置
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
步骤2、
主module配置如下
apply plugin: 'android-apt'
apt 'com.github.hotchemi:permissionsdispatcher-processor:2.3.1'
compile 'com.github.hotchemi:permissionsdispatcher:2.3.1'
步骤三
安装PermissionsDispatcher插件(安装方法类似普通插件安装方法)
安装后怎么使用?
清单文件写上必要的权限,比如
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
然后右键generate…
选择
最后勾选对应权限,选择需要的方法,写上名字,即可自动生成代码
生成的代码和简单使用
@RuntimePermissions
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// storageNeedPer 原本涉及到纯粹权限的代码,我们放到storageNeedPer这个方法里面
MainActivityPermissionsDispatcher.storageNeedPerWithCheck(MainActivity.this);
}
@NeedsPermission({Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE})
void storageNeedPer() {
// 那些权限涉及到存储权限的,写在这里
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
//权限请求回调,提示用户之后,用户点击“允许”或者“拒绝”之后调用此方法
}
@OnShowRationale({Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE})
void storageNeedShowRat(final PermissionRequest request) {
// 解释为什么需要这个权限
}
@OnPermissionDenied({Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE})
void storageDenied() {
// 如果用户不授予某权限时调用的方法,
}
@OnNeverAskAgain({Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE})
void storageAsk() {
//如果用户选择了让设备“不再询问”,而调用的方法
}
}
本文大概就到这里,github上关于权限的库有很多,大家可以自行查找,还有详细的一些权限组啊之类的东西,权限的细节资料,大家也可以自行google。
本来权限就已经被说了很久的事情,也有很多优秀的文章,此文是因为看到清单软件一条信息误标为完成,哈哈哈哈,只是为了完成清单。
四、注意点
- 申请权限是app会进入后台(界面上没体现)
(弹出权限申请框时,其实这个这个时候程序会进入后台,这个我们打印一下application的生命周期就知道了,如果程序涉及到手势,需要根据这个特点可能需要调整下)
.
.
.
参考:
google官方文章
Android 6.0+ 运行时权限探索