Android 权限管理实战:运行时申请、ActivityResultLauncher 与设置页授权

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) INTERNETACCESS_NETWORK_STATEVIBRATE 清单声明后通常自动授予
运行时权限(dangerous) ACCESS_FINE_LOCATIONCAMERARECORD_AUDIOREAD_CONTACTS 清单声明 + 运行时请求
设置页授权权限 REQUEST_INSTALL_PACKAGESSYSTEM_ALERT_WINDOWWRITE_SETTINGSMANAGE_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. 注意事项与常见坑

  1. shouldShowRequestPermissionRationale() 返回 false 并不只代表"不再询问",首次请求时也可能为 false
  2. 权限分支判断必须与当前请求权限一致,避免"申请 A,判断 B"导致错误分流。
  3. Android 13+ 媒体访问应优先按图片/视频/音频细分权限申请。
  4. 设置页授权类权限无法替代普通运行时权限,两类流程应分别实现。
  5. 涉及高 API 方法时,必须做 SDK_INT 判断,避免低版本崩溃。
  6. 权限申请文案要与功能直接相关,避免一次性批量申请多个无关权限。

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

相关推荐
xiaoye37081 小时前
Spring中使用自定义@Lock 注解解决线程并发问题
java·spring·wpf
FreeFly辉2 小时前
VScode搭建javaDemo
java·vscode
知我Deja_Vu2 小时前
【避坑指南】ConcurrentHashMap 并发操作的致命陷阱
java·开发语言
未来之窗软件服务2 小时前
自己写算法(十)js加密UUID保护解密——东方仙盟化神期
java·javascript·算法·代码加密·东方仙盟算法
lang201509282 小时前
08 ByteBuddy 加载策略全解析:从“隔离”到“注入”,如何避开循环依赖的深坑?
java·byte buddy
橙子199110162 小时前
Android 中的权限申请
android
沙漏无语2 小时前
(一)TiDB简介
java·开发语言·tidb
2501_915921432 小时前
iOS APP上架工具,在没有 Mac 的环境中发布苹果应用
android·macos·ios·小程序·uni-app·iphone·webview
Chan162 小时前
LeetCode 热题 100 | 链表
java·数据结构·spring boot·算法·leetcode·链表·java-ee