Administrator
发布于 2026-08-25 / 35 阅读
0
0

系列文章《Android 工程师的 100 篇进阶笔记》第 58 篇:Android 14/15 新特性深度解析:适配清单与实战

上一篇讲了 Android Automotive,回到手机平台——Android 14/15 带来了大量新能力,也打破了不少兼容代码。这一篇把官方更新梳理一遍,重点是"哪些会影响你的 App"。

Android 14(API 34)的关键变化

1. 前台服务新规则

必须声明 type,否则启动 FGS 直接 IllegalStateException:

<service
    android:name=".MyForegroundService"
    android:foregroundServiceType="dataSync"
    android:exported="false" />

<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />

type

用途

权限

dataSync

数据同步

FOREGROUND_SERVICE_DATA_SYNC

mediaPlayback

音乐播放

FOREGROUND_SERVICE_MEDIA_PLAYBACK

location

位置上报

FOREGROUND_SERVICE_LOCATION

camera

摄像头

FOREGROUND_SERVICE_CAMERA

phoneCall

电话

FOREGROUND_SERVICE_PHONE_CALL

health

健康

FOREGROUND_SERVICE_HEALTH

mediaProjection

录屏

FOREGROUND_SERVICE_MEDIA_PROJECTION

microphone

录音

FOREGROUND_SERVICE_MICROPHONE

一次性类型可以传 mediaPlayback\|dataSync 用 | 分割。

2. Context-registered receivers 必须指定导出/未导出

老代码:

registerReceiver(connectivityReceiver, IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"))

Android 14 编译必须写明:

registerReceiver(
    connectivityReceiver,
    IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"),
    Context.RECEIVER_NOT_EXPORTED // 或 RECEIVER_EXPORTED
)

RECEIVER_NOT_EXPORTED 表示只接收本应用发送的广播,避免被恶意应用骗。

3. 隐式 Intent 启动 Service 限制

// 编译警告,运行 Android 14 设备直接抛 IllegalArgumentException
startService(Intent("com.example.SOME_SERVICE"))

必须显式指定 package 或 ComponentName:

startService(Intent("com.example.SOME_SERVICE").setPackage(packageName))

4. 仅 ARM 的 16KB Page Size 支持

Google Play 2025 年开始强制要求新 App 支持 16KB page size。原生库(.so)如果不支持,运行崩溃。

# 检查 so 是否兼容
zipalign -c -v 4 -P 16 <apk> 4 | grep "page size"

如果用 NDK 编译,升级 NDK 到 r26+。

5. Photo Picker

不用 READ_EXTERNAL_STORAGE 也能选图:

// 单选
val photoPicker = registerForActivityResult(
    ActivityResultContracts.PickVisualMedia()
) { uri -> /* handle uri */ }

photoPicker.launch(
    PickVisualMediaRequest(
        ActivityResultContracts.PickVisualMedia.ImageOnly
    )
)

// 多选
photoPicker.launch(
    PickVisualMediaRequest(
        ActivityResultContracts.PickVisualMedia.ImageOnly
    ).withMaxItems(10)
)

Photo Picker 不需要任何权限,建议优先使用。

Android 15(API 35)的关键变化

1. 强制 edge-to-edge

从 Android 15 开始,targetSdk=35 的应用默认全屏覆盖(edge-to-edge)。

systemBars insets 行为变化:

// 之前:系统栏背景透明但 insets 还能告诉内容不要被遮挡
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
    val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
    v.setPadding(bars.left, bars.top, bars.right, bars.bottom)
    WindowInsetsCompat.CONSUMED
}

// Android 15:默认已经处理,但 IME insets 行为变了
// imeAnimationSource 变化,部分输入法动效会掉帧

老 App 升级到 targetSdk=35:

  • 要么显式禁用 edge-to-edge:<application android:theme="@style/AppCompatTheme"> 加 windowOptOutEdgeToEdgeEnforcement="true"

  • 要么主动监听 insets 重新布局

2. 仅允许本地 ndk 调试

<!-- Android 15 强制要求 -->
<application android:debuggable="true">
    <profileable android:shell="true" />
</application>

不能通过 adb shell setprop 之类的方式绕过。

3. 系统级字体非拉丁语扩展

阿拉伯文、印度文、泰文等复杂文字改进:

<style name="Theme.App" parent="Theme.Material3.DayNight">
    <!-- 不需要显式调整 -->
</style>

但如果你用自定义字体确保覆盖了 fallback。

4. 16KB Page Size 强制

Android 15 必须支持 16KB page size。测试方法:

# 启动 adb 模拟器
emulator -avd 15dp1 -page-size 16384

5. 部分 foreground service 类型废弃

connectedDevice、dataSync、health 在 Android 15 有严格使用场景限制。比如 dataSync:

<!-- Android 15 之前 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />

<!-- Android 15 -->
<!-- 仍然可用,但单次运行最多 6 小时 -->

跟 iOS 不同,Android 还在给 foreground service 留余地。

适配最佳实践

渐进式升级 strategy

当前状态

建议

targetSdk = 33

升 35 必须做完整适配

升级到 34

简单 App 改 manifest 即可

升级到 35

重测启动、UI 适配、后台行为

升级工具:

# 看官方 advice
# https://developer.android.com/about/versions/15/behavior-changes-15

# 用 lint 跑兼容性扫描
./gradlew lintDebug --warning-mode all \
    --baseline lint-baseline.xml

AGP 升级会遇到 Kotlin、Compose、KSP 的兼容性矩阵问题,参考版本对应表。

升级顺序

  1. 升级 AGP:8.x 一般兼容 targetSdk 35(AGP 版本决定编译能力)

  2. 升级 compileSdk:compileSdkVersion 跟 targetSdk 不必相同,但新特性必须 compileSdk 是新版本

  3. 升级 targetSdk:分阶段(先升级到 34,稳定后再到 35)

  4. 升级 minSdk:考虑后才升级,影响可用设备范围

截图回归测试

UI 变化影响最大,必须跑截图回归。工具:

  • Paparazzi(单元测试层截图)

  • Roborazzi(Robolectric 截图)

  • Showkase(Compose 预览管理)

// Paparazzi 示例
class LoginScreenSnapshotTest {
    @get:Rule
    val paparazzi = Paparazzi(
        deviceConfig = DeviceConfig.PIXEL_5,
        theme = "android:Theme.Material3.Light.NoActionBar"
    )

    @Test
    fun `login screen default state`() {
        val screen = paparazzi.unsafeUpdate(ConfigPixelSize(1080, 2400)) {
            LoginScreen()
        }
        paparazzi.snapshot(screen, "login_screen_default")
    }
}

跑测试:

./gradlew :feature:verifyPaparazziDebug

CI 上对比截图差异,人工 review diff。

一些容易忽略的细节

1. 三方库的 targetSdk 兼容

很多老库声明 targetSdk=30,跑在 Android 14 上会出各种奇怪行为(行为变更可能影响库)。升级这些库是必须的。

2. 权限的默认行为

<!-- 普通权限不需要运行时申请 -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- 危险权限需要运行时申请 -->
<uses-permission android:name="android.permission.CAMERA" />

<!-- Android 14 新增的 partial photo access -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />

READ_MEDIA_VISUAL_USER_SELECTED 让用户选照片时给三种选择:"允许所有"、"仅选定"、"拒绝"。体验更好但代码要兼容分支。

3. 缩略图保存

Android 14 Environment.DIRECTORY_PICTURES 权限变化,保存图片不需要额外权限了。

升级清单

我列一份适配清单,打勾逐条处理:

  • Foreground service 声明 type

  • 隐式 Intent → 显式 Intent

  • 动态注册广播加 EXPORTED/UNEXPORTED

  • 适配 targetSdk 35 edge-to-edge

  • Photo Picker 替换部分场景

  • NDK 库 16KB page size 兼容

  • 危险权限运行时申请

  • 截图回归测试

  • 用 Paparazzi/Roborazzi 跑视觉回归

  • 旧库迁移到 Kotlin、Compose、Material 3

小结

Android 平台一年一更新,每版都带来行为变更。建议每半年集中梳理一次:把 manifest 升级、把第三方库升一版、把 lint warning 扫一遍。新能力(foreground service type、Photo Picker、edge-to-edge)值得用,但要在测试环境充分验证。

下一篇回到性能专题——Choreographer 是怎么暴露卡顿问题的。


评论