上一篇讲了 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" />
一次性类型可以传 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
升级工具:
# 看官方 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 的兼容性矩阵问题,参考版本对应表。
升级顺序
升级 AGP:8.x 一般兼容 targetSdk 35(AGP 版本决定编译能力)
升级 compileSdk:compileSdkVersion 跟 targetSdk 不必相同,但新特性必须 compileSdk 是新版本
升级 targetSdk:分阶段(先升级到 34,稳定后再到 35)
升级 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 是怎么暴露卡顿问题的。