【AndroidAPP】权限被拒绝:[android.permission.READ_EXTERNAL_STORAGE],U ...

我可以不吃啊  论坛元老 | 2025-4-26 04:24:11 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 2039|帖子 2039|积分 6117

一、问题原因


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)代码示例:
  1. package com.example.serialportdebugapp
  2. import android.app.PendingIntent
  3. import android.content.Intent
  4. import android.hardware.usb.UsbDevice
  5. import android.hardware.usb.UsbManager
  6. import android.os.Bundle
  7. import android.widget.Button
  8. import android.widget.TextView
  9. import android.widget.Toast
  10. import androidx.appcompat.app.AppCompatActivity
  11. class MainActivity : AppCompatActivity() {
  12.     private lateinit var statusTextView: TextView
  13.     private lateinit var logTextView: TextView
  14.     private lateinit var checkPortButton: Button
  15.     private lateinit var usbManager: UsbManager
  16.     override fun onCreate(savedInstanceState: Bundle?) {
  17.         super.onCreate(savedInstanceState)
  18.         setContentView(R.layout.activity_main)
  19.         // 初始化视图
  20.         statusTextView = findViewById(R.id.statusTextView)
  21.         logTextView = findViewById(R.id.logTextView)
  22.         checkPortButton = findViewById(R.id.checkPortButton)
  23.         // 获取 USB 管理器
  24.         usbManager = getSystemService(USB_SERVICE) as UsbManager
  25.         // 按钮点击事件
  26.         checkPortButton.setOnClickListener {
  27.             detectUsbDevices()
  28.         }
  29.     }
  30.     /**
  31.      * 检测 USB 设备
  32.      */
  33.     private fun detectUsbDevices() {
  34.         val deviceList = usbManager.deviceList
  35.         if (deviceList.isEmpty()) {
  36.             logTextView.append("No USB devices found\n")
  37.             return
  38.         }
  39.         for ((_, device) in deviceList) {
  40.             logTextView.append("Detected device: ${device.deviceName}\n")
  41.             requestPermission(device)
  42.         }
  43.     }
  44.     /**
  45.      * 请求 USB 权限
  46.      */
  47.     private fun requestPermission(device: UsbDevice) {
  48.         val intent = PendingIntent.getBroadcast(
  49.             this, 0, Intent("com.android.example.USB_PERMISSION"),
  50.             PendingIntent.FLAG_IMMUTABLE
  51.         )
  52.         usbManager.requestPermission(device, intent)
  53.     }
  54. }
复制代码
UsbBroadcastReceiver.kt(Kotlin class)代码示例:
  1. package com.example.serialportdebugapp
  2. import android.content.BroadcastReceiver
  3. import android.content.Context
  4. import android.content.Intent
  5. import android.hardware.usb.UsbDevice
  6. import android.hardware.usb.UsbManager
  7. import android.util.Log
  8. class UsbBroadcastReceiver : BroadcastReceiver() {
  9.     override fun onReceive(context: Context, intent: Intent) {
  10.         val action = intent.action
  11.         if (action == "com.android.example.USB_PERMISSION") {
  12.             synchronized(this) {
  13.                 val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
  14.                 if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
  15.                     device?.let {
  16.                         Log.d("UsbBroadcastReceiver", "Permission granted for device: ${device.deviceName}")
  17.                     }
  18.                 } else {
  19.                     Log.d("UsbBroadcastReceiver", "Permission denied for device: ${device?.deviceName}")
  20.                 }
  21.             }
  22.         }
  23.     }
  24. }
复制代码
AndroidManifest.xml 代码示例(总设置文件)
  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android">
  2.     <uses-feature android:name="android.hardware.usb.host" />
  3.     <uses-permission android:name="android.permission.INTERNET" />
  4.     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
  5.     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  6.     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  7.     <application
  8.         android:allowBackup="true"
  9.         android:icon="@mipmap/ic_launcher"
  10.         android:label="@string/app_name"
  11.         android:roundIcon="@mipmap/ic_launcher_round"
  12.         android:supportsRtl="true"
  13.         android:theme="@style/Theme.SerialPortDebugApp">
  14.         <activity
  15.             android:name=".MainActivity"
  16.             android:exported="true">
  17.             <intent-filter>
  18.                 <action android:name="android.intent.action.MAIN" />
  19.                 <category android:name="android.intent.category.LAUNCHER" />
  20.             </intent-filter>
  21.         </activity>
  22.         <!-- 广播接收器,处理 USB 权限 -->
  23.         <receiver android:name=".UsbBroadcastReceiver" android:exported="false">
  24.             <intent-filter>
  25.                 <action android:name="com.android.example.USB_PERMISSION" />
  26.             </intent-filter>
  27.         </receiver>
  28.     </application>
  29. </manifest>
复制代码
build.gradle.kts(:app) 相关依赖代码示例
  1. plugins {
  2.     alias(libs.plugins.android.application)
  3.     alias(libs.plugins.kotlin.android)
  4.     alias(libs.plugins.kotlin.compose)
  5. }
  6. android {
  7.     namespace = "com.example.serialportdebugapp"
  8.     compileSdk = 35
  9.     defaultConfig {
  10.         applicationId = "com.example.serialportdebugapp"
  11.         minSdk = 26
  12.         targetSdk = 35
  13.         versionCode = 1
  14.         versionName = "1.0"
  15.         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
  16.     }
  17.     buildTypes {
  18.         release {
  19.             isMinifyEnabled = false
  20.             proguardFiles(
  21.                 getDefaultProguardFile("proguard-android-optimize.txt"),
  22.                 "proguard-rules.pro"
  23.             )
  24.         }
  25.     }
  26.     compileOptions {
  27.         sourceCompatibility = JavaVersion.VERSION_11
  28.         targetCompatibility = JavaVersion.VERSION_11
  29.     }
  30.     kotlinOptions {
  31.         jvmTarget = "11"
  32.     }
  33.     buildFeatures {
  34.         compose = true
  35.     }
  36. }
  37. dependencies {
  38.     implementation(libs.androidx.core.ktx)
  39.     implementation(libs.androidx.lifecycle.runtime.ktx)
  40.     implementation(libs.androidx.activity.compose)
  41.     implementation(platform(libs.androidx.compose.bom))
  42.     implementation(libs.androidx.ui)
  43.     implementation(libs.androidx.ui.graphics)
  44.     implementation(libs.androidx.ui.tooling.preview)
  45.     implementation(libs.androidx.material3)
  46.     implementation(libs.androidx.appcompat)
  47.     testImplementation(libs.junit)
  48.     androidTestImplementation(libs.androidx.junit)
  49.     androidTestImplementation(libs.androidx.espresso.core)
  50.     androidTestImplementation(platform(libs.androidx.compose.bom))
  51.     androidTestImplementation(libs.androidx.ui.test.junit4)
  52.     debugImplementation(libs.androidx.ui.tooling)
  53.     debugImplementation(libs.androidx.ui.test.manifest)
  54.     implementation(libs.purejavacomm)
  55. }
复制代码
libs.versions.toml 代码示例:
  1. [versions]
  2. agp = "8.7.2"
  3. kotlin = "2.0.0"
  4. coreKtx = "1.15.0"
  5. junit = "4.13.2"
  6. junitVersion = "1.2.1"
  7. espressoCore = "3.6.1"
  8. lifecycleRuntimeKtx = "2.8.7"
  9. activityCompose = "1.8.0"
  10. composeBom = "2024.04.01"
  11. appcompat = "1.7.0"
  12. [libraries]
  13. androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
  14. junit = { group = "junit", name = "junit", version.ref = "junit" }
  15. androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
  16. androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
  17. androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
  18. androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
  19. androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
  20. androidx-ui = { group = "androidx.compose.ui", name = "ui" }
  21. androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
  22. androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
  23. androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
  24. androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
  25. androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
  26. androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
  27. purejavacomm = { group = "com.github.purejavacomm", name = "purejavacomm", version = "1.0.2.RELEASE" }
  28. androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
  29. [plugins]
  30. android-application = { id = "com.android.application", version.ref = "agp" }
  31. kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
  32. kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
复制代码
activity_main.xml 代码示例 (APP界面,UI,用于测试USB串口调试APP的界面UI设计)
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout
  3.     xmlns:android="http://schemas.android.com/apk/res/android"
  4.     android:layout_width="match_parent"
  5.     android:layout_height="match_parent"
  6.     android:orientation="vertical"
  7.     android:padding="16dp">
  8.     <TextView
  9.         android:id="@+id/statusTextView"
  10.         android:layout_width="match_parent"
  11.         android:layout_height="wrap_content"
  12.         android:text="USB Status: Not connected"
  13.         android:textSize="18sp" />
  14.     <TextView
  15.         android:id="@+id/logTextView"
  16.         android:layout_width="match_parent"
  17.         android:layout_height="wrap_content"
  18.         android:text="Log output:\n"
  19.         android:padding="8dp"
  20.         android:scrollbars="vertical" />
  21.     <Button
  22.         android:id="@+id/checkPortButton"
  23.         android:layout_width="match_parent"
  24.         android:layout_height="wrap_content"
  25.         android:text="Check USB Devices" />
  26. </LinearLayout>
复制代码
2.关键点

关键点在于,MainActivity.kt 中的 这段代码:
  1.     /**
  2.      * 请求 USB 权限
  3.      */
  4.     private fun requestPermission(device: UsbDevice) {
  5.         val intent = PendingIntent.getBroadcast(
  6.             this, 0, Intent("com.android.example.USB_PERMISSION"),
  7.             PendingIntent.FLAG_IMMUTABLE
  8.         )
  9.         usbManager.requestPermission(device, intent)
  10.     }
复制代码
能够看到,在使用 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企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

我可以不吃啊

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表