据上一篇文又是一个月过去了,虽说金九银十,但今年的氛围实在是太冷清了,能有一份工就不错了吧。但愿美元加息早点结束,经济早点好起来~
上一篇所说内容是所有 App 安装到 Android14 设备上的影响和需要注意的内容,本篇接下来就要介绍当 targetSdkVersion 升级到 34 时,App 需要注意和修改的地方。
1. 核心功能变更
1.1 前台服务类型
在 targetSdkVersion >= 34 的情况下,必须为应用内的每个前台服务(Foreground Service)指定至少一种前台服务类型。
什么是前台服务?前台服务(Foreground Service)是一种特殊类型的服务,用于执行与用户当前活动相关的长时间运行的任务,这些服务会在系统状态栏中显示通知,以告知用户应用正在前台执行任务,并且正在使用系统资源。在 Android12(API级别31)及更高版本的设备上,系统对短时间运行的前台服务进行了优化。系统会等待10秒,然后才显示与前台服务相关联的通知,以改善用户体验,减少即时通知的干扰。使用时需要在 Manifest 文件中申请 android.permission.FOREGROUND_SERVICE
权限。
前台服务类型是在 Android10 引入的,通过 android:foregroundServiceType
可以指定 <service> 的服务类型,可供选择的前台服务类型有:
1、camera:需要在后台时继续访问摄像头,比如支持多任务处理的视频聊天应用。
2、connectedDevice:与需要蓝牙、NFC、IR、USB 或网络连接的外部设备进行交互。
3、dataSync:数据传输操作,例如:数据上传或下载、备份与恢复操作、导入或导出操作、获取数据、本地文件处理、通过网络在设备和云之间传输数据。(这种类型可能会在后续 Android 版本中废弃,建议使用 WorkManager
或 user-initiated data transfer jobs 替换)
4、health
:用于任何需要长期运行的用例,以支持健身类应用程序,如运动追踪器。
5、location:需要位置访问的长时间运行的用例,例如导航和位置共享。
6、mediaPlayback:需要在后台持续播放音频或视频,或在 Android TV 上支持数字视频录制(DVR)功能。
7、mediaProjection:使用 MediaProjection
API 可以将内容投影到非主显示器或外部设备。这些内容不一定是专门的媒体内容。
8、microphone:需要持续在后台 (如录音机或通信应用程序) 进行麦克风捕获。
9、phoneCall:需要持续使用 ConnectionService
API 的场景。
10、remoteMessaging
:将短信从一台设备转移到另一台设备。在用户切换设备时,帮助确保用户消息任务的连续性。
11、shortService
:需要快速完成不能打断或推迟的重要工作;有 5 个特点:1)只能运行较短的时长,大概 3 分钟;2)不支持粘性前台服务;3)无法启动其他前台服务;4)不需要另外申请特定类型的权限,但少不了 FOREGROUND_SERVICE
权限;5)正在运行的前台服务不能在 shortService 类型之间切换。超时之后会调用 Service.onTimeout()
,这个 API 是 Android14 新增的,为了避免 ANR 建议实现 onTimeout
回调。
12、specialUse
:如果不是上述所有类型所包含的,则使用这个类型。除了声明FOREGROUND_SERVICE_TYPE_SPECIAL_USE
前台服务类型外,还应该在 Manifest 中声明用例。即需要在 <service> 元素中指定元素,如下所示。在 Google Play Console 中提交应用时,这些值和相应的用例将被审查。
// code 1 <service android:name="fooService" android:foregroundServiceType="specialUse"> <property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="foo"/> </service>
13、systemExempted
:预留给系统应用程序和特定的系统集成,以继续使用前台服务。普通 App 开发者不用管。
另外,上述 13 种类型中,做有彩色标记
的是 Android14 上新增的;其他的则是之前就有的。
举个常见的后台播放的例子,先在 Manifest 文件中申明权限,并设置好 foregroundServiceType
:
// code 2 <manifest ...> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> <application ...> <service android:name=".MusicPlayerService" android:foregroundServiceType="mediaPlayback" android:exported="false"> </service> </application> </manifest>
然后再去实现 MusicPlayerService 类,主要就是在 onStartCommand
回调中打开通知并开始播放音乐:
// code 3 class MusicPlayerService : Service() { private var mediaPlayer: MediaPlayer? = null override fun onBind(intent: Intent?): IBinder? { return null } @RequiresApi(Build.VERSION_CODES.O) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val NOTIFICATION_CHANNEL_ID = "com.example.foregroundservice" val notificationManager = NotificationManagerCompat.from(this) // If the notification supports a direct reply action, use // PendingIntent.FLAG_MUTABLE instead. val pendingIntent: PendingIntent = Intent(this, NotificationFullActivity::class.java).let { notificationIntent -> PendingIntent.getActivity( this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE ) } // 创建通知渠道 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val name: CharSequence = "Notification Channel Name" val description = "Description of Notification Channel" val importance = NotificationManager.IMPORTANCE_DEFAULT val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance) channel.description = description notificationManager.createNotificationChannel(channel) } // 构建通知 val builder: NotificationCompat.Builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) .setSmallIcon(android.R.drawable.ic_lock_idle_alarm) .setContentTitle("音乐播放中") .setContentText("艺术家 - 音乐") .setContentIntent(pendingIntent) .setPriority(NotificationCompat.PRIORITY_DEFAULT) // 启动通知 val notificationId = 1 // 每个通知的唯一标识符 if (ActivityCompat.checkSelfPermission( this, Manifest.permission.POST_NOTIFICATIONS ) == PackageManager.PERMISSION_GRANTED ) { notificationManager.notify(notificationId, builder.build()) } // Notification ID cannot be 0. startForeground(notificationId, builder.build()) // 播放音乐 mediaPlayer = MediaPlayer.create(this, R.raw.music1) mediaPlayer?.isLooping = true mediaPlayer?.start() return START_STICKY } }
最后就是启动这个 Service 了:
// code 4 requireActivity().startForegroundService(Intent(requireActivity(), MusicPlayerService::class.java))
如果没在 Manifest 文件中写明类型,那么在调用 startForeground()
方法时将会抛出 MissingForegroundServiceTypeException
异常。
对于上面的示例代码需要额外注意的是,在 Android13 及以上的手机上弹出 Notification 通知时,需要动态申请 android.permission.POST_NOTIFICATIONS
权限,当然还需要需要创建一个 NotificationChannel 渠道,这在 Android8 及以上就已经要求了。
再说回前台服务,上述每个前台服务类型所需要的权限是不一样的,并且这些权限都被定义成了普通权限,在默认情况下是已经授予的,用户不能撤销这些权限。例如 Camera 服务类型,需要在 Manifest 文件中声明 FOREGROUND_SERVICE_CAMERA
权限,并在运行时申请 Camera 权限。其他的服务类型都是如此:
Foreground Service Type |
Manifest requirements |
Runtime requirements |
Camera | FOREGROUND_SERVICE_CAMERA | CAMERA |
Connected device | FOREGROUND_SERVICE_CONNECTED_DEVICE | 在 Manifest 声明 CHANGE_NETWORK_STATE or CHANGE_WIFI_STATE or CHANGE_WIFI_MULTICAST_STATE or NFC or TRANSMIT_IR 或者请求运行时权限 BLUETOOTH_CONNECT or BLUETOOTH_ADVERTISE or BLUETOOTH_SCAN or UWB_RANGING 或者调用 UsbManager.requestPermission() |
Data sync | FOREGROUND_SERVICE_DATA_SYNC | - |
Health | FOREGROUND_SERVICE_HEALTH | 在 Manifest 声明 HIGH_SAMPLING_RATE_SENSORS 或申请运行时权限 (BODY_SENSORS or ACTIVITY_RECOGNITION) |
Location | FOREGROUND_SERVICE_LOCATION | ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION |
MediaPlayback | FOREGROUND_SERVICE_MEDIA_PLAYBACK | - |
Media projection | FOREGROUND_SERVICE_MEDIA_PROJECTION | 需要在之前调用 createScreenCaptureIntent() |
Microphone | FOREGROUND_SERVICE_MICROPHONE | RECORD_AUDIO |
Phone call | FOREGROUND_SERVICE_PHONE_CALL | 在 Manifest 声明 MANAGE_OWN_CALLS |
Remote messaging | FOREGROUND_SERVICE_REMOTE_MESSAGING | - |
Short service | - | - |
Special use | FOREGROUND_SERVICE_SPECIAL_USE | - |
System exempted | FOREGROUND_SERVICE_SYSTEM_EXEMPTED | - |
当然,肯定有特殊情况。如果自己的前台服务与上面提到的这 13 种都不太相关,那么官方建议将这些服务迁移到 WorkManager
或者 user-initiated data transfer jobs.
user-initiated data transfer jobs 就是由用户发起的数据传输任务。此 API 是 Android14 新增的,适用于需要由用户发起的持续时间较长的数据传输,例如从远程服务器下载文件。这些任务需要在通知栏中显示一个通知,会立即启动,并且可能在系统条件允许的情况下长时间运行。我们可以同时运行多个由用户发起的数据传输作业。
小结:如果目前应用中已用到了前台服务,且 targetSdkVersion 想升到 34,那么就得添加这个前台服务的类型;否则不用管。