// 推荐的工具方法
private fun isStoragePermissionGranted(): Boolean {
val permission = when {
// Android 13+ (API 33)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU ->
Manifest.permission.READ_MEDIA_IMAGES
// Android 6.0+ (API 23)
else -> Manifest.permission.READ_EXTERNAL_STORAGE
}
// 这里就是你说的 AndroidX 兼容调用
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
}
这是一个非常具有工程实践意义的问题。作为一名有经验的开发者,你应该考虑的不仅是“能运行”,而是代码的复用性、可测试性和 UI 层的清爽度。
建议根据你的项目架构选择以下方案之一:
方案 A:作为 Activity 的私有扩展方法(最简单、适合小型项目)
如果这个判断逻辑只在 MainActivity 中使用,直接写在 Activity 底部作为一个 private 方法或扩展方法即可。
- 优点:调用直观,不需要额外的类。
- 缺点:无法复用,且会让 Activity 的代码量增加。
// 在 MainActivity.kt 底部
private fun MainActivity.isStoragePermissionGranted(): Boolean {
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES // 或者 READ_MEDIA_VISUAL_USER_SELECTED
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
}
方案 B:封装成 Context 扩展属性(推荐,平衡性最好)
权限检查本质上是对 Context 的一种状态查询。将其封装为 Context 的扩展属性,可以让你在任何 Activity、Fragment 甚至 Service 中一行代码调用。
-
位置:新建一个
ContextExt.kt文件。 - 优点:全工程复用,符合 Kotlin 的惯用法。
// ContextExt.kt
val Context.hasStoragePermission: Boolean
get() {
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
}
调用时:
lifecycle.addObserver(viewModel.createPermissionObserver { hasStoragePermission })
方案 C:封装进 PermissionManager(工程化程度最高,最纯净)
如果你使用了 Hilt 或其他依赖注入工具,或者打算将来做更复杂的权限申请(如权限被拒后的引导),建议创建一个专门的类。
-
位置:
util或manager包下。 - 优点:极易进行单元测试。你可以通过 Mock 这个接口来测试 ViewModel,而不需要真正的 Android 环境。
// 使用 Hilt 注入
class PermissionManager @Inject constructor(@ApplicationContext private val context: Context) {
fun isStoragePermissionGranted(): Boolean {
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
}
}
如果你正在使用 Hilt 并且非常注重代码的可测试性与架构纯净度,那么 方案 C(封装 PermissionManager) 是最符合资深开发者直觉的选择。
这种方式的核心思想是将“权限检查”抽象成一个服务,注入到需要的地方,从而彻底摆脱在 Activity 逻辑中写 if-else 的碎片化代码。
以下是具体的工程化实现步骤:
第一步:创建 PermissionManager
利用 Hilt 的 @ApplicationContext,我们可以安全地在非 UI 类中使用 Context 而不导致内存泄漏。
// utils/PermissionManager.kt
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.content.ContextCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
@Singleton // 全局单例
class PermissionManager @Inject constructor(
@ApplicationContext private val context: Context
) {
/**
* 判断存储权限是否已授予
* 适配了 Android 13 (API 33) 的权限拆分逻辑
*/
fun isStoragePermissionGranted(): Boolean {
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Android 13+ 需要检查媒体权限(根据你的 SmartPDF 需求,这里通常选 IMAGES 或 DOCUMENTS)
Manifest.permission.READ_MEDIA_IMAGES
} else {
// Android 12 及以下使用旧权限
Manifest.permission.READ_EXTERNAL_STORAGE
}
return ContextCompat.checkSelfPermission(context, permission) ==
PackageManager.PERMISSION_GRANTED
}
}
第二步:在 MainActivity 中注入并使用
现在,你的 MainActivity 不再需要知道如何判断版本,也不需要持有复杂的逻辑,只需“下达指令”。
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
// 注入权限管理器
@Inject lateinit var permissionManager: PermissionManager
@Inject lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ... 其他初始化代码 ...
setupObservers()
}
private fun setupObservers() {
// 使用注入的对象进行逻辑判断,Activity 变得非常清爽
lifecycle.addObserver(viewModel.createPermissionObserver {
permissionManager.isStoragePermissionGranted()
})
}
}
为什么说这种方式“高级”?(核心优势)
1. 彻底解决单元测试难题 (The Testing Game-Changer)
还记得你担心的 JVM 单元测试吗?
现在如果你要测试 MainViewModel,你根本不需要 Mock 一个复杂的 Activity。你只需要用 MockK 模拟一个 PermissionManager 即可。
- 英文关键词:Dependency Injection (DI), Mocking Dependencies.
2. 逻辑集中化 (Centralized Logic)
如果以后 Google 又改了权限规则(比如 Android 16),你只需要修改 PermissionManager 这一个类,整个项目的几十个 Activity 都会自动适配,这符合 DRY (Don't Repeat Yourself) 原则。
3. 规避了“冗余”的感觉
你之前提到 ViewModel 处理系统级 UI 冗余很烦。虽然现在还是要传闭包,但这个闭包内部调用的已经是业务级的方法名(isStoragePermissionGranted),而不是系统级的方法名(checkSelfPermission)。这种抽象层级的提升,极大地减轻了心理上的“冗余感”。
进阶思考:如果我想在 ViewModel 里直接用?
虽然建议由 UI 层触发检查,但有了 PermissionManager,你甚至可以直接把它注入给 ViewModel(因为它只持有 ApplicationContext,不会导致内存泄漏)。
// 这样也是安全的
class MainViewModel @Inject constructor(
private val permissionManager: PermissionManager
) : ViewModel() {
fun checkData() {
if (permissionManager.isStoragePermissionGranted()) {
// 执行加载逻辑
}
}
}
**这种方式让你在保持 ViewModel 纯净的同时,又拥有了操作系统的感知能力。