资讯详情

Android 9.0 Pie (API 28) SDK适配实战:老项目兼容性升级与踩坑排查

📅 2026/10/15 12:54:07 | 华诺云谱 👁 阅读
Android 9.0 Pie (API 28) SDK适配实战:老项目兼容性升级与踩坑排查
简介面向需要适配 Android 9.0 Pie 的移动开发者这份 API 28 SDK 资源包提供了完整的 android-28 平台文件可在 Android Studio、Eclipse 等 IDE 中配置为编译目标解决缺少对应 platform 导致无法构建、调试或运行指定版本应用的问题。压缩包内共 2000 个文件以 png 图标与切图、xml 布局和资源配置、ttf 字体为主还包含 jar 库、aidl 接口定义、properties 配置、template 模板及 HTML 说明页等平台组件整体约 66.58MB解压后目录结构清晰便于定位 platform、build-tools 等模块。资源覆盖 Pie 的数字健康、自适应电池、自适应亮度、手势导航、应用切片、Multi-Camera 多摄、Wi-Fi RTT 室内定位、强制 HTTPS 等新特性接口适合初学者快速搭建 API 28 开发环境也适合进阶开发者查看系统自带资源、框架文件并核验兼容项。已有 966 人学习下载可结合官方变更日志做版本适配、兼容性测试或离线开发参考方便离线查阅、接口比对与资源复用。1. Android 9.0 PieAPI 28SDK存量设备最后的“免费午餐”为什么还值得花时间手里还压着几个 targetSdkVersion 28 的老工程又不敢贸然升到 34这时候把 SDK Android 9.0 Pie (API 28) 的环境弄清楚可能比换个新手机还实在。API 28 是 Android 在“行为变更”上比较克制的最后一代通知渠道、后台执行限制、明文流量开关这些硬规矩从 API 26/27 就开始铺垫到了 9.0 只是收紧没有像 Android 10 之后那样把分区存储、前台服务类型、照片选择器一股脑砸过来。所以很多中小团队把 28 当“适配性价比最高”的档位compileSdkVersion 升到 28targetSdkVersion 保持 28既能跑在新系统上又不用面对 29 的存储和权限重构。这篇笔记把 API 28 这套 SDK 的安装、适配、验证和 CI 复现整条链路拆开适合维护老 App、做 ROM 预装适配、或者给测试团队搭 9.0 兼容性环境的开发者照着操作。2. 先搞清楚你装的到底是什么SDK Platform、系统镜像与 compileSdkVersion 的三角关系很多人在 API 28 上翻车不是因为代码改不对而是因为“Android 9.0 SDK”这个说法本身太含糊。它至少指三样东西SDK Platform、系统镜像、Build Tools它们分别影响编译、模拟器运行和打包装错一个后面全是玄学错误。2.1 为什么 API 28 的 SDK Platform 不等于系统镜像两套组件各管什么SDK Platformplatforms;android-28是编译时用的“API 骨架”。它提供 android.jar里面是 API 28 的类和方法签名比如NotificationChannel、ActivityManager里新增的isBackgroundRestricted()。Gradle 编译时拿它做符号解析和类型检查但 android.jar 里所有方法都是空实现stub它不负责在真机上跑代码。系统镜像system-images;android-28;...是给模拟器用的完整 Android 9.0 系统。它包含 Framework、系统 App、HAL 层实现决定你的 App 在模拟器上看到的真实运行时行为——包括 API 28 的后台限制、通知渠道是否生效、电源管理策略等。举一个常见误区有人只装了 system-images 没装 platforms;android-28结果 Android Studio 里建项目选了 API 28Gradle 直接报SDK platform not found反过来只装 platforms 不装系统镜像模拟器创建时找不到 AVD 系统。两条腿缺一条SDK 就是瘸的。我在配环境时习惯把四类组件一次性装齐platform编译、system-image模拟器运行、build-tools打包和签名、platform-toolsadb 和 fastboot。这四件套缺一个后面某个环节必然出幺蛾子与其装一半卡住再排查不如全装。2.2 用 sdkmanager 装齐 API 28 的四个组件命令行安装与版本选择Android Studio 的 SDK Manager 图形界面能装但命令行更可控尤其在 CI 上或者只想要 API 28 这一套、不想被 GUI 默认勾选的其他版本干扰时。我用的是 sdkmanager它位于$ANDROID_HOME/cmdline-tools/latest/bin下。# 先看有哪些可装的 API 28 相关组件 sdkmanager --list | grep android-28 # 装齐四件套platform、x86_64 系统镜像、build-tools 28.0.3、platform-tools sdkmanager --install platforms;android-28 \ system-images;android-28;google_apis;x86_64 \ build-tools;28.0.3 \ platform-tools--list输出的组件名需要解释一下。system-images后面那段google_apis表示镜像里带了 Google APIs比如地图、GMS 基础库如果你做的 App 不依赖 GMS也可以选default版本x86_64是 CPU 架构在 macOS 和 Linux 上跑模拟器比arm64快得多。build-tools 版本我选28.0.3——这是 Android 9.0 时代的最终维护版本修了早期 28.0.0/28.0.1 的一些 AAPT2 和 D8 问题。不建议用低版本否则打包时可能碰到老 bug也不建议在命令行里装build-tools;28这种不带小版本的写法sdkmanager 不一定认。参数这块还有个坑sdkmanager 装镜像时要确认接受许可第一次运行会提示y/n。上面命令没加--licenses如果系统没接受过许可会卡在交互输入。批量处理时我会先用yes | sdkmanager --licenses把所有许可都过一遍再装组件这个顺序在 CI 里尤其重要后面还会再提。2.3 在 build.gradle 里锁定 compileSdkVersion 与 targetSdkVersion一组可复制的配置装完 SDK接下来就是让 Gradle 知道用哪套 API 编译。这里有一个经常混淆的概念compileSdkVersion决定编译时用哪个 API Level 的 android.jartargetSdkVersion决定运行时系统按哪个 API Level 的行为规则来约束你。两者可以不一样但 API 28 项目我建议两个都锁定 28避免编译用新 API、运行时却被老规则限制的割裂状态。android { compileSdkVersion 28 buildToolsVersion 28.0.3 defaultConfig { applicationId com.example.demo minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName 1.0 } }compileSdkVersion 28告诉 Gradle编译时如果用到 API 28 新增的类或方法比如NotificationChannel或Context.RECEIVER_NOT_EXPORTED编译器不会报错。targetSdkVersion 28则告诉运行时的 Android 9.0 系统对这个 App 启用 API 28 的行为变更包括后台限制和明文流量默认关闭。buildToolsVersion 28.0.3这块值得注意如果你不写Gradle 会用自己默认的 build tools 版本但那个版本在 API 28 上打出来的包可能用的是新的 D8 dexer遇到某些老项目的手写混淆规则会崩。锁死一个明确版本CI 和本地行为才一致。这里还有一个旧项目迁移时容易忽略的点minSdkVersion如果低于 26那通知渠道、后台限制这些 API 28 特性需要做版本判断不能直接调用。我一般把 minSdk 21 作为分界线21 以下的设备走旧的通知方式21 以上走渠道模式这样既能用 API 28 的新特性又不牺牲老设备。3. 把老项目真正“跑”在 API 28 上的适配改动从通知到安全的五项必修很多工程在 API 26/27 上已经适配过一轮但 API 28 有几个变更点是 26/27 没覆盖或只是“预告”的。这些改不彻底App 在 Android 9.0 真机上的表现就是“时好时坏”——通知有的出有的不出后台服务时不时被杀HTTP 请求直接抛异常。3.1 通知渠道没有创建渠道API 28 上通知直接不显示通知渠道机制从 Android 8.0API 26就开始强制但很多老项目当时只是“绕过”——在 8.0 上把通知发出来就算完没正经设计渠道。到了 Android 9.0系统对渠道的管理更严用户在设置里关掉某个渠道的通知App 即使再发系统也直接拦截连NotificationManager.notify()都不会产生可见反馈。// API 28 上通知必须绑定一个渠道否则通知不显示 if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { val channel NotificationChannel( default_channel, 默认通知, NotificationManager.IMPORTANCE_DEFAULT ).apply { description 普通业务消息 } val manager getSystemService(NotificationManager::class.java) manager.createNotificationChannel(channel) } val builder NotificationCompat.Builder(this, default_channel) .setSmallIcon(R.drawable.ic_notification) .setContentTitle(提示) .setContentText(这是一条 API 28 测试通知) .setAutoCancel(true) NotificationManagerCompat.from(this).notify(1001, builder.build())注意NotificationCompat.Builder的第二个参数如果你不传渠道 IDAndroid 9.0 上构建不会报错但notify()时会看到MissingNotificationChannelException这条异常是运行时抛出的不是编译期错误很容易在测试时漏掉。渠道的IMPORTANCE_DEFAULT对应的是“发出提示音但不打断”的级别如果业务里有闹钟、计时器这类需要强提醒的可以考虑IMPORTANCE_HIGH。但别把普通推送全设成 HIGH——用户可以在设置里关掉整个渠道设置得越打扰被关掉的概率越大。一个真实的翻车案例某开发者在 API 28 模拟器上测试通知正常显示但同一版本在 9.0 真机上消失。排查半天发现模拟器上通知渠道是第一次安装时自动创建的而真机上用户之前装过旧版本旧版本没有创建渠道升级后开发者在代码里创建渠道的时机太晚——在发通知的时候才创建。系统发现渠道已经存在但处于“被用户关闭”状态直接不显示。解决方式是升级后在onCreate或启动流程里尽早createNotificationChannel并且给渠道设置setShowBadge(true)之类属性尽量在用户还没手动操作前把渠道建好。3.2 后台执行限制前台服务与 JobScheduler 的取舍API 28 对后台限制最狠的一条是App 处于后台时系统会限制它访问 CPU、网络和 GPS 的频次。如果 App 没有在“前台运行”——即没有任何Activity可见、也没有前台服务——那它的Timer、Handler.postDelayed、Thread.sleep这类延迟任务都可能被系统延后或直接丢弃。// 需要长时间在后台执行的场景用前台服务跑而不是靠定时代码 val notification NotificationCompat.Builder(this, default_channel) .setSmallIcon(R.drawable.ic_notification) .setContentTitle(正在同步) .setContentText(数据上传中) .build() startForegroundService(Intent(this, SyncService::class.java)) // 短期的、非实时任务交给 JobSchedulerAPI 28 上比 Timer 可靠 val jobInfo JobInfo.Builder(1001, ComponentName(this, SyncJobService::class.java)) .setMinimumLatency(5000) // 最少延迟 5 秒后执行 .setOverrideDeadline(15000) // 最多延迟 15 秒超时后会尽量执行 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) // 仅在 Wi-Fi 下执行 .build() jobScheduler.schedule(jobInfo)startForegroundService()是 API 26 引入的入口API 28 上如果你用老式的startService()来启动一个带前台属性的服务系统会报ForegroundServiceStartNotAllowedException。之后必须在服务启动后 5 秒内调用startForeground()否则系统直接崩掉服务并记录一个ANR级别的错误。Timer这类任务在 API 28 上的表现很有迷惑性系统在设备亮屏且插着电时可能完全不限制后台执行于是你在开发机上测试一切正常一旦上真机、息屏、拔掉充电器后台任务就消失。我建议适配时统一把“周期性任务”改成 JobScheduler它有系统级的省电调度策略适配了 API 28 的同时也兼顾未来版本。需要精确到秒级的定时任务再考虑短期唤醒锁但唤醒锁用多了会加速耗电Store 上架审核时也可能被警告。3.3 明文 HTTP 流量默认关闭网络安全配置文件怎么补Android 9.0 上usesCleartextTraffic的默认值变成false意味着 App 里所有http://明文请求——包括图片加载、API 调用、WebView 加载——都会被系统拦截logcat 里出现Cleartext HTTP traffic not permitted。这不是网络库的锅OkHttp 也好、HttpURLConnection 也好都拦在系统层。?xml version1.0 encodingutf-8? network-security-config !-- 仅对特定域名放开明文而不是全局放开 -- domain-config cleartextTrafficPermittedtrue domain includeSubdomainstrueapi.example.com/domain domain includeSubdomainstrueimg.example.com/domain /domain-config /network-security-config然后在AndroidManifest.xml的application标签上引用application android:networkSecurityConfigxml/network_security_config ... 为什么我不建议直接在AndroidManifest.xml里写android:usesCleartextTraffictrue因为那会让整个 App 的明文流量都放行API 28 的安全校验形同虚设。按域名白名单放开内网测试域名、旧版 CDN 域名单独配置既解决业务问题又不会在安全审计时被挑刺。这里有个隐蔽的坑usesCleartextTraffictrue只对HttpURLConnection和OkHttp这类基于标准库的请求生效WebView 不完全受这个属性管控。WebView 加载http://页面时还需要在 WebSettings 里开setMixedContentMode(MODE_COMPATIBILITY)并且页面本身允许明文。如果业务里有 WebView 加载内网 H5 的场景光配 networkSecurityConfig 不够得两边同时设置否则就是“API 能通、H5 白屏”的诡异现象。3.4 自适应图标不做精美 512dp 适配时的最低可用方案API 26 开始引入自适应图标API 28 上系统桌面会强制用圆形遮罩显示所有图标。如果你的 App 只提供传统的mipmap方形图标Android 9.0 启动器会把它放在白色圆形背景上边缘可能被裁掉一圈视觉上像没适配过的“远古应用”。!-- res/mipmap-anydpi-v26/ic_launcher.xml -- adaptive-icon xmlns:androidhttp://schemas.android.com/apk/res/android background android:drawabledrawable/ic_launcher_background / foreground android:drawabledrawable/ic_launcher_foreground / /adaptive-icondrawable/ic_launcher_background是一张纯色或渐变图片占据整个 108dp 的画布drawable/ic_launcher_foreground是你原来的图标内容但只画在中心 72dp 的安全区域内。如果嫌两张图麻烦最低可行的方案是背景直接用纯色比如品牌主色的ColorDrawable前景用原来那张图标但四周留 18dp 以上边距。图标这块的“最低可用”标准是不显示白色圆形底、不裁切主体内容。代码里很多组件会读取ic_launcher或ic_launcher_round这两个资源名如果你只改了ic_launcher.xml没改ic_launcher_round.xml系统在某些页面上如系统设置的应用信息会看到方形老图标这种不一致也很容易被 UI 验收挑出来。3.5 WebView 与 Apache HTTP 兼容两个历史包袱的处理方式API 28 有几个“退场”级别的变更。org.apache.http.legacy库在 API 28 上默认不再可用如果你的老代码里直接用了HttpClient编译时会报Cannot resolve symbol HttpClient。另一个是 WebView 在 9.0 开始强制多进程模式老代码里如果在onRenderProcessGone里做复杂操作容易崩。// 老项目用到了 Apache HTTP 客户端的补救方案 android { useLibrary org.apache.http.legacy }useLibrary org.apache.http.legacy告诉 Gradle 打包时把 legacy 库带进来运行时就能用。但这里有一个优先级问题如果你把 targetSdkVersion 升到 28 且compileSdkVersion也是 28加上useLibrary虽然编译和运行都不报错但 Google 官方明确说这个库在未来的 API Level 会彻底移除。我一般建议利用这次适配的机会把HttpClient的调用迁移到 OkHttp 或 HttpURLConnection不然等下次升到 API 34 再动改动面会大得多。WebView 这块API 28 的onRenderProcessGone()回调需要你主动处理如果 WebView 崩溃且没有WebViewClient处理这个回调App 的整个进程会被系统杀死。处理方式是在这个回调里返回true并移除 WebView然后重建不要在里面做耗时操作或访问已销毁的 WebView 对象。很多 App 在 API 28 上“莫名闪退”就是折在这——页面里嵌了个 WebView 当广告位WebView 渲染进程崩了没有回调兜底整个 App 跟着没了。4. 去 API 28 的路上4 个典型踩坑与排查记录适配做完只是开始。真正费时间的是那些“代码看着没问题、运行时表现却不符合预期”的坑。这里记四条我在 API 28 环境里踩过或帮别人排查过的记录现象、原因、解决一条条说清楚。4.1 现象targetSdkVersion28 但系统识别出的 targetSdk 还是 26某开发者的 App 在 Android 9.0 上运行后台任务依旧被限制系统弹窗提示“此应用专为旧版 Android 打造”。查build.gradle里明明写着targetSdkVersion 28但adb shell dumpsys package com.example.app | grep target显示的还是 26。原因项目里存在多个build.gradleapp/build.gradle里写的 targetSdkVersion 28但某个 library 模块比如推送 SDK自己声明了targetSdkVersion 26打包时资源合并或 manifest 合并把主模块的值覆盖了更常见的是没有清缓存重装——App 从旧版本升级上来时系统会用安装时的 targetSdk 判定行为规则即使新版本的 manifest 声明是 28系统也按旧值执行。升级安装的包和全新安装的包在系统里的行为规则不一样。解决先确认是哪个模块覆盖了 manifest。用aapt dump badging app-release.apk看最终 APK 里的targetSdkVersion是什么如果显示 26就在所有子模块的defaultConfig里统一加targetSdkVersion 28或者只在主模块设置并在子模块的开头加targetSdkVersion rootProject.ext.targetSdkVersion。如果是升级安装导致的测试时卸载旧包重装一次别用覆盖安装验证行为变更。4.2 现象模拟器装不上 x86 镜像API 28 镜像全是 arm64 可选在 Mac 上装 API 28 系统镜像sdkmanager --list里只有system-images;android-28;google_apis;arm64-v8a没有 x86_64或者安装了 x86_64 镜像但 emulator 启动时直接报x86_64 emulation currently requires hardware acceleration。原因API 28 的 Google APIs 镜像在某些平台的 SDK 仓库里确实优先发 arm64因为 Google 认为开发者主要用真机而 x86_64 镜像也不是完全没有只是需要单独指定。x86_64 emulation requires hardware acceleration则是宿主机没开 HAXM 或者 macOS 新版本不兼容老的 HAXM 驱动。解决先用sdkmanager --list | grep android-28;default找非 Google APIs 的镜像default;x86_64在大多数平台有发布如果必须用 Google APIs 镜像可以在 AVD 创建时把架构选为 x86_64再在config.ini里手动确认hw.cpu.archx86_64。硬件加速这块Windows 上装最新的 Intel HAXM 或者改用 Windows Hypervisor PlatformmacOS 上如果 Intel 芯片检查是否装了 HAXM 8.6 以上Apple Silicon 机器跑 x86_64 镜像会非常慢不如直接在真机上测试。4.3 现象通知渠道已创建但 Android 9.0 真机上仍然不显示代码里createNotificationChannel执行了没有报错notify()也调了logcat 里也没有异常但真机上通知栏就是空的。模拟器上正常真机上不正常。原因真机上 Android 9.0 的“通知渠道”和“通知权限”是两层控制。App 在 9.0 上首次运行系统会默认拉高渠道的优先级但标记了“需要用户确认”的渠道可能被系统自动设成“免打扰”模式另一种更常见的情况是App 之前被用户强制停止过Force Stop在强制停止状态下系统会阻止通知、后台任务和广播而且NotificationManagerCompat.from(this).notify()不会抛异常只是静默丢弃。解决先检查NotificationManagerCompat.areNotificationsEnabled()返回是否 false再检查渠道的当前重要度channel.importance如果变成了IMPORTANCE_NONE说明渠道被系统或用户降级了。处理方式是进入 App 设置页让用户手动开通知权限不要尝试代码里强制改渠道重要度——API 28 不允许 App 修改已经被用户降级的渠道只能在用户主动开启后下次代码里重新 update 渠道。还有一个真机特有因素部分厂商 ROM 在 9.0 基础上加了自己的“省电模式”对不在白名单的 App 统一拦截通知。这种问题代码层排查不出来只能引导用户把 App 加入电池优化白名单或者用ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS引导跳转设置。我测试时习惯准备一台原生 Android 9.0 的模拟器做基准验证确认行为变化是系统原生问题还是厂商 ROM 的问题。4.4 现象升级到 compileSdkVersion 28 后 AAPT2 编译报错老项目从起 compileSdkVersion 26 升到 28同步后 Gradle 直接失败报AAPT2 error: failed analyzing style或者attribute android:keyboardNavigationCluster not found。原因API 28 的 AndroidX 兼容库和旧版 support library 混在一起资源里的 style 引用又指向老版本的 AppCompat导致 AAPT2 在解析资源时找不到新属性。另一个常见原因是compileSdk 升到 28 后Gradle 插件版本太低比如 3.1 以下它自带的 AAPT2 与 API 28 的资源格式不兼容。解决先升 Gradle Android 插件到 3.2.0 或以上它带了和 API 28 匹配的 AAPT2然后把 support library 统一升到 28.0.0或者直接迁到 AndroidXAPI 28 是 AndroidX 全面适配的起点版本迁移成本反而比旧版低。如果报错集中在 style 属性检查values/styles.xml里是否引用了已被移除的旧属性比如android:windowDisablePreview在某些依赖库里还有残留可以用tools:replace覆盖掉。5. 验证你的 App 真的“适配了 API 28”用 apkanalyzer 与 adb 把黑匣子撬开代码改完、打包出来别急着说“适配完成”。验证要做两层静态验证 APK 里的声明动态验证运行时行为。前者确认 manifest 和资源没被覆盖后者确认 API 28 的限制真的生效。5.1 用 apkanalyzer 核对 compileSdkVersion 与 targetSdkVersion命令与输出解读apkanalyzer是 Android SDK 自带的 APK 分析工具位于$ANDROID_HOME/tools/bin或cmdline-tools下。它读取 APK 的 manifest 和资源索引比直接解压看页纸更高效。# 查看 APK 声明的 targetSdkVersion 和 minSdkVersion apkanalyzer manifest target-sdk-version app-debug.apk apkanalyzer manifest min-sdk-version app-debug.apk # 查看 APK 使用的 SDK 平台版本编译版本 apkanalyzer manifest compile-sdk-version app-debug.apk输出里target-sdk-version 28表示系统会按 API 28 的行为规则运行compile-sdk-version 28表示编译时用的是 API 28 的 android.jar。如果target比compile低说明编译时引用了新 API 但运行时没声明适配这种情况在 API 28 上通常不会崩但到了未来版本会埋雷。静态验证还有个价值relealse 包和 debug 包可能被构建脚本改过配置比如 release 里为了兼容某个渠道targetSdkVersion 被渠道包脚本拉低了。用 apkanalyzer 对着 release 包跑一遍比看 Gradle 配置更接近用户实际拿到的那个文件。5.2 用 aapt dump badging 检查权限与 launchable-activityaapt是更老牌的资源打包工具dump badging能从 APK 里提取包名、版本、权限、入口 Activity 的完整清单。它不像 apkanalyzer 那样只抽取单项适合一次性看全局。# 查看 APK 的全部元信息权限、入口、SDK 版本 aapt dump badging app-release.apk | grep -E package:|sdkVersion|targetSdkVersion|uses-permission|launchable-activity输出的launchable-activity: namecom.example.ui.MainActivity要确认不是空的——如果入口 Activity 丢了装上后桌面图标点了没反应。uses-permission里要特别检查有没有多出来的权限比如 API 28 适配时为了兼容某 SDK 自动加上了READ_PHONE_STATE这种权限对用户来说很敏感能去掉就去掉。aapt在 Windows 上路径是$ANDROID_HOME\build-tools\28.0.3\aapt.exemacOS/Linux 直接$ANDROID_HOME/build-tools/28.0.3/aapt。这个工具和 apkanalyzer 的区别在于aapt 更偏“资源打包结果”适合查权限和入口apkanalyzer 更偏“manifest 语义”适合查 SDK 版本。两者配合用基本能覆盖发布前的大部分检查项。5.3 用 adb logcat 捕获 API 28 特有的运行时警告哪些日志值得追静态验证过了还得上真机或模拟器跑一轮动态验证。API 28 上有几条典型日志见到就得处理。# 抓取 API 28 相关警告按时间戳输出 adb logcat -v time | grep -E Cleartext|ForegroundServiceStartNotAllowed|MissingNotificationChannel|DeathException|RenderProcessGoneCleartext HTTP traffic not permitted说明有明文请求没配网络安全配置ForegroundServiceStartNotAllowedException说明前台服务启动方式不对MissingNotificationChannelException说明通知没绑渠道RenderProcessGone则是 WebView 渲染进程崩溃。这几条在 API 28 上都是“运行时才暴露”的问题静态检查看不出来。验证后台限制是否生效可以用 adb 把 App 切到后台然后观察# 模拟 APP 进入后台 adb shell input keyevent KEYCODE_HOME # 等 10 秒后查看后台任务是否被执行 adb shell dumpsys activity services com.example.app如果dumpsys里显示服务处于executing状态且时间戳没变化说明后台任务被系统延后了。这一步能帮你在版本上架前发现“适配了但没完全适配”的模块尤其是推送、IM、定位这类长驻后台的业务。6. 让 API 28 的工程在 CI 流水线里稳定复现三个值得养成的习惯API 28 的适配告一段落后别把环境装在本地就完事。团队协作时本地能跑通、CI 上失败的 9.0 项目比比皆是根源就是 SDK 组件版本在两端不一致。这里说三个我形成的习惯。第一个习惯CI 上先批量接受许可再装组件。yes | sdkmanager --licenses这句在本地可能多余但换一台新 CI 机器第一次跑构建如果卡在许可输入整个任务挂在那里等到超时。把它写进 CI 脚本的“安装 SDK”阶段后面所有组件安装都顺畅。第二个习惯用文本锁定所有 SDK 组件版本而不是靠sdkmanager platforms;android-28这种“装了就行”的思路。我在项目根目录放一个sdk-versions.sh里面固定BUILD_TOOLS_VERSION28.0.3、COMPILE_SDK28、TARGET_SDK28Gradle 和 CI 脚本都读这个变量。这样本地和 CI 永远不会出现“一边是 28.0.3、一边是 30.0.0”的打包差异。第三个习惯模拟器用 snapshot 而不是每次冷启动。API 28 模拟器冷启动要 30 秒到一分钟CI 里每轮构建都冷启动一次很浪费时间。我在emulator -avd api28 -no-snapshot -no-boot-anim跑完一轮后再手动emulator -avd api28正常退出会自动保存 snapshot后续测试直接emulator -avd api28 -no-window就能快速起来验证。注意 CI 上如果加了-no-snapshot那就每次都冷启动速度差异非常明显。有一次我调了半天的“API 28 上推送收不到”问题最后发现是 CI 机器上的模拟器镜像和本地不是同一个——本地的system-images;android-28;google_apis;x86_64带 GMSCI 上用的是default;x86_64没带 GMS推送通道根本没初始化成功。从那以后我坚持把 SDK 组件的版本锁进构建脚本任何人跑代码跑的都是一模一样的 API 28 环境。希望这篇笔记能帮你把 API 28 这条“存量兼容之路”走得更稳。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑