Android13 新特性与适配指南

Android 13(API 33)引入了多项重要特性和行为变更,本文从 Android 开发视角,梳理核心适配点及对应的 Kotlin 实现方案,帮助开发者快速完成适配。

一、权限适配(核心变更)

Android 13 对权限体系做了精细化拆分,重点涉及媒体权限通知权限,需重点适配。

1. 媒体权限拆分(替代 READ_EXTERNAL_STORAGE)

Android 13 移除了 READ_EXTERNAL_STORAGE 权限,将其拆分为 3 个更细分的权限:

  • READ_MEDIA_IMAGES:读取图片
  • READ_MEDIA_VIDEO:读取视频
  • READ_MEDIA_AUDIO:读取音频
适配步骤
(1)Manifest 声明权限
xml 复制代码
<!-- Android 13+ 媒体权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

<!-- 兼容 Android 12 及以下 -->
<uses-permission
    android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />
(2)Kotlin 动态申请权限
kotlin 复制代码
import android.Manifest
import android.os.Build
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity

class MediaPermissionActivity : AppCompatActivity() {

    // 权限申请回调
    private val requestMediaPermissions = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        val isImageGranted = permissions[Manifest.permission.READ_MEDIA_IMAGES] ?: false
        val isVideoGranted = permissions[Manifest.permission.READ_MEDIA_VIDEO] ?: false
        val isAudioGranted = permissions[Manifest.permission.READ_MEDIA_AUDIO] ?: false
        
        if (isImageGranted || isVideoGranted || isAudioGranted) {
            // 权限申请成功,处理媒体文件
            handleMediaFiles()
        } else {
            // 权限被拒绝,引导用户去设置页开启
            showPermissionDeniedDialog()
        }
    }

    // 申请媒体权限
    private fun requestMediaPerms() {
        val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            // Android 13+ 申请细分权限
            arrayOf(
                Manifest.permission.READ_MEDIA_IMAGES,
                Manifest.permission.READ_MEDIA_VIDEO,
                Manifest.permission.READ_MEDIA_AUDIO
            )
        } else {
            // 兼容低版本
            arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
        }
        requestMediaPermissions.launch(permissions)
    }

    private fun handleMediaFiles() {
        // 处理图片/视频/音频逻辑
    }

    private fun showPermissionDeniedDialog() {
        // 权限拒绝提示弹窗
    }
}

2. 通知权限(POST_NOTIFICATIONS)

Android 13 要求所有应用必须申请 POST_NOTIFICATIONS 权限才能发送通知,默认情况下权限为关闭状态。

适配步骤
(1)Manifest 声明权限
xml 复制代码
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
(2)Kotlin 申请通知权限
kotlin 复制代码
import android.Manifest
import android.os.Build
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity

class NotificationPermissionActivity : AppCompatActivity() {

    private val requestNotificationPermission = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        if (isGranted) {
            // 通知权限已授予,初始化通知
            initNotification()
        } else {
            // 权限被拒绝,提示用户开启
            showNotificationPermissionTip()
        }
    }

    // 检查并申请通知权限
    private fun checkNotificationPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
            if (!notificationManager.areNotificationsEnabled()) {
                // 申请通知权限
                requestNotificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
            } else {
                initNotification()
            }
        } else {
            // 低版本无需申请,直接初始化
            initNotification()
        }
    }

    private fun initNotification() {
        // 初始化通知渠道、发送通知等逻辑
    }

    private fun showNotificationPermissionTip() {
        // 提示用户去设置页开启通知权限
    }
}

二、应用内语言设置适配

Android 13 新增了应用内语言设置 API,允许应用独立于系统语言设置自身的显示语言,无需重启应用。

适配实现

kotlin 复制代码
import android.os.Build
import android.os.LocaleList
import androidx.appcompat.app.AppCompatActivity
import java.util.Locale

class LanguageSettingActivity : AppCompatActivity() {

    // 设置应用内语言(例如:设置为简体中文)
    private fun setAppLanguage(locale: Locale) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            // Android 13+ 官方 API
            val appLocale = android.app.LocaleManager.getApplicationLocales(baseContext)
            appLocale.setLocaleList(LocaleList(locale))
            android.app.LocaleManager.setApplicationLocales(baseContext, appLocale)
            
            // 刷新界面(无需重启 Activity)
            recreate()
        } else {
            // 兼容低版本(传统方式)
            val resources = resources
            val configuration = resources.configuration
            configuration.setLocale(locale)
            resources.updateConfiguration(configuration, resources.displayMetrics)
            recreate()
        }
    }

    // 示例:设置为英文
    fun setEnglish() {
        setAppLanguage(Locale.ENGLISH)
    }

    // 示例:设置为简体中文
    fun setChinese() {
        setAppLanguage(Locale.SIMPLIFIED_CHINESE)
    }

    // 获取当前应用内语言
    private fun getCurrentAppLocale(): Locale {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            val locales = android.app.LocaleManager.getApplicationLocales(baseContext)
            locales[0] ?: Locale.getDefault()
        } else {
            resources.configuration.locale
        }
    }
}

三、照片选择器适配

Android 13 强化了照片选择器(Photo Picker),提供更安全的媒体选择方式,无需申请存储权限即可让用户选择图片/视频。

适配实现

kotlin 复制代码
import android.content.Intent
import android.os.Build
import android.provider.MediaStore
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity

class PhotoPickerActivity : AppCompatActivity() {

    // 照片选择器回调
    private val pickImageLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode == RESULT_OK) {
            val uri = result.data?.data
            uri?.let {
                // 处理选中的图片 URI
                handleSelectedImage(it)
            }
        }
    }

    // 打开照片选择器
    private fun openPhotoPicker() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            // Android 13+ 照片选择器(支持多选、指定媒体类型)
            val intent = Intent(MediaStore.ACTION_PICK_IMAGES).apply {
                // 设置最多选择数量
                putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 5)
                // 指定媒体类型:仅图片(可选:VIDEO、IMAGES_AND_VIDEO)
                type = "image/*"
            }
            pickImageLauncher.launch(intent)
        } else {
            // 兼容低版本(传统相册选择)
            val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
            pickImageLauncher.launch(intent)
        }
    }

    private fun handleSelectedImage(uri: android.net.Uri) {
        // 加载图片、上传等逻辑
    }
}

四、其他关键适配点

1. 前台服务类型限制

Android 13 要求前台服务必须指定具体的 foregroundServiceType,且新增了 healthremoteMessaging 等类型。

Manifest 声明示例
xml 复制代码
<service
    android:name=".MyForegroundService"
    android:foregroundServiceType="location|mediaPlayback" />
Kotlin 启动前台服务
kotlin 复制代码
import android.app.Notification
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder

class MyForegroundService : Service() {

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = createNotification() // 自定义创建通知
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            // 指定前台服务类型
            startForeground(1, notification, Service.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK)
        } else {
            startForeground(1, notification)
        }
        return START_STICKY
    }

    private fun createNotification(): Notification {
        // 构建前台服务通知(需创建通知渠道)
        return Notification.Builder(this, "media_channel")
            .setContentTitle("播放中")
            .setSmallIcon(R.drawable.ic_play)
            .build()
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

2. 剪贴板访问限制

Android 13 限制应用在后台访问剪贴板,仅当应用处于前台且用户主动触发(如点击按钮)时才能访问。

kotlin 复制代码
// 仅在前台主动触发时访问剪贴板
fun getClipboardContent() {
    val clipboard = getSystemService(CLIPBOARD_SERVICE) as android.content.ClipboardManager
    val clipData = clipboard.primaryClip
    if (clipData != null && clipData.itemCount > 0) {
        val content = clipData.getItemAt(0).text.toString()
        // 处理剪贴板内容
    }
}

3. 蓝牙权限变更

Android 13 将蓝牙权限拆分为 BLUETOOTH_SCANBLUETOOTH_ADVERTISEBLUETOOTH_CONNECT,需按需申请:

xml 复制代码
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

五、适配注意事项

  1. 编译版本升级 :将 compileSdkVersiontargetSdkVersion 升级到 33 及以上,才能使用 Android 13 新 API。
  2. 兼容性测试:适配后需在 Android 13 设备和低版本设备(如 Android 12、11)上充分测试,避免兼容性问题。
  3. 权限申请时机:遵循"按需申请"原则,避免启动页一次性申请所有权限,提升用户体验。
  4. 隐私合规:适配媒体权限、剪贴板等变更时,需符合应用商店的隐私政策要求。

六、参考资源

总结

Android 13 的适配核心集中在权限精细化用户隐私保护体验优化(如应用内语言、照片选择器)。通过上述 Kotlin 代码示例,可快速完成核心特性的适配,同时需注意低版本兼容,确保应用在全版本设备上稳定运行。

相关推荐
跟着杰哥学嵌入式2 小时前
如何使用gitee协作开发项目(入门教程)
gitee
天下无敌笨笨熊7 小时前
kotlin函数式编程
开发语言·数据库·kotlin
Jul1en_8 小时前
解决 GitHub Actions 同步 Gitee 仓库中遇到的一些问题
ci/cd·gitee·自动化·github
QING6188 小时前
Kotlin Flow 去重 (distinctUntilChanged) 详解
kotlin·android studio·android jetpack
QING6188 小时前
Kotlin Flow 节流 (Throttle) 详解
android·kotlin·android jetpack
Kapaseker9 小时前
Context 知多少,组件通联有门道
android·kotlin
初遇你时动了情1 天前
sourcetree 怎么吧gitee仓库拉取到本地项目中?
gitee
冬-梦1 天前
iPad Obsidian Git 同步 Gitee 仓库完整指南
git·gitee·ipad·obsidian·efficiency
儿歌八万首1 天前
Jetpack Compose 实战:打造高性能轮播图 (Carousel) 组件
android·前端·kotlin