在 Android 中,什么是权限?
权限是应用访问设备硬件功能(如相机、定位)或用户隐私数据(如通讯录、短信)的授权机制。开发者必须在代码中明确声明,并根据权限类型来确定是否需要动态请求用户授权后才能使用敏感功能。
一、Android 权限基础
权限常见的 2 种类型
- 普通权限:自动授予(如网络访问)
- 危险权限:需用户手动授权(如CAMERA、ACCESS_FINE_LOCATION)
权限组
- 权限组将功能相似的权限归类,例如 STORAGE 组包含读/写外部存储的权限。
- 系统弹窗会显示权限组名称(而非具体权限)。例如申请 READ_EXTERNAL_STORAGE 时,弹窗提示“允许应用访问设备上的照片、媒体内容和文件?”(即 STORAGE 组)
注意:低版本(API < 26)会自动授予同组权限,但高版本需显式请求所有需要的权限
常见的权限和权限类型
无需运行时申请,安装时自动授予,通常不涉及敏感隐私或硬件控制。
INTERNET | 访问互联网(如网络请求、加载网页)。 |
ACCESS_NETWORK_STATE | 获取网络状态(如检测是否联网、Wi-Fi 或移动数据)。 |
ACCESS_WIFI_STATE | 获取 Wi-Fi 连接状态(如扫描可用 Wi-Fi)。 |
需运行时动态申请,涉及用户隐私或硬件敏感操作,按权限组分类
CALENDAR | READ_CALENDAR | 读取日历事件(如查看日程安排)。 |
WRITE_CALENDAR | 修改日历事件(如添加/删除会议)。 | |
CAMERA | CAMERA | 访问摄像头硬件(如拍照、录制视频)。 |
CONTACTS | READ_CONTACTS | 读取联系人信息(如显示通讯录)。 |
WRITE_CONTACTS | 修改联系人信息(如添加/删除联系人)。 | |
LOCATION | ACCESS_FINE_LOCATION | 获取精确位置(如 GPS 定位)。 |
ACCESS_COARSE_LOCATION | 获取粗略位置(如基站/Wi-Fi 定位)。 | |
MICROPHONE | RECORD_AUDIO | 使用麦克风录制音频(如语音消息、通话录音)。 |
PHONE | READ_PHONE_STATE | 获取设备状态(如 IMEI 码、网络类型)。 |
CALL_PHONE | 直接拨打电话(如免提拨号)。 | |
READ_CALL_LOG | 读取通话记录(如显示未接来电)。 | |
WRITE_CALL_LOG | 修改通话记录(如标记已接电话)。 | |
SENSORS | BODY_SENSORS | 访问健康传感器(如心率监测、运动数据)。 |
SMS | SEND_SMS | 发送短信(如验证码自动填写)。 |
RECEIVE_SMS | 接收短信(如读取验证码)。 | |
READ_SMS | 读取短信内容(如备份短信)。 | |
STORAGE | READ_EXTERNAL_STORAGE | 读取外部存储(如访问相册、下载文件)。 |
WRITE_EXTERNAL_STORAGE | 写入外部存储(如保存图片、创建文档)。 |
二、权限的声明与请求
1、声明权限
无论是普通权限还是危险权限,都需要在清单文件中声明。
打开 AndroidManifest.xml 文件,在 manifest 节点内 application 节点外声明权限。
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 相机权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<!--位置权限-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application>
……
</application>
</manifest>
2、判断某个权限是否已授权
注意:普通权限不需要这一步
使用 checkSelfPermission 方法可判断某个权限是否获取了授权,该方法属于 ContextWrapper 类,在 activity 中可以直接调用。
int flag=checkSelfPermission(Manifest.permission.CAMERA);
if (flag== PackageManager.PERMISSION_DENIED){
//权限未授予
toast("权限未授予");
}else {
//权限已授予
toast("权限已授予");
}
如果需要判断某个权限是否被永久拒绝了授权,可以使用
shouldShowRequestPermissionRationale 方法进行判断,如果该权限被永久拒绝的话,只能引导用户去设置中,手动打开该权限。
该方法属于 Activity 类,在 activity 中可以直接调用。
//权限是否被永久拒绝
boolean foreverDenied=shouldShowRequestPermissionRationale(Manifest.permission.CAMERA);
if (foreverDenied){
toast("程序被永久拒绝,需要用户到设置中手动开启");
}else {
toast("程序被拒绝,可以再次申请");
}
3、申请权限
注意:普通权限不需要这一步。
使用 requestPermissions 方法进行动态申请权限,其中第一个参数是权限数组,第二个参数是请求码,int 类型,在
onRequestPermissionsResult 函数中用于区分是哪个请求的回调。该方法属于 activity 类,在 activity 的子类中可以直接调用。
private int PERMISSION_REQUEST_CODE=0;
/**
* 获取相机权限
*/
private void requestCamera(){
//获取权限有两种写法,1、直接使用requestPermissions,然后在onRequestPermissionsResult中处理
requestPermissions(new String[]{Manifest.permission.CAMERA},PERMISSION_REQUEST_CODE);
}
在调用 requestPermissions 方法后,系统会自动将结果回调至
onRequestPermissionsResult 方法中,我们在 activity 的子类中,重写
onRequestPermissionsResult 方法即可。
其中共有三个参数:
第一个参数(int requestCode)是请求码
第二个参数是请求的权限列表
第三个参数是授权结果数组,第二个参数和第三个参数数组长度是一致的,并且授权结果和请求的权限列表位置是一一对应的,由此可以确定哪些权限被授予,哪些权限被拒绝。
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode==PERMISSION_REQUEST_CODE){
List<String>deniedList=new ArrayList<>();
for (int i=0;i<grantResults.length;i++){
int result=grantResults[i];
if (result==PackageManager.PERMISSION_DENIED){
deniedList.add(permissions[i]);
}
}
if (deniedList.size()==0){
toast("权限都被授予");
}else {
toast("有"+deniedList.size()+"个权限被拒绝");
//此时和弹出对话框,对用户说明该权限的重要性,并且在此申请所需的权限
}
}
}
4、处理被永久拒绝的权限
当权限被永久拒绝后,将不会再弹出权限申请框,而是直接返回被拒绝,此时,我们可以跳转至设置页面,让用户手动打开权限。
5、完整代码
public class NativePermissionActivity extends AppCompatActivity {
private int PERMISSION_REQUEST_CODE=0;
ScrollView root;
Button haveCamera;
Button requestCamera;
Button openCamera;
Button requestGroup;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_native_permission);
initView();
initClick();
}
private void initView(){
root=findViewById(R.id.root);
haveCamera=findViewById(R.id.have_camera);
requestCamera=findViewById(R.id.request_camera);
openCamera=findViewById(R.id.open_camera);
requestGroup=findViewById(R.id.request_group);
}
private void initClick(){
//监听系统窗口变化,动态调整视图内边距,以避免内容被系统栏遮挡
ViewCompat.setOnApplyWindowInsetsListener(root, (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
//判断是否已经获取了相机权限
haveCamera.setOnClickListener(v -> {
checkPermission();
});
//获取相机权限
requestCamera.setOnClickListener(v->{
requestCamera();
});
}
/**
* 判断权限
*/
private void checkPermission(){
int flag=checkSelfPermission(Manifest.permission.CAMERA);
if (flag== PackageManager.PERMISSION_DENIED){
//权限未授予
toast("权限未授予");
//权限是否被永久拒绝
boolean foreverDenied=shouldShowRequestPermissionRationale(Manifest.permission.CAMERA);
if (foreverDenied){
toast("程序被永久拒绝,需要用户到设置中手动开启");
}else {
toast("程序被拒绝,可以再次申请");
}
}else {
//权限已授予
toast("权限已授予");
}
}
/**
* 获取相机权限
*/
private void requestCamera(){
//获取权限有两种写法,1、直接使用requestPermissions,然后在onRequestPermissionsResult中处理
requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.ACCESS_FINE_LOCATION},PERMISSION_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode==PERMISSION_REQUEST_CODE){
List<String>deniedList=new ArrayList<>();
for (int i=0;i<grantResults.length;i++){
int result=grantResults[i];
if (result==PackageManager.PERMISSION_DENIED){
deniedList.add(permissions[i]);
}
}
if (deniedList.size()==0){
toast("权限都被授予");
}else {
toast("有"+deniedList.size()+"个权限被拒绝");
//此时和弹出对话框,对用户说明该权限的重要性,并且在此申请所需的权限
}
}
}
private void toast(String msg){
Toast.makeText(this,msg,Toast.LENGTH_SHORT).show();
}
}
一些使用原则
1、在申请权限前,应该先说明权限的用处,为什么要申请权限,让用户有一个比较好的接受心态;
2、最小化权限申请原则,只在必要的时候申请必要的权限
最后
如果嫌自己手动申请比较麻烦,可以使用一些成熟的开源框架。
XXPermissions: https://github.com/getActivity/XXPermissions/blob/master/README.md
PermissionX: https://github.com/guolindev/PermissionX
仓库地址
https://github.com/YDimanche/AndroidLibraryDemo/tree/main/permission
https://gitee.com/ydimanche/AndroidLibraryDemo