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_MUTABLEFLAG_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_MUTABLEFLAG_IMMUTABLE
  • 蓝牙相关功能已迁移到新的权限(BLUETOOTH_SCAN / BLUETOOTH_CONNECT
  • 精确闹钟功能已处理 SCHEDULE_EXACT_ALARM 权限
  • 在 Android 12 真机或模拟器上完整测试

六、参考资源


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

相关推荐
用户5052372099151 小时前
一张表看懂 Android 8-15 所有适配要点
android
_祝你今天愉快2 小时前
Android 12 (AOSP) 添加自定义系统服务
android
程序员陆业聪4 小时前
AI编码提效实战:Skill、Rule与上下文工程
android
程序员陆业聪5 小时前
AI驱动需求梳理与Spec编写:让PRD自动变成技术方案
android
李艺为6 小时前
Android Studio使用switch匹配资源id时报需要常量表达式解决办法
android
YaBingSec8 小时前
玄机靶场-2024ccb初赛sc05 WP
android·运维·网络·笔记·安全·ssh
常利兵8 小时前
解锁Android嵌入式照片选择器,让你的App体验丝滑起飞
android
峥嵘life8 小时前
Android 切换用户后无法获取 MAC 地址分析解决
android·python·macos
JJay.9 小时前
Android BLE 为什么连上了却收不到数据
android