2. 动态权限请求

任务说明

本案例实现Android 12(API 31)及以上版本运行时动态请求蓝牙扫描与连接权限,并处理用户授权结果。核心目标:在应用启动后主动弹出系统权限对话框,根据用户选择更新界面状态并给出明确提示。

权限配置

在AndroidManifest.xml中声明蓝牙权限(Android 12+需声明BLUETOOTH_SCAN与BLUETOOTH_CONNECT):

<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
    android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
    android:maxSdkVersion="30" />

核心实现

在MainActivity.java中实现权限请求逻辑。使用ActivityResultContracts.RequestMultiplePermissions统一请求两项权限,避免重复回调:

public class MainActivity extends AppCompatActivity {
    private static final String[] REQUIRED_PERMISSIONS = {
        Manifest.permission.BLUETOOTH_SCAN,
        Manifest.permission.BLUETOOTH_CONNECT
    };

    private final ActivityResultLauncher<String[]> permissionLauncher =
        registerForActivityResult(
            new ActivityResultContracts.RequestMultiplePermissions(),
            result -> {
                boolean scanGranted = Boolean.TRUE.equals(
                    result.get(Manifest.permission.BLUETOOTH_SCAN));
                boolean connectGranted = Boolean.TRUE.equals(
                    result.get(Manifest.permission.BLUETOOTH_CONNECT));

                if (scanGranted && connectGranted) {
                    updateStatus("权限已授予,可以开始蓝牙扫描");
                } else if (!scanGranted) {
                    updateStatus("蓝牙扫描权限被拒绝,无法发现设备");
                } else {
                    updateStatus("蓝牙连接权限被拒绝,无法连接设备");
                }
            });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.btn_request).setOnClickListener(v -> {
            if (hasAllPermissions()) {
                updateStatus("权限已具备,无需重复请求");
            } else {
                permissionLauncher.launch(REQUIRED_PERMISSIONS);
            }
        });
    }

    private boolean hasAllPermissions() {
        for (String permission : REQUIRED_PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission)
                    != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

    private void updateStatus(String message) {
        TextView tvStatus = findViewById(R.id.tv_status);
        tvStatus.setText(message);
    }
}

布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="24dp">

    <Button
        android:id="@+id/btn_request"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="请求蓝牙权限" />

    <TextView
        android:id="@+id/tv_status"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="未请求权限"
        android:textSize="16sp" />
</LinearLayout>

权限请求时序

MainActivity 系统权限管理器 用户 launch(权限数组) 弹出授权对话框 用户点击 "允许"或"拒绝" 回调结果Map<String, Boolean> 检查各权限 更新UI状态 若用户拒绝且勾选"不再询问",需跳转系统设置页(本案例未包含)

验证方法

运行步骤:

  1. 使用Android Studio创建空项目,将上述代码分别填入MainActivity.java、activity_main.xml和AndroidManifest.xml对应位置;
  2. 在Android 12+模拟器或真机上运行应用;
  3. 点击"请求蓝牙权限"按钮。

判定标准:

  • 首次点击时系统弹出权限对话框,分别显示"扫描附近的设备"和"与蓝牙设备配对"两项权限请求;
  • 点击"允许"后,界面文本更新为"权限已授予,可以开始蓝牙扫描";
  • 拒绝任一权限后,界面文本对应显示"蓝牙扫描权限被拒绝"或"蓝牙连接权限被拒绝";
  • 再次点击按钮时,若权限已全部授予,显示"权限已具备,无需重复请求",不再弹出对话框。

预期输出示例:首次点击后系统弹出授权对话框;用户全部允许后,tv_status显示"权限已授予,可以开始蓝牙扫描"。此结果由代码逻辑直接推出,实际表现以系统对话框交互为准。