一、问题原因
1.安卓安全性变更
Android 12+ 的安全性变更,Google 引入了更严格的 PendingIntent 安全管理,逼迫要求开发者明确指定 PendingIntent 的可变性(Mutable)或不可变性(Immutable)。
但是,在从 Android 14+ (API 34) 开始,FLAG_MUTABLE 和隐式 Intent 的组合会被禁止。因此,在使用静态的广播请求的时间,FLAG_MUTABLE多余,且违反安全规则。
2.关键点
关键在于安卓 14+ 版本的安全计谋变化,导致无法再继承使用 FLAG_MUTABLE。
二、办理办法
1.代码示例
先给出代码示例,供大家参考,然后解释关键点在哪。
MainActivity.kt(Kotlin class)代码示例:
- package com.example.serialportdebugapp
- import android.app.PendingIntent
- import android.content.Intent
- import android.hardware.usb.UsbDevice
- import android.hardware.usb.UsbManager
- import android.os.Bundle
- import android.widget.Button
- import android.widget.TextView
- import android.widget.Toast
- import androidx.appcompat.app.AppCompatActivity
- class MainActivity : AppCompatActivity() {
- private lateinit var statusTextView: TextView
- private lateinit var logTextView: TextView
- private lateinit var checkPortButton: Button
- private lateinit var usbManager: UsbManager
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- // 初始化视图
- statusTextView = findViewById(R.id.statusTextView)
- logTextView = findViewById(R.id.logTextView)
- checkPortButton = findViewById(R.id.checkPortButton)
- // 获取 USB 管理器
- usbManager = getSystemService(USB_SERVICE) as UsbManager
- // 按钮点击事件
- checkPortButton.setOnClickListener {
- detectUsbDevices()
- }
- }
- /**
- * 检测 USB 设备
- */
- private fun detectUsbDevices() {
- val deviceList = usbManager.deviceList
- if (deviceList.isEmpty()) {
- logTextView.append("No USB devices found\n")
- return
- }
- for ((_, device) in deviceList) {
- logTextView.append("Detected device: ${device.deviceName}\n")
- requestPermission(device)
- }
- }
- /**
- * 请求 USB 权限
- */
- private fun requestPermission(device: UsbDevice) {
- val intent = PendingIntent.getBroadcast(
- this, 0, Intent("com.android.example.USB_PERMISSION"),
- PendingIntent.FLAG_IMMUTABLE
- )
- usbManager.requestPermission(device, intent)
- }
- }
复制代码 UsbBroadcastReceiver.kt(Kotlin class)代码示例:
- package com.example.serialportdebugapp
- import android.content.BroadcastReceiver
- import android.content.Context
- import android.content.Intent
- import android.hardware.usb.UsbDevice
- import android.hardware.usb.UsbManager
- import android.util.Log
- class UsbBroadcastReceiver : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- val action = intent.action
- if (action == "com.android.example.USB_PERMISSION") {
- synchronized(this) {
- val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
- if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
- device?.let {
- Log.d("UsbBroadcastReceiver", "Permission granted for device: ${device.deviceName}")
- }
- } else {
- Log.d("UsbBroadcastReceiver", "Permission denied for device: ${device?.deviceName}")
- }
- }
- }
- }
- }
复制代码 AndroidManifest.xml 代码示例(总设置文件)
- <manifest xmlns:android="http://schemas.android.com/apk/res/android">
- <uses-feature android:name="android.hardware.usb.host" />
- <uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/Theme.SerialPortDebugApp">
- <activity
- android:name=".MainActivity"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <!-- 广播接收器,处理 USB 权限 -->
- <receiver android:name=".UsbBroadcastReceiver" android:exported="false">
- <intent-filter>
- <action android:name="com.android.example.USB_PERMISSION" />
- </intent-filter>
- </receiver>
- </application>
- </manifest>
复制代码 build.gradle.kts(:app) 相关依赖代码示例
- plugins {
- alias(libs.plugins.android.application)
- alias(libs.plugins.kotlin.android)
- alias(libs.plugins.kotlin.compose)
- }
- android {
- namespace = "com.example.serialportdebugapp"
- compileSdk = 35
- defaultConfig {
- applicationId = "com.example.serialportdebugapp"
- minSdk = 26
- targetSdk = 35
- versionCode = 1
- versionName = "1.0"
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- }
- buildTypes {
- release {
- isMinifyEnabled = false
- proguardFiles(
- getDefaultProguardFile("proguard-android-optimize.txt"),
- "proguard-rules.pro"
- )
- }
- }
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
- }
- kotlinOptions {
- jvmTarget = "11"
- }
- buildFeatures {
- compose = true
- }
- }
- dependencies {
- implementation(libs.androidx.core.ktx)
- implementation(libs.androidx.lifecycle.runtime.ktx)
- implementation(libs.androidx.activity.compose)
- implementation(platform(libs.androidx.compose.bom))
- implementation(libs.androidx.ui)
- implementation(libs.androidx.ui.graphics)
- implementation(libs.androidx.ui.tooling.preview)
- implementation(libs.androidx.material3)
- implementation(libs.androidx.appcompat)
- testImplementation(libs.junit)
- androidTestImplementation(libs.androidx.junit)
- androidTestImplementation(libs.androidx.espresso.core)
- androidTestImplementation(platform(libs.androidx.compose.bom))
- androidTestImplementation(libs.androidx.ui.test.junit4)
- debugImplementation(libs.androidx.ui.tooling)
- debugImplementation(libs.androidx.ui.test.manifest)
- implementation(libs.purejavacomm)
- }
复制代码 libs.versions.toml 代码示例:
- [versions]
- agp = "8.7.2"
- kotlin = "2.0.0"
- coreKtx = "1.15.0"
- junit = "4.13.2"
- junitVersion = "1.2.1"
- espressoCore = "3.6.1"
- lifecycleRuntimeKtx = "2.8.7"
- activityCompose = "1.8.0"
- composeBom = "2024.04.01"
- appcompat = "1.7.0"
- [libraries]
- androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
- junit = { group = "junit", name = "junit", version.ref = "junit" }
- androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
- androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
- androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
- androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
- androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
- androidx-ui = { group = "androidx.compose.ui", name = "ui" }
- androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
- androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
- androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
- androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
- androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
- androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
- purejavacomm = { group = "com.github.purejavacomm", name = "purejavacomm", version = "1.0.2.RELEASE" }
- androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
- [plugins]
- android-application = { id = "com.android.application", version.ref = "agp" }
- kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
- kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
复制代码 activity_main.xml 代码示例 (APP界面,UI,用于测试USB串口调试APP的界面UI设计)
- <?xml version="1.0" encoding="utf-8"?>
- <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="16dp">
- <TextView
- android:id="@+id/statusTextView"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="USB Status: Not connected"
- android:textSize="18sp" />
- <TextView
- android:id="@+id/logTextView"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Log output:\n"
- android:padding="8dp"
- android:scrollbars="vertical" />
- <Button
- android:id="@+id/checkPortButton"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Check USB Devices" />
- </LinearLayout>
复制代码 2.关键点
关键点在于,MainActivity.kt 中的 这段代码:
- /**
- * 请求 USB 权限
- */
- private fun requestPermission(device: UsbDevice) {
- val intent = PendingIntent.getBroadcast(
- this, 0, Intent("com.android.example.USB_PERMISSION"),
- PendingIntent.FLAG_IMMUTABLE
- )
- usbManager.requestPermission(device, intent)
- }
复制代码 能够看到,在使用 PendingIntent 的时间,我们告知了“FLAG_IMMUTABLE”,这是关键,回到文章最开始的时间我们说到过的:
这是 Android 14+ 的特性,我们只能使用 FLAG_IMMUTABLE,也就是不可变的 PendingIntent。
只要这个写对了,权限被拒绝:[android.permission.READ_EXTERNAL_STORAGE] 的 BUG基本就会被办理。
3.关于 Intent 和 PendingIntent:
Intent 是 Android 中的一种消息对象,用于形貌应用程序要执行的利用。
作用是用来启动 活动(Activity)、服务(Service) 或 广播(Broadcast)。
可以携带数据,以便被启动的组件可以接收到并使用这些数据。
分为显示和隐式:
常见的 Intent 的用途:
PendingIntent 是一种特殊范例的 Intent,可以在 未来 的某个时间由系统或其他应用触发。
它充当一个 “授权”,允许其他应用或系统在您的应用上下文中执行利用。
通常用于将利用 “延迟执行”,而不是立即执行。
适用于一些 异步场景,比方:通知(Notification)的点击事件。定时任务。广播接收器。
说白了,它就似乎嵌入式和VUE中的 “监听”,是用来等待消息的,而不是主动出击。
而且,紧张的是:Intent 是瞬发的,使用后就烧毁。
但是 PendingIntent 是连续的,会一直存在到 被触发、被取消 为止。
Intent 和 PendingIntent 的对比
PendingIntent 的 3 种范例
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |