动态申请权限是比较新的功能,
从 Android 6.0(API 级别 23)开始,用户开始在应用运行时向其授予权限,而不是在应用安装时授予。此方法可以简化应用安装过程,因为用户在安装或更新应用时不需要授予权限。它还让用户可以对应用的功能进行更多控制;例如,用户可以选择为相机应用提供相机访问权限,而不提供设备位置的访问权限。用户可以随时进入应用的“Settings”屏幕调用权限。
系统权限分为两类:正常权限和危险权限:
- 正常权限不会直接给用户隐私权带来风险。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。
- 危险权限会授予应用访问用户机密数据的权限。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。如果您列出了危险权限,则用户必须明确批准您的应用使用这些权限。
在运行时请求权限官方文档(需翻墙)
- 检查是否有此权限
- 如果有这个权限,那就可以直接使用,不用再去询问用户
- 如果没有这个权限,那么需要去请求权限,使用的方法:ActivityCompat.requestPermissions()
- 申请权限后返回 会回到 onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) 方法中
下面的案例是选取了两个比较有代表性的案例
案例1:单个权限的动态申请
public class MyPermissionAct extends AppCompatActivity {
// 要申请的权限
private String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
private AlertDialog dialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Assume thisActivity is the current activity
// 版本判断。当手机系统大于 23 时,才有必要去判断权限是否获取
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//checkAccouts();
// 检查该权限是否已经获取
int i = ContextCompat.checkSelfPermission(this, permissions[0]);
// 权限是否已经 授权 GRANTED---授权 DINIED---拒绝
if (i != PackageManager.PERMISSION_GRANTED) {
// 如果没有授予该权限,就去提示用户请求
showDialogTipUserRequestPermission();
}
}
}
// 提示用户该请求权限的弹出框
private void showDialogTipUserRequestPermission() {
new AlertDialog.Builder(this)
.setTitle("存储权限不可用")
.setMessage("由于支付宝需要获取存储空间,为你存储个人信息;\n否则,您将无法正常使用支付宝")
.setPositiveButton("立即开启", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startRequestPermission();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
}).setCancelable(false).show();
}
// 开始提交请求权限
private void startRequestPermission() {
ActivityCompat.requestPermissions(this, permissions, 321);
}
// 用户权限 申请 的回调方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 321) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
// 判断用户是否 点击了不再提醒。(检测该权限是否还可以申请)
boolean b = shouldShowRequestPermissionRationale(permissions[0]);
if (!b) {
// 用户还是想用我的 APP 的
// 提示用户去应用设置界面手动开启权限
showDialogTipUserGoToAppSettting();
} else
finish();
} else {
Toast.makeText(this, "权限获取成功", Toast.LENGTH_SHORT).show();
}
}
}
}
// 提示用户去应用设置界面手动开启权限
private void showDialogTipUserGoToAppSettting() {
dialog = new AlertDialog.Builder(this)
.setTitle("存储权限不可用")
.setMessage("请在-应用设置-权限-中,允许支付宝使用存储权限来保存用户数据")
.setPositiveButton("立即开启", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 跳转到应用设置界面
goToAppSetting();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
}).setCancelable(false).show();
}
// 跳转到当前应用的设置界面
private void goToAppSetting() {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivityForResult(intent, 123);
}
//
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 123) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 检查该权限是否已经获取
int i = ContextCompat.checkSelfPermission(this, permissions[0]);
// 权限是否已经 授权 GRANTED---授权 DINIED---拒绝
if (i != PackageManager.PERMISSION_GRANTED) {
// 提示用户应该去应用设置界面手动开启权限
showDialogTipUserGoToAppSettting();
} else {
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
Toast.makeText(this, "权限获取成功", Toast.LENGTH_SHORT).show();
}
}
}
}
}
案例2:多个权限的动态申请
import android.Manifest;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;
/**
* @author Holly
* @time 2017/10/17 15:16
* @desc ${TODD}
*/
public class MyCountactAct extends AppCompatActivity {
private static final int MY_PERMISSION_REQUEST_CODE = 10000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_coutact);
}
/**
* 点击按钮,将通讯录备份保存到外部存储器备。
*
* 需要3个权限(都是危险权限):
* 1. 读取通讯录权限;
* 2. 读取外部存储器权限;
* 3. 写入外部存储器权限.
*/
public void click(View view) {
/**
* 第 1 步: 检查是否有相应的权限
*/
boolean isAllGranted = checkPermissionAllGranted(
new String[] {
Manifest.permission.READ_CONTACTS,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
}
);
// 如果这3个权限全都拥有, 则直接执行备份代码
if (isAllGranted) {
doBackup();
return;
}
/**
* 第 2 步: 请求权限
*/
// 一次请求多个权限, 如果其他有权限是已经授予的将会自动忽略掉
ActivityCompat.requestPermissions(
this,
new String[] {
Manifest.permission.READ_CONTACTS,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
},
MY_PERMISSION_REQUEST_CODE
);
}
/**
* 检查是否拥有指定的所有权限
*/
private boolean checkPermissionAllGranted(String[] permissions) {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
// 只要有一个权限没有被授予, 则直接返回 false
return false;
}
}
return true;
}
/**
* 第 3 步: 申请权限结果返回处理
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == MY_PERMISSION_REQUEST_CODE) {
boolean isAllGranted = false;
// 判断是否所有的权限都已经授予了
for (int grant : grantResults) {
if (grant != PackageManager.PERMISSION_GRANTED) {
isAllGranted = false;
break;
}
}
if (isAllGranted) {
// 如果所有的权限都授予了, 则执行备份代码
doBackup();
} else {
// 弹出对话框告诉用户需要权限的原因, 并引导用户去应用权限管理中手动打开权限按钮
openAppDetails();
}
}
}
/**
* 第 4 步: 备份通讯录操作
*/
private void doBackup() {
// 本文主旨是讲解如果动态申请权限, 具体备份代码不再展示, 就假装备份一下
Toast.makeText(this, "正在备份通讯录...", Toast.LENGTH_SHORT).show();
}
/**
* 打开 APP 的详情设置
*/
private void openAppDetails() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("备份通讯录需要访问 “通讯录” 和 “外部存储器”,请到 “应用信息 -> 权限” 中授予!");
builder.setPositiveButton("去手动授权", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(Uri.parse("package:" + getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
startActivity(intent);
}
});
builder.setNegativeButton("取消", null);
builder.show();
}
}
正常权限(Normal Permissions)
在API 23中,下面权限被定义为正常权限
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
INSTALL_SHORTCUT
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
REQUEST_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
敏感权限(Dangerous permissions)
Permission Group Permissions
CALENDAR READ_CALENDAR, WRITE_CALENDAR
CAMERA CAMERA
CONTACTS READ_CONTACTS ,WRITE_CONTACTS, GET_ACCOUNTS
LOCATION ACCESS_FINE_LOCATION , ACCESS_COARSE_LOCATION
MICROPHONE RECORD_AUDIO
PHONE READ_PHONE_STATE, CALL_PHONE READ_CALL_LOG , WRITE_CALL_LOG , ADD_VOICEMAIL , USE_SIP, PROCESS_OUTGOING_CALLS
SENSORS BODY_SENSORS
SMS SEND_SMS RECEIVE_SMS , READ_SMS RECEIVE_WAP_PUSH , RECEIVE_MMS
STORAGE READ_EXTERNAL_STORAGE , WRITE_EXTERNAL_STORAGE
网友评论