Android 12 适配指南:SplashScreen API 与 PendingIntent 变更
Android 12(API 31)引入了新的 SplashScreen API 和 PendingIntent 可变性强制要求,这两个变更直接影响应用的启动体验和稳定性。本文结合实际代码,系统梳理适配方案。
一、SplashScreen API 适配
1.1 背景
Android 12 统一了应用启动页的规范,系统在应用启动前会显示一个启动页(根据应用的 Icon 和主题自动生成)。
影响:
- 原来使用
android:windowBackground实现的启动页效果会被系统覆盖 - 需要在
styles.xml中配置 SplashScreen 相关属性 - 可以使用
SplashScreen兼容库支持 Android 5.0+ 的设备
1.2 使用 SplashScreen 兼容库(推荐)
添加依赖:
// app/build.gradle
dependencies {
implementation "androidx.core:core-splashscreen:1.0.1"
}
配置主题:
<!-- values/themes.xml -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- 原主题配置不变 -->
</style>
<style name="Theme.App.Splash" parent="Theme.SplashScreen">
<!-- 启动页图标 -->
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
<!-- 图标背景(可选) -->
<item name="windowSplashScreenIconBackgroundColor">@color/white</item>
<!-- 启动页背景 -->
<item name="windowSplashScreenBackground">@color/primary</item>
<!-- 启动页退出后的主题 -->
<item name="postSplashScreenTheme">@style/AppTheme</item>
</style>
在 Manifest 中应用 Splash 主题:
<application
android:theme="@style/Theme.App.Splash"
... >
<activity
android:name=".MainActivity"
android:theme="@style/Theme.App.Splash"
... >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
在 Activity 中安装 SplashScreen:
// MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// 必须在 setContentView 之前调用
installSplashScreen()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 如果需要保持启动页显示直到数据加载完成:
// installSplashScreen().setKeepOnScreenCondition {
// !isDataLoaded
// }
}
}
1.3 延迟关闭启动页
如果应用需要在启动时加载数据,可以延迟关闭启动页:
class MainActivity : AppCompatActivity() {
private var isDataLoaded = false
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
// 保持启动页,直到数据加载完成
splashScreen.setKeepOnScreenCondition {
!isDataLoaded
}
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 模拟数据加载
loadData {
isDataLoaded = true
}
}
private fun loadData(callback: () -> Unit) {
Thread {
Thread.sleep(2000) // 模拟耗时操作
runOnUiThread {
callback()
}
}.start()
}
}
1.4 自定义启动页动画
installSplashScreen().setOnExitAnimationListener { splashScreenView ->
// 自定义退出动画
splashScreenView.view.alpha = 1f
splashScreenView.view.animate()
.alpha(0f)
.setDuration(500)
.withEndAction {
splashScreenView.remove()
}
.start()
}
二、PendingIntent 可变性强制要求
2.1 背景
Android 12 要求创建 PendingIntent 时必须显式指定可变性(FLAG_MUTABLE 或 FLAG_IMMUTABLE),否则会抛出 IllegalArgumentException。
影响范围: 所有使用 PendingIntent 的场景(通知、闹钟、定时任务等)。
2.2 适配方案
// ❌ 错误:不指定可变性(Android 12+ 会崩溃)
val pendingIntent = PendingIntent.getActivity(
context,
requestCode,
intent,
PendingIntent.FLAG_UPDATE_CURRENT // 缺少可变性标志
)
// ✅ 正确:显式指定 FLAG_IMMUTABLE(大多数场景)
val pendingIntent = PendingIntent.getActivity(
context,
requestCode,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// ✅ 需要修改 Extra 的场景:使用 FLAG_MUTABLE
val mutablePendingIntent = PendingIntent.getActivity(
context,
requestCode,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
2.3 各场景适配示例
通知中的 PendingIntent
val notification = NotificationCompat.Builder(context, channelId)
.setContentTitle("标题")
.setContentText("内容")
.setSmallIcon(R.drawable.ic_notification)
.setContentIntent(
PendingIntent.getActivity(
context,
0,
Intent(context, MainActivity::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
)
.build()
AlarmManager 中的 PendingIntent
val alarmIntent = PendingIntent.getBroadcast(
context,
0,
Intent(context, AlarmReceiver::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerTime,
alarmIntent
)
AppWidget 中的 PendingIntent
val pendingIntent = PendingIntent.getActivity(
context,
appWidgetId, // 使用 appWidgetId 作为 requestCode,避免冲突
Intent(context, MainActivity::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
remoteViews.setOnClickPendingIntent(R.id.button, pendingIntent)
2.4 兼容旧版本
// 兼容 Android 12 以下版本
fun createPendingActivityIntent(
context: Context,
requestCode: Int,
intent: Intent
): PendingIntent {
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
return PendingIntent.getActivity(context, requestCode, intent, flags)
}
三、其他 Android 12 适配要点
3.1 前台服务通知延迟
Android 12 开始,前台服务启动后,通知会延迟 10 秒 显示(给用户时间了解应用在前台运行)。
无需适配,但需要注意用户体验:如果应用依赖立即显示通知,需要重新考虑设计。
3.2 蓝牙权限细化
Android 12 将原来的 BLUETOOTH / BLUETOOTH_ADMIN 权限替换为更细化的权限:
<!-- AndroidManifest.xml -->
<!-- 扫描蓝牙设备(不需要位置权限了!) -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- 连接蓝牙设备 -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- 广播蓝牙设备(可选) -->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!-- 兼容旧版本 -->
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
代码示例:
// 扫描蓝牙设备(Android 12+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.BLUETOOTH_SCAN
) != PackageManager.PERMISSION_GRANTED
) {
requestPermissions(arrayOf(Manifest.permission.BLUETOOTH_SCAN), REQUEST_BLUETOOTH)
}
} else {
// Android 11 及以下,需要位置权限
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_LOCATION)
}
}
3.3 精确闹钟权限
Android 12 引入 SCHEDULE_EXACT_ALARM 权限,用于设置精确闹钟。
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
检查并请求权限:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
if (!alarmManager.canScheduleExactAlarms()) {
// 引导用户到设置页面开启权限
val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM).apply {
data = Uri.fromParts("package", packageName, null)
}
startActivity(intent)
}
}
四、Gradle 配置
// app/build.gradle
android {
compileSdk 34 // 或 35
defaultConfig {
targetSdk 34
minSdk 21
}
}
dependencies {
// SplashScreen 兼容库
implementation "androidx.core:core-splashscreen:1.0.1"
// AndroidX 兼容库
implementation "androidx.appcompat:appcompat:1.7.0"
}
五、适配检查清单
完成 Android 12 适配后,建议对照以下清单进行检查:
- 已使用 SplashScreen 兼容库,或已适配新的启动页规范
- 所有
PendingIntent创建时都显式指定了FLAG_MUTABLE或FLAG_IMMUTABLE - 蓝牙相关功能已迁移到新的权限(
BLUETOOTH_SCAN/BLUETOOTH_CONNECT) - 精确闹钟功能已处理
SCHEDULE_EXACT_ALARM权限 - 在 Android 12 真机或模拟器上完整测试
六、参考资源
如果本文对你有帮助,欢迎点赞收藏。如有疑问,欢迎在评论区交流。