Android 权限管理实战:运行时申请、ActivityResultLauncher 与设置页授权
目录
- [Android 权限管理实战:运行时申请、ActivityResultLauncher 与设置页授权](#Android 权限管理实战:运行时申请、ActivityResultLauncher 与设置页授权)
- [1. 前言与背景](#1. 前言与背景)
- [1.1 为什么权限管理是 Android 开发基础能力](#1.1 为什么权限管理是 Android 开发基础能力)
- [1.2 Android 权限模型演进](#1.2 Android 权限模型演进)
- [2. 核心概念与判断方法](#2. 核心概念与判断方法)
- [2.1 静态声明与动态申请](#2.1 静态声明与动态申请)
- [2.2 三个关键 API 的职责边界](#2.2 三个关键 API 的职责边界)
- [2.3 经典请求流程调用链](#2.3 经典请求流程调用链)
- [3. 实战流程一:录音与定位权限申请](#3. 实战流程一:录音与定位权限申请)
- [3.1 清单声明与页面准备](#3.1 清单声明与页面准备)
- [3.2 录音权限申请(requestCode = 200)](#3.2 录音权限申请(requestCode = 200))
- [3.3 处理录音权限回调结果](#3.3 处理录音权限回调结果)
- [3.4 定位权限申请与二次解释(requestCode = 100)](#3.4 定位权限申请与二次解释(requestCode = 100))
- [4. 实战流程二:使用 ActivityResultLauncher 请求权限](#4. 实战流程二:使用 ActivityResultLauncher 请求权限)
- [4.1 核心实现](#4.1 核心实现)
- [4.2 结果判断与行为分支](#4.2 结果判断与行为分支)
- [5. 实战流程三:设置页手动授权(安装未知来源应用)](#5. 实战流程三:设置页手动授权(安装未知来源应用))
- [5.1 清单与界面](#5.1 清单与界面)
- [5.2 API 版本兼容与最小版本策略](#5.2 API 版本兼容与最小版本策略)
- [5.3 跳转设置页与回调处理](#5.3 跳转设置页与回调处理)
- [6. 常见权限分类速查](#6. 常见权限分类速查)
- [7. 注意事项与常见坑](#7. 注意事项与常见坑)
- [8. 小结](#8. 小结)
- [9. 相关代码附录](#9. 相关代码附录)
- [9.1 MyPermissionActivity](#9.1 MyPermissionActivity)
- [9.2 ARLauncherActivity](#9.2 ARLauncherActivity)
- [9.3 SettingsPremissionActivity](#9.3 SettingsPremissionActivity)
- [9.4 AndroidManifest 权限声明片段](#9.4 AndroidManifest 权限声明片段)
- [9.5 布局文件片段](#9.5 布局文件片段)
1. 前言与背景
1.1 为什么权限管理是 Android 开发基础能力
Android 设备中包含联系人、位置、麦克风、媒体文件等敏感数据。权限系统的核心目标是:
- 将敏感能力的使用权交给用户决策。
- 限制应用在未授权状态下访问隐私能力。
- 让权限申请与具体功能形成可解释的对应关系。
权限处理质量直接影响功能可用性、审核通过率与用户信任。
1.2 Android 权限模型演进
| Android 版本 | 权限模型变化 | 典型影响 |
|---|---|---|
| 6.0 之前 | 安装时一次性授权 | 用户不同意即无法安装 |
| 6.0 | 引入运行时权限 | 敏感权限需在使用时动态申请 |
| 8.0 | 后台位置能力限制增强 | 后台定位策略需更谨慎 |
| 10 | 位置权限新增"仅使用时允许" | 前后台访问差异更明显 |
| 11 | 一次性权限 | 应用需处理权限短周期失效 |
| 12+ | 隐私仪表盘与摄像头/麦克风指示器 | 权限使用更透明 |
| 13+ | 媒体权限细分(图/视频/音频) | 不再依赖单一外部存储读取权限 |
2. 核心概念与判断方法
2.1 静态声明与动态申请
静态声明用于告诉系统"应用可能使用某能力",动态申请用于在运行时获得用户授权,两者缺一不可。
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 请求网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 录音权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 读取内存卡、读取文件 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 获取精确位置信息 -->
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"
tools:ignore="CoarseFineLocation" />
<!--安装apk的权限-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
AndroidComponentByJavaProject/app/src/main/AndroidManifest.xml
2.2 三个关键 API 的职责边界
| API | 作用 | 典型返回语义 |
|---|---|---|
checkSelfPermission() |
判断当前权限是否已授予 | PERMISSION_GRANTED / PERMISSION_DENIED |
requestPermissions() |
触发系统权限弹窗(传统方式) | 异步回调到 onRequestPermissionsResult() |
shouldShowRequestPermissionRationale() |
判断是否应先展示权限说明 | true 多见于曾拒绝但未勾选"不再询问";false 多见于首次请求或勾选"不再询问"后 |
2.3 经典请求流程调用链
是
否
true
false
是
否且可再询问
否且不再询问
触发功能按钮
checkSelfPermission 是否已授权
直接执行业务
shouldShowRequestPermissionRationale
展示解释弹窗
requestPermissions
onRequestPermissionsResult
是否授权
执行业务
提示拒绝
引导到设置页
3. 实战流程一:录音与定位权限申请
3.1 清单声明与页面准备
布局包含两个按钮,分别用于录音权限和定位权限触发:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".activity.perrmission.MyPermissionActivity">
<Button
android:id="@+id/btn_record"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="权限录音申请" />
<Button
android:id="@+id/btn_location"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="定位权限申请" />
</LinearLayout>
AndroidComponentByJavaProject/app/src/main/res/layout/activity_my_permission.xml
3.2 录音权限申请(requestCode = 200)
java
findViewById(R.id.btn_record).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//录音权限
int selfPermission = checkSelfPermission(Manifest.permission.RECORD_AUDIO);
//PackageManager.PERMISSION_DENIED;表示还没有获取到权限
if (selfPermission != PackageManager.PERMISSION_GRANTED) {
//权限没有授予,需要在这里调用权限申请
requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 200);
} else {
//权限已获取
Toast.makeText(MyPermissionActivity.this, "已获取录音权限", Toast.LENGTH_SHORT).show();
}
}
});
AndroidComponentByJavaProject/app/src/main/java/com/ls/acbjp/activity/perrmission/MyPermissionActivity.java
3.3 处理录音权限回调结果
java
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 200) {
//在这里检查是否获取权限
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//可以直接开启录音操作
Toast.makeText(MyPermissionActivity.this, "已获取录音权限", Toast.LENGTH_SHORT).show();
} else {
//用户拒绝权限,或者是权限获取失败
//用于判断是否应该向用户展示权限申请的说明对话框,向用户解释为什么需要这个权限
boolean b = shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO);
if (!b) {
//用户勾选了"不在询问"
//让用户自行前往设置页面手动授权
new AlertDialog.Builder(this)
.setTitle("需要麦克风权限")
.setMessage("应用需要麦克风权限来录音,请前往设置开启权限。")
.setPositiveButton("前往设置", (dialog, which) -> {
// 引导用户前往设置页面
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
})
.setNegativeButton("取消", null)
.show();
} else {
//用户拒绝了权限,但是没有勾选"不再询问"
Toast.makeText(MyPermissionActivity.this, "已拒绝录音权限", Toast.LENGTH_SHORT).show();
}
}
}
弹出系统定位权限弹窗的代码是这一句:
activityResultLauncher2.launch("android.permission.ACCESS_FINE_LOCATION");
launch(...)执行后,ActivityResultContracts.RequestPermission( ) 会调用系统权限申请流程,如果当前还没拿到定位权限,系统就会弹出授权框。
这几部分的关系是:
1. `registerForActivityResult(new ActivityResultContracts.RequestPermission(), ...)`
这里注册了一个"申请单个权限"的启动器。
2. `activityResultLauncher2.launch("android.permission.ACCESS_FINE_LOCATION")`
这里真正发起权限申请,也就是触发系统弹窗的地方。
3. `onActivityResult(Boolean o)`
这里不是弹窗代码,而是用户在系统弹窗里点"允许"或"拒绝"之后的结果回调。
如果你想看得更直白,可以理解成:
registerForActivityResult(...)= 先准备好一个权限申请器launch(...)= 点击按钮后正式弹出系统权限框onActivityResult(...)= 接收弹窗操作结果
当用户选择"不再询问"后,再次触发申请不会再出现系统权限弹窗,此时应改为引导到系统设置页完成授权。


3.4 定位权限申请与二次解释(requestCode = 100)
定位权限流程使用"先解释后申请"的分支处理:
java
findViewById(R.id.btn_location).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取位置权限
//返回true:一般是因为之前用户拒绝过了这个权限,但是没勾选不在询问
//返回false:有可能是第一次请求权限,或者是用户选择了"不再询问"
boolean showRequestPermissionRationale
= shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION);
if (showRequestPermissionRationale) {
new AlertDialog.Builder(MyPermissionActivity.this)
.setTitle("权限请求")
.setMessage("我接下来,需要获取你的定位,因为我要跟踪你!")
.setPositiveButton("确定", (dialog, which) -> {
//再次弹出权限申请
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 100);
})
.setNegativeButton("取消", null)
.show();
} else {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 100);
}
}
});
AndroidComponentByJavaProject/app/src/main/java/com/ls/acbjp/activity/perrmission/MyPermissionActivity.java
定位权限在回调中的处理结构与录音权限一致,差异点是请求码与提示文案:
java
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 200) {
// .......
} else if (requestCode == 100) {
//在这里检查是否获取权限
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//可以直接开启录音操作
Toast.makeText(MyPermissionActivity.this, "已获取位置权限", Toast.LENGTH_SHORT).show();
} else {
//用户拒绝权限,或者是权限获取失败
//用于判断是否应该向用户展示权限申请的说明对话框,向用户解释为什么需要这个权限
boolean b = shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO);
if (!b) {
//用户勾选了"不在询问"
//让用户自行前往设置页面手动授权
new AlertDialog.Builder(this)
.setTitle("需要位置权限")
.setMessage("应用需要位置权限,请前往设置开启权限。")
.setPositiveButton("前往设置", (dialog, which) -> {
// 引导用户前往设置页面
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
})
.setNegativeButton("取消", null)
.show();
} else {
//用户拒绝了权限,但是没有勾选"不再询问"
Toast.makeText(MyPermissionActivity.this, "已拒绝位置权限", Toast.LENGTH_SHORT).show();
}
}
}
}
说明:该分支中 shouldShowRequestPermissionRationale(ACCESS_FINE_LOCATION) 与定位场景 requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 100) 一致,实际使用时应确保判断权限与当前请求权限一致,否则会影响分支判断准确性。
4. 实战流程二:使用 ActivityResultLauncher 请求权限
4.1 核心实现
ActivityResultLauncher 用于替代传统 onRequestPermissionsResult() 分发方式,回调更聚焦。
java
public class ARLauncherActivity extends AppCompatActivity {
//注册permission回调函数,用户处理完权限,会触发onActivityResult回调方法
private ActivityResultLauncher<String> activityResultLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
new ActivityResultCallback<Boolean>() {
@Override
public void onActivityResult(Boolean o) {
if (o) {
//同意授权
Toast.makeText(ARLauncherActivity.this, "同意", Toast.LENGTH_SHORT).show();
} else {
//不同意
if (!shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {
Toast.makeText(ARLauncherActivity.this, "拒绝了权限,勾选了不再询问", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(ARLauncherActivity.this, "拒绝了权限,没有勾选不再询问", Toast.LENGTH_SHORT).show();
}
}
}
}
);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_arlauncher);
findViewById(R.id.btn_location).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
//没有获取到权限
activityResultLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE);
}
}
});
}
}
AndroidComponentByJavaProject/app/src/main/java/com/ls/acbjp/activity/perrmission/ARLauncherActivity.java
4.2 结果判断与行为分支
系统权限框 ARLauncherActivity 用户 系统权限框 ARLauncherActivity 用户 alt [isGranted = true] [isGranted = false] 点击按钮 checkSelfPermission() activityResultLauncher.launch(permission) Boolean isGranted Toast("同意") shouldShowRequestPermissionRationale() 区分"拒绝"与"拒绝且不再询问"
对应布局如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".activity.perrmission.ARLauncherActivity">
<Button
android:id="@+id/btn_location"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="定位权限申请" />
</LinearLayout>
AndroidComponentByJavaProject/app/src/main/res/layout/activity_arlauncher.xml
5. 实战流程三:设置页手动授权(安装未知来源应用)
5.1 清单与界面
该权限无法在应用内直接通过普通弹窗授予,需要跳转系统设置页。
xml
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.perrmission.SettingsPremissionActivity">
<Button
android:id="@+id/btn_install"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="获取安装APP权限"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="暂未获取安装app的权限"
app:layout_constraintEnd_toEndOf="@id/btn_install"
app:layout_constraintStart_toStartOf="@id/btn_install"
app:layout_constraintTop_toBottomOf="@id/btn_install" />
</androidx.constraintlayout.widget.ConstraintLayout>
AndroidComponentByJavaProject/app/src/main/res/layout/activity_settings_premission.xml
5.2 API 版本兼容与最小版本策略
需要先做系统版本判断:
java
boolean installs = true;
//如果当前设备版本大于等于8.0,就可以这么判断
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
//是否拥有安装apk的权限
installs = getPackageManager().canRequestPackageInstalls();
}
在构建配置中,minSdk 决定可安装设备下限;当使用高 API 能力时,需要保证版本判断与调用点匹配。

xml
android {
namespace 'com.ls.acbjp'
compileSdk 34
defaultConfig {
applicationId "com.ls.acbjp"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
5.3 跳转设置页与回调处理
java
findViewById(R.id.btn_install).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean installs = canInstanllPermission();
//默认8.0以下的设备不需要申请权限,可以直接获取安装
//如果installs = true,可能是8.0以下的设备,也可能是已经有了权限
if (installs) {
Toast.makeText(SettingsPremissionActivity.this, "已拥有权限", Toast.LENGTH_SHORT).show();
} else {
//在这里执行申请权限的流程
//跳转到设置页面手动授权
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
//拼接一个字符串,指定当前需要跳转的app设置页面的包名 package:com.ls.acbjp
String uriString = "package:" + getPackageName();
intent.setData(Uri.parse(uriString));
activityResultLauncher.launch(intent);
}
}
});
// ctrl + alt + m
private boolean canInstanllPermission() {
boolean installs = true;
//如果当前设备版本大于等于8.0,就可以这么判断
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
//是否拥有安装apk的权限
installs = getPackageManager().canRequestPackageInstalls();
}
return installs;
}
private ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_CANCELED) {
//用户取消了操作,用户关闭的权限
Toast.makeText(SettingsPremissionActivity.this,
"授权失败", Toast.LENGTH_SHORT).show();
} else if (result.getResultCode() == Activity.RESULT_OK) {
//授权成功
if (canInstanllPermission()) {
Toast.makeText(SettingsPremissionActivity.this,
"授权成功", Toast.LENGTH_SHORT).show();
textView.setText(canInstanllPermission() ? "已获取app安装权限" : "还未获取app安装权限");
}
}
}
}
);
AndroidComponentByJavaProject/app/src/main/java/com/ls/acbjp/activity/perrmission/SettingsPremissionActivity.java
6. 常见权限分类速查
| 类别 | 示例权限 | 申请方式 |
|---|---|---|
| 一般权限(normal) | INTERNET、ACCESS_NETWORK_STATE、VIBRATE |
清单声明后通常自动授予 |
| 运行时权限(dangerous) | ACCESS_FINE_LOCATION、CAMERA、RECORD_AUDIO、READ_CONTACTS |
清单声明 + 运行时请求 |
| 设置页授权权限 | REQUEST_INSTALL_PACKAGES、SYSTEM_ALERT_WINDOW、WRITE_SETTINGS、MANAGE_EXTERNAL_STORAGE |
跳转系统设置页,用户手动开关 |
可选硬件声明(避免无硬件设备无法安装):
xml
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
对应运行时检测:
java
if (getApplicationContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_CAMERA_FRONT)) {
// 有摄像头,可以继续正常执行代码
} else {
// 没有摄像头,不要调用相机相关代码,做个提示就行了
Toast.makeText(RPermissionActivity.this, "你没相机,用不了!", Toast.LENGTH_SHORT).show();
}
7. 注意事项与常见坑
shouldShowRequestPermissionRationale()返回false并不只代表"不再询问",首次请求时也可能为false。- 权限分支判断必须与当前请求权限一致,避免"申请 A,判断 B"导致错误分流。
- Android 13+ 媒体访问应优先按图片/视频/音频细分权限申请。
- 设置页授权类权限无法替代普通运行时权限,两类流程应分别实现。
- 涉及高 API 方法时,必须做
SDK_INT判断,避免低版本崩溃。 - 权限申请文案要与功能直接相关,避免一次性批量申请多个无关权限。
8. 小结
本文围绕三类常见权限场景构建了完整闭环:
- 运行时权限(录音、定位)的标准请求与拒绝分流。
ActivityResultLauncher方式的简化回调处理。- 需要系统设置页手动授权的特殊权限处理。
在实际开发中,稳定的权限策略应同时覆盖四个维度:请求时机、解释文案、拒绝分流、版本兼容。这样才能在功能可用性与用户体验之间取得平衡。
9. 相关代码附录
9.1 MyPermissionActivity
java
/**
* 权限管理与申请
*/
public class MyPermissionActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_permission);
findViewById(R.id.btn_record).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//录音权限
int selfPermission = checkSelfPermission(Manifest.permission.RECORD_AUDIO);
//PackageManager.PERMISSION_DENIED;表示还没有获取到权限
if (selfPermission != PackageManager.PERMISSION_GRANTED) {
//权限没有授予,需要在这里调用权限申请
requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 200);
} else {
//权限已获取
Toast.makeText(MyPermissionActivity.this, "已获取录音权限", Toast.LENGTH_SHORT).show();
}
}
});
findViewById(R.id.btn_location).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取位置权限
//返回true:一般是因为之前用户拒绝过了这个权限,但是没勾选不在询问
//返回false:有可能是第一次请求权限,或者是用户选择了"不再询问"
boolean showRequestPermissionRationale
= shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION);
if (showRequestPermissionRationale) {
new AlertDialog.Builder(MyPermissionActivity.this)
.setTitle("权限请求")
.setMessage("我接下来,需要获取你的定位,因为我要跟踪你!")
.setPositiveButton("确定", (dialog, which) -> {
//再次弹出权限申请
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 100);
})
.setNegativeButton("取消", null)
.show();
} else {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 100);
}
}
});
}
/**
* 处理用户返回的权限授予结果
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 200) {
//在这里检查是否获取权限
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//可以直接开启录音操作
Toast.makeText(MyPermissionActivity.this, "已获取录音权限", Toast.LENGTH_SHORT).show();
} else {
//用户拒绝权限,或者是权限获取失败
//用于判断是否应该向用户展示权限申请的说明对话框,向用户解释为什么需要这个权限
boolean b = shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO);
if (!b) {
//用户勾选了"不在询问"
//让用户自行前往设置页面手动授权
new AlertDialog.Builder(this)
.setTitle("需要麦克风权限")
.setMessage("应用需要麦克风权限来录音,请前往设置开启权限。")
.setPositiveButton("前往设置", (dialog, which) -> {
// 引导用户前往设置页面
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
})
.setNegativeButton("取消", null)
.show();
} else {
//用户拒绝了权限,但是没有勾选"不再询问"
Toast.makeText(MyPermissionActivity.this, "已拒绝录音权限", Toast.LENGTH_SHORT).show();
}
}
} else if (requestCode == 100) {
//在这里检查是否获取权限
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//可以直接开启录音操作
Toast.makeText(MyPermissionActivity.this, "已获取位置权限", Toast.LENGTH_SHORT).show();
} else {
//用户拒绝权限,或者是权限获取失败
//用于判断是否应该向用户展示权限申请的说明对话框,向用户解释为什么需要这个权限
boolean b = shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO);
if (!b) {
//用户勾选了"不在询问"
//让用户自行前往设置页面手动授权
new AlertDialog.Builder(this)
.setTitle("需要位置权限")
.setMessage("应用需要位置权限,请前往设置开启权限。")
.setPositiveButton("前往设置", (dialog, which) -> {
// 引导用户前往设置页面
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
})
.setNegativeButton("取消", null)
.show();
} else {
//用户拒绝了权限,但是没有勾选"不再询问"
Toast.makeText(MyPermissionActivity.this, "已拒绝位置权限", Toast.LENGTH_SHORT).show();
}
}
}
}
}
AndroidComponentByJavaProject/app/src/main/java/com/ls/acbjp/activity/perrmission/MyPermissionActivity.java
9.2 ARLauncherActivity
java
/**
* ActivityResultLauncher请求权限
*/
public class ARLauncherActivity extends AppCompatActivity {
//注册permission回调函数,用户处理完权限,会触发onActivityResult回调方法
private ActivityResultLauncher<String> activityResultLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
new ActivityResultCallback<Boolean>() {
@Override
public void onActivityResult(Boolean o) {
if (o) {
//同意授权
Toast.makeText(ARLauncherActivity.this, "同意", Toast.LENGTH_SHORT).show();
} else {
//不同意
if (!shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {
Toast.makeText(ARLauncherActivity.this, "拒绝了权限,勾选了不再询问", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(ARLauncherActivity.this, "拒绝了权限,没有勾选不再询问", Toast.LENGTH_SHORT).show();
}
}
}
}
);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_arlauncher);
findViewById(R.id.btn_location).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
//没有获取到权限
activityResultLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE);
}
}
});
}
}
AndroidComponentByJavaProject/app/src/main/java/com/ls/acbjp/activity/perrmission/ARLauncherActivity.java
9.3 SettingsPremissionActivity
java
/**
* 需要到设置页授权的权限
*/
public class SettingsPremissionActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings_premission);
findViewById(R.id.btn_install).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean installs = canInstanllPermission();
//默认8.0以下的设备不需要申请权限,可以直接获取安装
//如果installs = true,可能是8.0以下的设备,也可能是已经有了权限
if (installs) {
Toast.makeText(SettingsPremissionActivity.this, "已拥有权限", Toast.LENGTH_SHORT).show();
} else {
//在这里执行申请权限的流程
//跳转到设置页面手动授权
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
//拼接一个字符串,指定当前需要跳转的app设置页面的包名 package:com.ls.acbjp
String uriString = "package:" + getPackageName();
intent.setData(Uri.parse(uriString));
activityResultLauncher.launch(intent);
// startActivity(intent);
}
}
});
textView = findViewById(R.id.tv_info);
textView.setText(canInstanllPermission() ? "已获取app安装权限" : "还未获取app安装权限");
}
/**
* @return 是否拥有安装app权限
*/
private boolean canInstanllPermission() {
boolean installs = true;
//如果当前设备版本大于等于8.0,就可以这么判断
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
//是否拥有安装apk的权限
installs = getPackageManager().canRequestPackageInstalls();
}
return installs;
}
private ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_CANCELED) {
//用户取消了操作,用户关闭的权限
Toast.makeText(SettingsPremissionActivity.this,
"授权失败", Toast.LENGTH_SHORT).show();
} else if (result.getResultCode() == Activity.RESULT_OK) {
//授权成功
if (canInstanllPermission()) {
Toast.makeText(SettingsPremissionActivity.this,
"授权成功", Toast.LENGTH_SHORT).show();
textView.setText(canInstanllPermission() ? "已获取app安装权限" : "还未获取app安装权限");
}
}
}
}
);
}
AndroidComponentByJavaProject/app/src/main/java/com/ls/acbjp/activity/perrmission/SettingsPremissionActivity.java
9.4 AndroidManifest 权限声明片段
xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 请求网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 录音权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 读取内存卡、读取文件 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 获取精确位置信息 -->
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"
tools:ignore="CoarseFineLocation" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/><!--安装apk的权限-->
</manifest>
AndroidComponentByJavaProject/app/src/main/AndroidManifest.xml
9.5 布局文件片段
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".activity.perrmission.ARLauncherActivity">
<Button
android:id="@+id/btn_location"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="定位权限申请" />
</LinearLayout>
AndroidComponentByJavaProject/app/src/main/res/layout/activity_arlauncher.xml