和以前在安装 APP 的是就申请了权限不同,Google 在 API 23,也就 6.0 之后加入了动态权限。对于一些敏感的权限,决定权交还给了用户,不再是强制申请了。因为这个原因,如果 APP 需要支持 6.0 以上的系统,就需要进行一下适配,否则 APP 就会崩溃。
比如,下面拨打电话.。已经在在 AndroidManifest.xml
中申明权限了
<uses-permission android:name="android.permission.CALL_PHONE"/>
这是拨打电话的代码实现
private void callPhone() {
Intent intent = new Intent(Intent.ACTION_CALL);
Uri data = Uri.parse("tel:" + "12312341234");
intent.setData(data);
startActivity(intent);
}
但是程序如果是在 Android 6.0 的设备中运行的话,一运行就崩溃了,报错信息如下
java.lang.SecurityException: Permission Denial: ... 后面的信息省略
那么要怎么才能适配呢?
动态权限有哪些?
首先要先知道动态权限有哪些?
Andriod 中的动态权限是按组来分的,下面的表格来自官网
每个组中的权限不用全部申请,申请了其中一个,组中其他权限也就一起申请到了。
怎么申请动态权限?
有个前提,我们的 Activity 比如 MainActivity 不再是继承于 Acticity
了,而是继承于 AppCompatActivity
,因为动态权限的一些方法是只有 AppCompatActivity
才有的。
首先是检测是否已经赋予了权限,可以调用 ContextCompat.checkSelfPermission(Context, permissionName)
方法,通过方法的返回值来判断
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.CALL_PHONE)
== PackageManager.PERMISSION_GRANTED) {
// 已经赋予权限,直接调用拨打电话的代码
callPhone();
} else {
// 没有赋予权限,那就去申请权限
}
要申请权限可以使用 requestPermissions (Activity activity, String[] permissions, int requestCode)
进行申请
private static final int REQUEST_CALL_PHONE = 456;
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE},
REQUEST_CALL_PHONE);
于是在界面上就会弹出一个请求拨打电话权限的对话框(这个对话框是系统弹出来的,样式不可修改)
不管用户选择同意还是拒绝,都会在 onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
方法中接收到信息
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_CALL_PHONE:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限请求成功
callPhone();
} else {
// 用户拒绝了
showTipDialog();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
private void showTipDialog() {
new AlertDialog.Builder(this)
.setMessage("该程序需要电话权限,否则无法正常运行")
.setPositiveButton(android.R.string.ok, null)
.create()
.show();
}
如果用户选择”同意“,就可以调用拨打电话的按钮,同时这个对话框之后都不会出现了。
如果用户拒绝了,那就无法调用拨打电话的代码了。为了用户体验,可以弹出一个对话框,告知用户我需要这个权限,没有这个权限程序无法正常运行
用户看到了这个信息,于是再次点击“拨打电话”的按钮,程序又进行了权限检查,弹出申请权限的对话框,这时候的对话框的和之前第一次进行权限申请时弹出的对话框的样式不同,多出了一个“不再提醒”的勾选项
如果用户勾选“不再询问”,对话框是这样的
意味着用户永远地拒绝拨打电话的权限,同时这个对话框不会再弹出来了,这就造成程序无法正常运行。还有办法可以补救一下,就是跳转到设置页面,让用户手动开启权限。Anroid 提供了一个方法 ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)
可以用来判断是否选择了“不再提示”
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_CALL_PHONE:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限请求成功
callPhone();
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
// 用户选择了“拒绝”
showTipDialog();
} else {
// 用户勾选了“不再提示”
goToSetting();
}
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
private void goToSetting() {
new AlertDialog.Builder(this)
.setMessage("该程序需要电话权限,否则无法正常运行")
.setPositiveButton("去打开权限", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
}
})
.create()
.show();
}
完整代码
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CALL_PHONE = 456;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_capture).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
callPhoneWithCheck();
}
});
}
private void callPhone() {
Intent intent = new Intent(Intent.ACTION_CALL);
Uri data = Uri.parse("tel:" + "12312341234");
intent.setData(data);
startActivity(intent);
}
private void callPhoneWithCheck() {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.CALL_PHONE)
== PackageManager.PERMISSION_GRANTED) {
// 已经赋予权限
callPhone();
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE},
REQUEST_CALL_PHONE);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_CALL_PHONE:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限请求成功
callPhone();
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
// 用户选择了“拒绝”
showTipDialog();
} else {
// 用户勾选了“不再提示”
goToSetting();
}
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
private void goToSetting() {
new AlertDialog.Builder(this)
.setMessage("该程序需要电话权限,否则无法正常运行")
.setPositiveButton("去打开权限", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
}
})
.create()
.show();
}
private void showTipDialog() {
new AlertDialog.Builder(this)
.setMessage("该程序需要电话权限,否则无法正常运行")
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE},
REQUEST_CALL_PHONE);
}
})
.create()
.show();
}
}
开源库
为了申请一个拨打电话的权限,要写好多代码。网上有个一个流行的开源库 PermissionsDispatcher,采用注解来快速实现以上的逻辑。用法很简单,就是需要注意到是,写完代码是需要编译一下 project,这样才能生成相应的代码。
参考来源
http://blog.csdn.net/zhangqinghuazhangzhe/article/details/52801202
http://www.jianshu.com/p/d6b3e16cc1d9
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1110/3670.html
http://www.jianshu.com/p/b4a8b3d4f587
http://blog.csdn.net/quan356270259/article/details/50876272
http://blog.csdn.net/u010483016/article/details/50401605
http://www.jianshu.com/p/e1ab1a179fbb#
http://blog.csdn.net/u011200604/article/details/52874599
http://blog.csdn.net/yangqingqo/article/details/48371123