Android 12 适配指南:SplashScreen API 与 PendingIntent 变更

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 兼容库(推荐)

添加依赖:

gradle 复制代码
// app/build.gradle
dependencies {
    implementation "androidx.core:core-splashscreen:1.0.1"
}

配置主题:

xml 复制代码
<!-- 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 主题:

xml 复制代码
<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:

kotlin 复制代码
// 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 延迟关闭启动页

如果应用需要在启动时加载数据,可以延迟关闭启动页:

kotlin 复制代码
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 自定义启动页动画

kotlin 复制代码
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 适配方案

kotlin 复制代码
// ❌ 错误:不指定可变性(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
kotlin 复制代码
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
kotlin 复制代码
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
kotlin 复制代码
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 兼容旧版本

kotlin 复制代码
// 兼容 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 权限替换为更细化的权限:

xml 复制代码
<!-- 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" />

代码示例:

kotlin 复制代码
// 扫描蓝牙设备(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 权限,用于设置精确闹钟。

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

检查并请求权限:

kotlin 复制代码
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 配置

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 真机或模拟器上完整测试

六、参考资源


如果本文对你有帮助,欢迎点赞收藏。如有疑问,欢迎在评论区交流。

相关推荐
三少爷的鞋4 小时前
AI 时代,我们都将成为通才型开发者:只懂 Android,已经不够了
android
Dovis(誓平步青云)8 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码10 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程10 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程13 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程14 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程15 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程15 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓15 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程15 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer