
Gradle 是 Android 官方统一的项目构建工具,你可以把它理解成「安卓项目的自动化管家」。没有它,你的 Kotlin/Java 代码、图片、动画、布局资源无法编译成手机能安装的 APK 安装包。




DNS 解析失败,Android Studio 无法解析 Gradle 插件仓库域名,国内网络无法直连谷歌 / Gradle 官方插件源,域名找不到服务器。

API 23 是不是最新?
不是,而且不推荐你用 API 23
API 23 = Android 6.0,发布于 2015 年,非常老旧;
Jetpack Compose(你现在项目用的界面框架)最低兼容要求是 API 21,但 API23 缺少大量现代界面、通知权限相关 API,后续你做「读取手机消息、交互动画」会频繁出现兼容报错;
小米 13 出厂 Android13 (API33),虽然能装 API23 的 App,但高系统权限逻辑和 6.0 完全不一样,适配会很麻烦;
最新正式 Android 版本是 Android15(API35),建议改回 API24(Android7.0),兼顾兼容与功能。

minSdk=24(Android7.0)能补齐什么
完整支持通知渠道兼容代码,在小米 13 等新手机上通知、提示音 100% 正常弹出;
完善精确闹钟 API setExactAndAllowWhileIdle,大幅降低高系统后台杀定时任务概率;
XML 矢量动画、帧动画全部 API 原生兼容,无渲染 bug;
权限细分更完善,适配安卓 10~14 的后台、定位、通知限制代码更简洁,不需要大量 if 版本判断;
兼容绝大多数现代动画、定时第三方库,不会出现依赖版本冲突


API(Application Programming Interface)应用程序编程接口,一套功能调用规则 / 函数入口,是系统、库对外开放的 “操作按钮”。
SDK(Software Development Kit)软件开发工具包,一整套开发工具合集,里面包含:全部 API 源码文档、编译工具、模拟器、资源工具、系统库文件。
Gradle 项目构建工具,自动化管理工具,负责连接 Android Studio、SDK、第三方库,完成下载、编译、打包全流程。
Scaffold 英文原意:脚手架、支架
在 Jetpack Compose 里:页面标准布局容器,安卓官方给 App 页面封装好的「标准页面骨架」。









class MainActivity : ComponentActivity() {override funonCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()setContent {PetTheme {// 外层Box铺满屏幕,设置全局绿色背景Box(modifier = Modifier.fillMaxSize().background(colorResource(R.color.darkgreen))) {// Scaffold设置透明,不遮挡外层绿色Scaffold(modifier = Modifier.fillMaxSize(),containerColor = Color.Transparent) { innerPadding ->Greeting(name = "Android",modifier = Modifier.padding(innerPadding))}}}}}}@ComposablefunGreeting(name: String, modifier: Modifier = Modifier) {Text(text = "Hello $name!",modifier = modifier)}@Preview@ComposablefunGreetingPreview() {PetTheme {Box(modifier = Modifier.fillMaxSize().background(colorResource(R.color.darkgreen))) {Scaffold(modifier = Modifier.fillMaxSize(),containerColor = Color.Transparent) { innerPadding ->Greeting("Android", Modifier.padding(innerPadding))}}}}



package com.example.petimport android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.activity.enableEdgeToEdgeimport androidx.compose.foundation.backgroundimport androidx.compose.foundation.clickableimport androidx.compose.foundation.layout.Boximport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.layout.sizeimport androidx.compose.material3.Scaffoldimport androidx.compose.runtime.Composableimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.setValueimport androidx.compose.ui.Alignmentimport androidx.compose.ui.Modifierimport androidx.compose.ui.graphics.Colorimport androidx.compose.ui.res.colorResourceimport androidx.compose.ui.tooling.preview.Previewimport androidx.compose.ui.unit.dpimport androidx.compose.ui.unit.Dpimport com.example.pet.ui.theme.PetThemeimport androidx.compose.animation.core.animateDpAsStateimport androidx.compose.animation.core.animateFloatAsStateimport androidx.compose.animation.core.tweenimport androidx.compose.ui.graphics.graphicsLayerclass MainActivity : ComponentActivity() { override funonCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { PetTheme { // 全局绿色背景外层容器 Box( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.darkgreen)) ) { Scaffold( modifier = Modifier.fillMaxSize(), containerColor = Color.Transparent ) { innerPadding -> // 居中橙色像素小狗组件 Box( modifier = Modifier .fillMaxSize() .padding(innerPadding), contentAlignment = Alignment.Center ) { PixelDog() } } } } } }}/** * 橙色像素小狗 + 点击缩放交互动画 */@Composablefun PixelDog(modifier: Modifier = Modifier) { // 控制动画触发 var jumpTrigger by remember { mutableStateOf(false) } // Y轴偏移:向上跳跃100dp val jumpOffset: Dp by animateDpAsState( targetValue = if (jumpTrigger) (-200).dp else 0.dp, animationSpec = tween(durationMillis = 400), finishedListener = { jumpTrigger = false } // 动画结束复位 ) // 逆时针旋转360度 val rotateAngle: Float by animateFloatAsState( targetValue = if (jumpTrigger) -360f else 0f, animationSpec = tween(durationMillis = 400) ) Box( modifier = modifier .size(120.dp) .clickable { jumpTrigger = true } // 点击触发后空翻 .graphicsLayer { translationY = jumpOffset.toPx() rotationZ = rotateAngle }, contentAlignment = Alignment.Center ) { // 橙色像素小狗纯色方块(无图片依赖,直接消除资源报错) Box( modifier = Modifier .size(300.dp) .background(Color(0xFFfe8862)) ) }}@Preview@Composablefun Preview() { PetTheme { Box( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.darkgreen)) ) { Scaffold( modifier = Modifier.fillMaxSize(), containerColor = Color.Transparent ) { innerPadding -> Box( modifier = Modifier .fillMaxSize() .padding(innerPadding), contentAlignment = Alignment.Center ) { PixelDog() } } } }}https://steampp.net/

Mac 弹出要开机密码,是因为它需要修改系统网络代理 / Hosts 文件
https://replicate.com
(3)所以我改为使用线上可商用的音效资源:
https://www.ear0.com


package com.example.petimport android.media.MediaPlayerimport android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.activity.enableEdgeToEdgeimport androidx.compose.animation.core.animateDpAsStateimport androidx.compose.animation.core.animateFloatAsStateimport androidx.compose.animation.core.tweenimport androidx.compose.foundation.backgroundimport androidx.compose.foundation.clickableimport androidx.compose.foundation.layout.Boximport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.layout.sizeimport androidx.compose.material3.Scaffoldimport androidx.compose.runtime.Composableimport androidx.compose.runtime.DisposableEffectimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.setValueimport androidx.compose.ui.Alignmentimport androidx.compose.ui.Modifierimport androidx.compose.ui.graphics.Colorimport androidx.compose.ui.graphics.graphicsLayerimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.res.colorResourceimport androidx.compose.ui.tooling.preview.Previewimport androidx.compose.ui.unit.Dpimport androidx.compose.ui.unit.dpimport com.example.pet.ui.theme.PetThemeclass MainActivity : ComponentActivity() { override funonCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { PetTheme { Box( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.darkgreen)) ) { Scaffold( modifier = Modifier.fillMaxSize(), containerColor = Color.Transparent ) { innerPadding -> Box( modifier = Modifier .fillMaxSize() .padding(innerPadding), contentAlignment = Alignment.Center ) { PixelDog() } } } } } }}/** * 橙色像素小狗 + 点击缩放跳跃旋转 + 播放狗叫wav音频 */@Composablefun PixelDog(modifier: Modifier = Modifier) { val context = LocalContext.current var jumpTrigger by remember { mutableStateOf(false) } val mediaPlayer = remember { MediaPlayer.create(context, R.raw.dog_bark) } DisposableEffect(Unit) { onDispose { mediaPlayer.release() } } val jumpOffset: Dp by animateDpAsState( targetValue = if (jumpTrigger) (-200).dp else 0.dp, animationSpec = tween(durationMillis = 400), finishedListener = { jumpTrigger = false } ) val rotateAngle: Float by animateFloatAsState( targetValue = if (jumpTrigger) -360f else 0f, animationSpec = tween(durationMillis = 400) ) funonDogClick() { jumpTrigger = true mediaPlayer.seekTo(0) mediaPlayer.start() } Box( modifier = modifier .size(120.dp) .clickable { onDogClick() } .graphicsLayer { translationY = jumpOffset.toPx() rotationZ = rotateAngle }, contentAlignment = Alignment.Center ) { Box( modifier = Modifier .size(300.dp) .background(Color(0xFFfe8862)) ) }}@Preview@Composablefun Preview() { PetTheme { Box( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.darkgreen)) ) { Scaffold( modifier = Modifier.fillMaxSize(), containerColor = Color.Transparent ) { innerPadding -> Box( modifier = Modifier .fillMaxSize() .padding(innerPadding), contentAlignment = Alignment.Center ) { PixelDog() } } } }}
400 是服务端参数校验失败,代表客户端(Android Studio 里的 AI Agent 插件)发给 DeepSeek 的请求格式不符合接口最新要求;
关键词 reasoning_content:DeepSeek 近期更新了思考链(深度思考)模式的入参强制校验,旧版插件没有携带这个字段,直接被 API 拒绝返回 400。

小狗叫 许可:CC-BY-NC 作者:centianhua 来源:耳聆网 https://www.ear0.com/sound/38121
(2)把mp3命名为burp.mp3复制粘贴给res/raw文件夹
(3)修改代码:
package com.example.petimport android.media.MediaPlayerimport android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.activity.enableEdgeToEdgeimport androidx.compose.animation.core.animateDpAsStateimport androidx.compose.animation.core.animateFloatAsStateimport androidx.compose.animation.core.tweenimport androidx.compose.foundation.backgroundimport androidx.compose.foundation.clickableimport androidx.compose.foundation.layout.Boximport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.layout.sizeimport androidx.compose.runtime.Composableimport androidx.compose.runtime.DisposableEffectimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableLongStateOfimport androidx.compose.runtime.mutableIntStateOfimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.saveable.rememberSaveableimport androidx.compose.runtime.setValueimport androidx.compose.ui.Alignmentimport androidx.compose.ui.Modifierimport androidx.compose.ui.graphics.Colorimport androidx.compose.ui.graphics.graphicsLayerimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.res.colorResourceimport androidx.compose.ui.tooling.preview.Previewimport androidx.compose.ui.unit.Dpimport androidx.compose.ui.unit.dpimport com.example.pet.ui.theme.PetThemeclass MainActivity : ComponentActivity() { override funonCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { PetTheme { Box( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.darkgreen)) ) { androidx.compose.material3.Scaffold( modifier = Modifier.fillMaxSize(), containerColor = Color.Transparent ) { innerPadding -> Box( modifier = Modifier .fillMaxSize() .padding(innerPadding), contentAlignment = Alignment.Center ) { PixelDog() } } } } } }}@Composablefun PixelDog(modifier: Modifier = Modifier) { val context = LocalContext.current val FOUR_HOUR_MILLIS = 4 * 60 * 60 * 1000 var firstClickTime by rememberSaveable { mutableLongStateOf(0L) } var clickCount by rememberSaveable { mutableIntStateOf(0) } var triggerNormalAnim by remember { mutableStateOf(false) } var triggerBurpAnim by remember { mutableStateOf(false) } val barkPlayer = remember { MediaPlayer.create(context, R.raw.dog_bark) } val burpPlayer = remember { MediaPlayer.create(context, R.raw.burp) } DisposableEffect(barkPlayer, burpPlayer) { onDispose { barkPlayer.release() burpPlayer.release() } } funisBurpMode(): Boolean { if (firstClickTime == 0L) return false val now = System.currentTimeMillis() val timeRange = now - firstClickTime return timeRange <= FOUR_HOUR_MILLIS && clickCount > 10 } val jumpOffset: Dp by animateDpAsState( targetValue = if (triggerNormalAnim) (-200).dp else 0.dp, animationSpec = tween(400), finishedListener = { triggerNormalAnim = false } ) val rotateAngle: Float by animateFloatAsState( targetValue = if (triggerNormalAnim) -360f else 0f, animationSpec = tween(400) ) val scaleX by animateFloatAsState( targetValue = if (triggerBurpAnim) 1.3f else 1f, animationSpec = tween(450), finishedListener = { triggerBurpAnim = false } ) val scaleY by animateFloatAsState( targetValue = if (triggerBurpAnim) 0.6f else 1f, animationSpec = tween(450) ) funhandleClick() { val currentTime = System.currentTimeMillis() if (firstClickTime != 0L && currentTime - firstClickTime > FOUR_HOUR_MILLIS) { clickCount = 0 firstClickTime = 0 } if (firstClickTime == 0L) { firstClickTime = currentTime } clickCount += 1 if (isBurpMode()) { triggerBurpAnim = true burpPlayer.seekTo(0) burpPlayer.start() } else { triggerNormalAnim = true barkPlayer.seekTo(0) barkPlayer.start() } } Box( modifier = modifier .size(120.dp) .clickable { handleClick() } .graphicsLayer { if (isBurpMode()) { this.scaleX = scaleX this.scaleY = scaleY } else { translationY = jumpOffset.toPx() rotationZ = rotateAngle } }, contentAlignment = Alignment.Center ) { Box( modifier = Modifier .size(300.dp) .background(Color(0xFFfe8862)) ) }}@Preview@Composablefun Preview() { PetTheme { Box( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.darkgreen)) ) { androidx.compose.material3.Scaffold( modifier = Modifier.fillMaxSize(), containerColor = Color.Transparent ) { innerPadding -> Box( modifier = Modifier .fillMaxSize() .padding(innerPadding), contentAlignment = Alignment.Center ) { PixelDog() } } } }}07每天早上八点、中午十二点、晚上十点和用户打招呼,调用手机通知权限,发一条消息,分别从话术库中选择一条发送,提示音是狗叫
(1)修改 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?><manifestxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="com.example.pet"> <!-- 权限全部写在application外面,manifest内部 --> <uses-permissionandroid:name="android.permission.POST_NOTIFICATIONS"/> <uses-permissionandroid:name="android.permission.SCHEDULE_EXACT_ALARM"/> <uses-permissionandroid:name="android.permission.USE_EXACT_ALARM"/> <applicationandroid:allowBackup="true"android:dataExtractionRules="@xml/data_extraction_rules"android:fullBackupContent="@xml/backup_rules"android:icon="@drawable/ic_launcher_background"android:label="@string/app_name"android:roundIcon="@drawable/ic_launcher_background"android:supportsRtl="true"android:theme="@style/Theme.Pet"> <!-- 广播接收器必须放在application标签内部 --> <receiverandroid:name=".NotificationReceiver"android:exported="false"/> <activityandroid:name=".MainActivity"android:exported="true"android:label="@string/app_name"android:theme="@style/Theme.Pet"> <intent-filter> <actionandroid:name="android.intent.action.MAIN" /> <categoryandroid:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application></manifest>(2)新建NotificationReceiver.kt

新建于这个文件夹中,类型选择class

package com.example.petimport android.content.BroadcastReceiverimport android.content.Contextimport android.content.Intentimport android.content.pm.PackageManagerimport android.media.MediaPlayerimport androidx.core.app.NotificationCompatimport androidx.core.app.NotificationManagerCompatimport androidx.core.content.ContextCompatimport kotlin.random.Randomclass NotificationReceiver : BroadcastReceiver() {private val greetList = listOf("早上好,新的一天元气满满!","起床啦,今天也要开开心心","午饭时间到,记得好好吃饭哦","中午歇一会,放松一下吧","晚上好,忙碌一天辛苦啦","夜深了,早点休息不要熬夜"//这些台词我后面都改成不分时间段的了)override funonReceive(context: Context?, intent: Intent?) {if (context == null) returnval selectText = greetList[Random.nextInt(greetList.size)]val player = MediaPlayer.create(context, R.raw.dog_bark)player.start()player.setOnCompletionListener {it.release()}// 判断通知权限if (ContextCompat.checkSelfPermission(context,android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {try {val notification = NotificationCompat.Builder(context, "pet_remind").setSmallIcon(R.drawable.ic_launcher_background).setContentTitle("你的小宠物").setContentText(selectText).setPriority(NotificationCompat.PRIORITY_HIGH).build()NotificationManagerCompat.from(context).notify(1001, notification)} catch (e: SecurityException) {// 用户拒绝权限时什么都不执行}}}}
(3)新建ScheduleManager.kt,类型选择object
package com.example.petimport android.app.AlarmManagerimport android.app.PendingIntentimport android.content.Contextimport android.content.Intentimport java.util.*object ScheduleManager { funsetupAllAlarm(context: Context) { setAlarm(context, 8, 0, 1001) setAlarm(context, 12, 0, 1002) setAlarm(context, 22, 0, 1003) } private funsetAlarm(context: Context, hour: Int, minute: Int, requestCode: Int) { val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager val intent = Intent(context, NotificationReceiver::class.java) val pendingIntent = PendingIntent.getBroadcast( context, requestCode, intent, PendingIntent.FLAG_IMMUTABLE ) val calendar = Calendar.getInstance().apply { timeInMillis = System.currentTimeMillis() set(Calendar.HOUR_OF_DAY, hour) set(Calendar.MINUTE, minute) set(Calendar.SECOND, 0) } if (calendar.timeInMillis <= System.currentTimeMillis()) { calendar.add(Calendar.DAY_OF_YEAR, 1) } alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent ) } funcancelAllAlarm(context: Context) { val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager listOf(1001, 1002, 1003).forEach { code -> val intent = Intent(context, NotificationReceiver::class.java) val pendingIntent = PendingIntent.getBroadcast( context, code, intent, PendingIntent.FLAG_IMMUTABLE ) alarmManager.cancel(pendingIntent) } }}(4)在build.gradle.kts中添加依赖

dependencies { implementation("androidx.work:work-runtime-ktx:2.10.0")}(5)修改MainActivity.kt
package com.example.petimport android.media.MediaPlayerimport android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.activity.enableEdgeToEdgeimport androidx.compose.animation.core.animateDpAsStateimport androidx.compose.animation.core.animateFloatAsStateimport androidx.compose.animation.core.tweenimport androidx.compose.foundation.backgroundimport androidx.compose.foundation.clickableimport androidx.compose.foundation.layout.Boximport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.layout.sizeimport androidx.compose.runtime.Composableimport androidx.compose.runtime.DisposableEffectimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableLongStateOfimport androidx.compose.runtime.mutableIntStateOfimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.saveable.rememberSaveableimport androidx.compose.runtime.setValueimport androidx.compose.ui.Alignmentimport androidx.compose.ui.Modifierimport androidx.compose.ui.graphics.Colorimport androidx.compose.ui.graphics.graphicsLayerimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.res.colorResourceimport androidx.compose.ui.tooling.preview.Previewimport androidx.compose.ui.unit.Dpimport androidx.compose.ui.unit.dpimport com.example.pet.ui.theme.PetThemeclass MainActivity : ComponentActivity() { // 把权限申请的 launcher 放到方法外面,作为成员变量 private val requestPermissionLauncher = registerForActivityResult(androidx.activity.result.contract.ActivityResultContracts.RequestPermission()) { granted -> if (granted) { ScheduleManager.setupAllAlarm(this) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { //你的Compose页面代码不变 PetTheme { Box( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.darkgreen)) ) { androidx.compose.material3.Scaffold( modifier = Modifier.fillMaxSize(), containerColor = Color.Transparent ) { innerPadding -> Box( modifier = Modifier .fillMaxSize() .padding(innerPadding), contentAlignment = Alignment.Center ) { PixelDog() } } } } } requestNotificationPermission() } private fun requestNotificationPermission() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { requestPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS) } else { ScheduleManager.setupAllAlarm(this) } }}@Composablefun PixelDog(modifier: Modifier = Modifier) { val context = LocalContext.current val FOUR_HOUR_MILLIS = 4 * 60 * 60 * 1000 var firstClickTime by rememberSaveable { mutableLongStateOf(0L) } var clickCount by rememberSaveable { mutableIntStateOf(0) } var triggerNormalAnim by remember { mutableStateOf(false) } var triggerBurpAnim by remember { mutableStateOf(false) } val barkPlayer = remember { MediaPlayer.create(context, R.raw.dog_bark) } val burpPlayer = remember { MediaPlayer.create(context, R.raw.burp) } DisposableEffect(barkPlayer, burpPlayer) { onDispose { barkPlayer.release() burpPlayer.release() } } fun isBurpMode(): Boolean { if (firstClickTime == 0L) return false val now = System.currentTimeMillis() val timeRange = now - firstClickTime return timeRange <= FOUR_HOUR_MILLIS && clickCount > 10 } val jumpOffset: Dp by animateDpAsState( targetValue = if (triggerNormalAnim) (-200).dp else 0.dp, animationSpec = tween(400), finishedListener = { triggerNormalAnim = false } ) val rotateAngle: Float by animateFloatAsState( targetValue = if (triggerNormalAnim) -360f else 0f, animationSpec = tween(400) ) val scaleX by animateFloatAsState( targetValue = if (triggerBurpAnim) 1.3f else 1f, animationSpec = tween(450), finishedListener = { triggerBurpAnim = false } ) val scaleY by animateFloatAsState( targetValue = if (triggerBurpAnim) 0.6f else 1f, animationSpec = tween(450) ) fun handleClick() { val currentTime = System.currentTimeMillis() if (firstClickTime != 0L && currentTime - firstClickTime > FOUR_HOUR_MILLIS) { clickCount = 0 firstClickTime = 0 } if (firstClickTime == 0L) { firstClickTime = currentTime } clickCount += 1 if (isBurpMode()) { triggerBurpAnim = true burpPlayer.seekTo(0) burpPlayer.start() } else { triggerNormalAnim = true barkPlayer.seekTo(0) barkPlayer.start() } } Box( modifier = modifier .size(120.dp) .clickable { handleClick() } .graphicsLayer { if (isBurpMode()) { this.scaleX = scaleX this.scaleY = scaleY } else { translationY = jumpOffset.toPx() rotationZ = rotateAngle } }, contentAlignment = Alignment.Center ) { Box( modifier = Modifier .size(300.dp) .background(Color(0xFFfe8862)) ) }}@Preview@Composablefun Preview() { PetTheme { Box( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.darkgreen)) ) { androidx.compose.material3.Scaffold( modifier = Modifier.fillMaxSize(), containerColor = Color.Transparent ) { innerPadding -> Box( modifier = Modifier .fillMaxSize() .padding(innerPadding), contentAlignment = Alignment.Center ) { PixelDog() } } } }}08在小米13上部署测试
(1)开启手机开发者模式,设置/我的设备/全部参数与信息,点击OS版本七次,进入开发者模式

(2)设置/更多设置/开发者选项,开启USB调试和USB安装权限

(3)数据线连接电脑和手机
(4)Android Studio识别手机,点运行

于是手机上就会多出pet应用

并且能够运行,期待到点给我发消息。

测试结果:启动时有申请通知权限的提示框,但是到点并没有弹出通知,测试安卓studio的虚拟机也是同样结果。
更改NotificationReceiver的代码:
package com.example.petimport android.content.BroadcastReceiverimport android.content.Contextimport android.content.Intentimport android.content.pm.PackageManagerimport android.media.MediaPlayerimport android.os.Buildimport androidx.core.app.NotificationCompatimport androidx.core.app.NotificationManagerCompatimport androidx.core.content.ContextCompatimport android.app.NotificationChannelimport android.app.NotificationManagerimport kotlin.random.Randomclass NotificationReceiver : BroadcastReceiver() { private val greetList = listOf( "早上好,新的一天元气满满!", "起床啦,今天也要开开心心", "午饭时间到,记得好好吃饭哦", "中午歇一会,放松一下吧", "晚上好,忙碌一天辛苦啦", "夜深了,早点休息不要熬夜" ) private val CHANNEL_ID = "pet_remind" override funonReceive(context: Context?, intent: Intent?) { if (context == null) return // Android8.0+ 必须创建通知通道,否则通知不会弹出 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( CHANNEL_ID, "宠物定时问候", NotificationManager.IMPORTANCE_HIGH ).apply { setShowBadge(true) } val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) } val selectText = greetList[Random.nextInt(greetList.size)] val player = MediaPlayer.create(context, R.raw.dog_bark) player.start() player.setOnCompletionListener { it.release() } if (ContextCompat.checkSelfPermission( context, android.Manifest.permission.POST_NOTIFICATIONS ) == PackageManager.PERMISSION_GRANTED ) { try { val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.ic_launcher_background) .setContentTitle("你的小宠物") .setContentText(selectText) .setPriority(NotificationCompat.PRIORITY_HIGH) .build() NotificationManagerCompat.from(context).notify(1001, notification) } catch (_: SecurityException) {} } }}虚拟机通知成功。
重新安装pet。
但是第二次试发现只有在界面的时候才会发通知,后台无法运行。
于是在小米应用设置中修改pet应用权限:改了电池消耗和自启动。


通知功能就正常了。
夜雨聆风