2026-03 权限判断方法放那里?

// 推荐的工具方法
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 纯净的同时,又拥有了操作系统的感知能力。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容