前台服务
1. 指定前台服务类型
以 Android 14(API 级别 34)或更高版本为目标平台的应用,需要为应用中的每项前台服务指定服务类型,因为系统需要特定类型的前台服务满足特定用例。具体介绍如下:
在Android 10 在 <service> 元素内引入了 android:foregroundServiceType 属性。
如果您的应用以 Android 14 为目标平台,则必须指定适当的前台服务类型,可组合使用多个类型;以下了可供选择的前台服务类型:
• camera
• connectedDevice
• dataSync
• health
• location
• mediaPlayback
• mediaProjection
• microphone
• phoneCall
• remoteMessaging
• shortService
• specialUse
• systemExempted
如果应用中的用例与这些类型均不相关,考虑使用 WorkManager 或Android 14中引入的新Api,即作业必须是用户发起的数据传输作业。
在上述的类型中,Android 14 中新增 health, remoteMessaging, shortService, specialUse
和 systemExempted
等类型。
类型示例:
-
<manifest ...>
-
<uses-permission android:name=".FOREGROUND_SERVICE" />
-
<uses-permission android:name=".FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
-
<application ...>
-
<service
-
android:name=".MyMediaPlaybackService"
-
android:foregroundServiceType="mediaPlayback"
-
android:exported="false">
-
</service>
-
</application>
-
</manifest>
备注: 如果以 Android 14 或更高版本为目标的应用未在清单中定义给定服务的类型,系统会在调用 startForeground() 时引发 MissingForegroundServiceTypeException。
2. 启动时包含前台服务类型
启动前台服务,最好的方式,是使用 ServiceCompat 版本的 startForeground()(适用于 androidx-core 1.12 及更高版本),传入前台服务的类型值,可传入一个或多个,比如aa,或aa|bb, 或aa|bb|cc;
启动服务,(0, notification, FOREGROUND_SERVICE_TYPE_LOCATION)如果调用中未指定前台服务类型,则该类型默认为清单中定义的值。如果您没有在清单中指定服务类型,系统会抛出 MissingForegroundServiceTypeException。
如果前台服务在您启动后需要新的权限,需要调用 startForeground() 并添加新的服务类型。例如,假设健身应用运行一项跑步跟踪器服务,该服务始终需要 location 信息,只想跟踪其位置,您的应用应调用 startForeground() 并仅传递 location 服务类型。如果用户想要开始播放音频,请再次调用 startForeground() 并传递 location|mediaPlayback。
3. 声明新权限来使用
需要前台服务类型声明 Android 14 中引入的特定权限,需在清单文件中声明的权限中声明。所有这些权限都定义为一般权限,并默认授予。用户无法撤消这些权限。如果在调用 startForeground() 时未声明适当的前台服务类型权限,系统会抛出 SecurityException。
例如,使用前台服务类型 FOREGROUND_SERVICE_TYPE_LOCATION 的应用,需要请求 ACCESS_COARSE_LOCATION 或 ACCESS_FINE_LOCATION 权限。应用在尝试调用 startForeground() 之前,必须先请求并获得所需的权限。如果您的应用未满足启动前台服务的所有运行时要求,调用 startForeground() 后,系统会抛出 SecurityException。
举个启动相机前台服务的示例:
-
class MyCameraService: Service() {
-
-
private fun startForeground() {
-
// Before starting the service as foreground check that the app has the
-
// appropriate runtime permissions. In this case, verify that the user has
-
// granted the CAMERA permission.
-
val cameraPermission =
-
(this, )
-
if (cameraPermission != PermissionChecker.PERMISSION_GRANTED) {
-
// Without camera permissions the service cannot run in the foreground
-
// Consider informing user or updating your app UI if visible.
-
stopSelf()
-
return
-
}
-
-
try {
-
val notification = (this, "CHANNEL_ID")
-
// Create the notification to display while the service is running
-
.build()
-
(
-
/* service = */ this,
-
/* id = */ 100, // Cannot be 0
-
/* notification = */ notification,
-
/* foregroundServiceType = */
-
if (.SDK_INT >= Build.VERSION_CODES.R) {
-
ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA
-
} else {
-
0
-
},
-
)
-
} catch (e: Exception) {
-
if (.SDK_INT >= Build.VERSION_CODES.S
-
&& e is ForegroundServiceStartNotAllowedException) {
-
// App not in a valid state to start foreground service
-
// (. started from bg)
-
}
-
// ...
-
}
-
}
-
}
4. 每种前台服务类型及权限
举2个例子介绍,具体参考官网地址 /about/versions/14/changes/fgs-types-required?
为了使用给定的前台服务类型,必须在清单文件中声明特定权限,且运行时检查权限是否授权;
相机
1. 要在清单中的 android:foregroundServiceType 下声明的前台服务类型 - camera
2. 在manifest中声明的权限 - FOREGROUND_SERVICE_CAMERA
3. 传递给 startForeground() 的常量 - FOREGROUND_SERVICE_TYPE_CAMERA
4. 运行时前提条件 - 请求并获得 CAMERA 运行时权限
说明:继续在后台访问摄像头,例如支持多任务的视频聊天应用。
连接的设备
1. 清单中声明的前台服务类型android:foregroundServiceTypeconnectedDevice
2. 在manifest声明的权限FOREGROUND_SERVICE_CONNECTED_DEVICE
3. 传递给 startForeground() 的常量 FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
运行时前提条件
必须至少满足以下其中一个条件:
在清单中至少声明以下其中一项权限:
CHANGE_NETWORK_STATE
CHANGE_WIFI_STATE
CHANGE_WIFI_MULTICAST_STATE
NFC
TRANSMIT_IR
请求并获得以下至少一项运行时权限:
BLUETOOTH_CONNECT
BLUETOOTH_ADVERTISE
BLUETOOTH_SCAN
UWB_RANGING
调用 ()
使用场景:与需要蓝牙、NFC、IR、USB 或网络连接的外部设备进行互动。
注意 :如果您的应用执行投影或远程消息传递操作,请改用相应的媒体投影或远程消息传递类型。
其他类型,参考/about/versions/14/changes/fgs-types-required?
图片选择器
Android 14 引入了对所选照片的访问权限,使用户授权App访问其媒体库中的特定图片和视频,而不是授予对指定类型的所有媒体的访问权限。对以Android 14 及更高的版本的App才会启用此变更,系统提供图片选择器,App调用选择器即可,无需请求任何存储权限。
如果你的App使用存储权限以实现和维护自己的图库选择器,需要使用到READ_MEDIA_VISUAL_USER_SELECTED 权限,不使用新权限,系统会在兼容模式下运行应用。
OpenJDK 17 更新
Android 14 的 Android 的核心 与最新 OpenJDK LTS 版本中的功能保持一致,包括适合应用和平台开发者的库更新及 Java 17 语言支持。
影响应用兼容性的点:
正则表达式的更改:
严格地遵循 OpenJDK 的语义,不允许无效的组引用,否则抛出 类抛出 IllegalArgumentException。
UUID 处理:
() 方法会执行更严格的检查;
ProGuard 问题:
使用 ProGuard 缩减、混淆和优化应用时,添加 类会导致问题,因为 Kotlin 库会根据 ("") 返回类更改行为。若应用使用没有 类的旧版,新的优化可能会将 computeValue 方法从派生自 的类中移除。
其他
1. 动态代码加载
使用动态代码(DCL) 功能时,必须将所有动态加载的文件标记为只读,否则,系统会抛出异常。在动态文件(例如 DEX、JAR 或 APK 文件)打开之前将其设为只读:
-
val jar = File("DYNAMICALLY_LOADED_FILE.jar")
-
val os = FileOutputStream(jar)
-
{
-
// Set the file to read-only first to prevent race conditions
-
()
-
// Then write the actual file content
-
}
-
val cl = PathClassLoader(jar, parentClassLoader)
2. 后台activity 启动处理Intent/Service的限制
以Android 14及以上的目标平台的App,系统会进一步限制允许应用何时从后台启动 Activty:
当应用使用 PendingIntent#send() 或类似方法发送 PendingIntent 时,如果它想授予自己后台的activity 启动处理 intent 的权限,需要选择启用。选择启用,通过 setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED) 传递 ActivityOptions的方式;
当可见应用使用 bindService() 方法绑定另一个后台应用的服务时,这时想授予自己的后台 Activity 对绑定服务的启动权限,需要在调用 bindService() 方法时包含 BIND_ALLOW_ACTIVITY_STARTS 标志。
以上改动目的是防止恶意应用滥用 API 在后台做启动干扰活动。
3. 压缩路径遍历
Android 14 及更高版本的应用中,如果 zip 文件条目名称包含“..”或以“/”开头,使用 () 函数时,会抛出 ZipException。App可以通过调用 () 选择停用此验证
4. MediaProjection 拍摄会话都需要征得用户同意
Android14及以上的目标版本App,在以下任一情况下使用,MediaProjection#createVirtualDisplay 会抛出 SecurityException,
第一种情况:App缓存从 MediaProjectionManager#createScreenCaptureIntent 返回的 Intent,并将其多次传递给 MediaProjectionManager#getMediaProjection。
第二种情况:App在同一 MediaProjection 实例上多次调用MediaProjection#createVirtualDisplay。
所以,适配方式:App必须在每次捕获会话之前征求用户同意。单次拍摄会话是指对 MediaProjection#createVirtualDisplay 的单次调用,且每个 MediaProjection 实例只能使用一次。
总结:
结合前两张的描述,Android 14的适配基本写完,其实应该在去年底写完的,只是今年才想在****上发表一些文章。