马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
迩来刚刚做了Android 14的适配(即targetSdkVersion 升级到 34 ),通过此博客整理下干系注意点。
前台服务范例
当targetSdkVersion >= 34 ,应用内的前台服务(Foreground Service)必要指定至少一种前台服务范例。
在Android体系中,前台服务(Foreground Service)是一种特别的服务,它可以在背景实行操纵,同时向用户体现一个关照,这体现该服务正在实行任务。像音乐播放器、健身步伐、定位步伐,通过前台服务体近况态栏关照,让用户知道APP实行任务而且正在斲丧体系资源。。Android10 引入了前台服务范例。(android:foregroundServiceType 属性)。
Android 8 (targetSdkVersion 28) 以后要用startForegroundService来启动"前台服务"。
- private fun startLocationService() {
- val intent = Intent(this, LocationService::class.java)
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
- startForegroundService(intent)
- } else {
- startService(intent)
- }
- }
复制代码 Android 9及以后前台服务要在manifest中设置以下权限
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
复制代码 前台应用启动服务后,要在service内里通过startForeground方法向关照栏发送一个关照:
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- val notification = NotificationCompat.Builder(this, "channel_id").setContentTitle("前台服务").setContentText("前台服务正在运行中......").setSmallIcon(
- R.drawable.ic_launcher_background).build()
- startForeground(1, notification)
- return super.onStartCommand(intent, flags, startId)
- }
复制代码- <service
- android:name=".service.LocationService"
- android:foregroundServiceType="camera"
- android:enabled="true"
- android:exported="true"></service>
复制代码
常用的前台服务范例有:
- camera
- connectedDevice
- dataSync
- health
- location
- mediaPlayback
- mediaProjection
- microphone
- phoneCall
- remoteMessaging
- shortService
- specialUse
- systemExempted
复制代码 注意:如果您调用 startForeground() 但未声明适当的前台服务范例权限,体系会抛出 SecurityException。
安全干系
限定隐式Intent和PendingIntent
当targetSdk为34时,通过隐式Intent或隐式Intent创建的PendingIntent只能打开设置了android:exported="true"的组件,如果android:exported属性值为false,体系会抛出非常。(即应用必须使用明确的intent来打开(android:exported="false")的组件)通过上述限定可防止恶意应用拦截只供给用内部组件使用的隐式 intent。
如果应用创建一个PendingIntent ,但 intent 未指定组件或包,体系现在会抛出非常。
- <activity
- android:name=".NormalIntentMainActivity"
- android:exported="false">
- <intent-filter>
- <action android:name="com.transsion.targetsdk34android.APP_ACTION"/>
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
复制代码
错误写法:(注意上面Activity的exported属性是false)
- startActivity(Intent("com.transsion.targetsdk34android.APP_ACTION"))
复制代码 报错信息如下:
- E FATAL EXCEPTION: main
- Process: com.transsion.targetsdk34android, PID: 6361
- android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.transsion.targetsdk34android.APP_ACTION }
复制代码
正确写法:(注意上面Activity的exported属性是false)
- val intent = Intent(MainActivity2@ this, NormalIntentMainActivity::class.java)
- startActivity(intent)
复制代码
运行时动态注册广播吸收器必须指定导出活动
在 Android 14 上,运行时通过 Context的registerReceiver() 动态注册广播吸收器,必要设置标志 RECEIVER_EXPORTED 或 RECEIVER_NOT_EXPORTED ,标识是否导出该广播,克制应用步伐出现安全弊端,如果注册的是体系广播,则不必要指定标志。(如果应用仅通过 Context的registerReceiver 方法为体系广播注册吸收器时,那么它可以不在注册吸收器时指定标志。)
选择广播吸收器是否应导出并对装备上的其他应用可见。假云云吸收器正在监服从体系或其他应用(以致是您拥有的其他应用)发送的广播,请使用 RECEIVER_EXPORTED 标志。假云云吸收器仅监听应用发送的广播,请使用 RECEIVER_NOT_EXPORTED 标志。
一些体系广播来自具有较高特权 这类应用(比方蓝牙和电话应用), 但不在体系的唯一历程 ID (UID) 下运行。吸收者 吸收全部体系广播,包罗来自具有较高特权的广播 应用,请使用 RECEIVER_EXPORTED 标志您的吸收器。
如果您使用 RECEIVER_NOT_EXPORTED 标志吸收器, 吸收器可以吸收来自您的服务器 应用,但不从具有较高特权的应用举行广播。
如果您的应用要监听多个广播,但只应监听部门广播 已将RECEIVER_NOT_EXPORTED和部门标志为 RECEIVER_EXPORTED,将广播分别到差别的 广播吸收器。
- val listenToBroadcastsFromOtherApps = false
- val receiverFlags = if (listenToBroadcastsFromOtherApps) {
- ContextCompat.RECEIVER_EXPORTED
- } else {
- ContextCompat.RECEIVER_NOT_EXPORTED
- }
复制代码 注意 :如果导出广播吸收器,其他应用大概会向您的应用发送不受掩护的广播。
调用 registerReceiver() 注册吸收器:
- ContextCompat.registerReceiver(context, br, filter, receiverFlags)
复制代码
安全的动态代码加载
如果应用以 Android 14 为目标平台并使用动态代码加载 (DCL),则全部动态加载的文件都必须标志为只读,否则,体系会抛出非常。
我们发起应用尽大概克制动态加载代码,由于如许做会大大增长应用因代码注入或代码窜改而受到危害的风险。
如果必须动态加载代码,请使用以下方法将动态加载的文件(比方 DEX、JAR 或 APK 文件)在文件打开后和写入任何内容之前立即设置为只读:
- val jar = File("DYNAMICALLY_LOADED_FILE.jar")
- val os = FileOutputStream(jar)
- os.use {
- // Set the file to read-only first to prevent race conditions
- jar.setReadOnly()
- // Then write the actual file content
- }
- val cl = PathClassLoader(jar, parentClassLoader)
复制代码 处置处罚已存在的动态加载文件
为防止体系对现有动态加载的文件抛出非常,我们发起您先删除并重新创建文件,然后再实行在应用中重新动态加载这些文件。重新创建文件时,请按照上述指南在写入时将文件标志为只读。大概,您可以将现有文件重新标志为只读,但在这种环境下,我们猛烈发起您先验证文件的完备性(比方,对照可信值查抄文件的署名)以掩护应用免遭恶意操纵的影响。
针对从背景启动 activity 的其他限定
对于以 Android 14(API 级别 34)或更高版本为目标平台的应用,体系会进一步限定允许应用何时从背景启动 activity:
- 当应用使用 PendingIntent#send() 或雷同方法发送 PendingIntent 时,如果它想要授予本身的背景 activity 启动待处置处罚 intent 的启动特权,则必须选择启用。如需选择启用,应用应通过 setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED) 通报 ActivityOptions 软件包。(使用PendingIntent从背景打开Activity时,必须设置setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED)。)
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- private fun startPendingActivity() {
- val options = ActivityOptions.makeBasic().apply {
- pendingIntentBackgroundActivityStartMode =
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- }
- PendingIntent.getActivity(
- this,
- 0,
- Intent(this, NormalIntentMainActivity::class.java),
- PendingIntent.FLAG_IMMUTABLE,
- options.toBundle()
- )
- }
复制代码
- 当一个可见应用使用 bindService() 绑定另一个在背景运行的应用的服务时,如果该可见应用想要将其本身的背景 activity 启动权限授予绑定服务,则它现在必须选择参加 BIND_ALLOW_ACTIVITY_STARTS 标志。(即背景服务想要启动前台Activity)
- private fun bindService() {
- bindService(intent, serviceConnection, Context.BIND_ALLOW_ACTIVITY_STARTS)
- }
复制代码 这些更改扩展了现有的一组限定 ,通过防止恶意应用滥用 API 从背景启动粉碎性活动来掩护用户。
压缩路径遍历
对于以 Android 14(API 级别 34)或更高版本为目标平台的应用,Android 会通过以下方式防止 Zip 路径遍历弊端:如果 zip 文件条目名称包罗“..”或以“/”开头,则 ZipInputStream.getNextEntry() 会抛出 ZipException。ZipFile(String)
应用可以通过调用 dalvik.system.ZipPathValidator.clearCallback() 选择停用此验证。
OpenJDK 17
Android 14 将继续更新 Android 的焦点库,以与最新 OpenJDK LTS 版本中的功能保持划一,包罗适当应用宁静台开辟者的库更新和 Java 17 语言支持。
以下变动大概会影相应用兼容性:
- 对正则表达式的更改:现在,为了更严格地遵照 OpenJDK 的语义,不允许无效的组引用。您大概会看到 java.util.regex.Matcher 类抛出 IllegalArgumentException 的新环境,因此请务必测试应用中使用正则表达式的情况。如需在测试期间启用或停用此变动,请使用兼容性框架工具切换 DISALLOW_INVALID_GROUP_REFERENCE 标志。
- UUID 处置处罚:现在,验证输入参数时,java.util.UUID.fromString() 方法会实行更严格的查抄,因此您大概会在反序列化期间看到 IllegalArgumentException。如需在测试期间启用或停用此变动,请使用兼容性框架工具切换 ENABLE_STRICT_VALIDATION 标志。
- ProGuard 标题:偶然,在您实行使用 ProGuard 缩减、肴杂和优化应用时,添加 java.lang.ClassValue 类会导致标题。标题源自 Kotlin 库,该库会根据 Class.forName("java.lang.ClassValue") 是否会返回类更改运行时活动。如果您的应用是根据没有 java.lang.ClassValue 类的旧版运行时开辟的,则这些优化大概会将 computeValue 方法从派生自 java.lang.ClassValue 的类中移除。
应用只能杀死本身的背景历程
从 Anroid 14 开始,调用 ActivityManager的killBackgroundProcesses() 方法时,只能制止本身的应用步伐,如果传入其他应用步伐的包名,不会对其他应用步伐产生影响。
- public void killBackgroundProcesses (String packageName)
复制代码 在运行Android 14或更高版本的装备上,第三方应用只能使用此API来制止本身的历程。
最低可安装目标 API 级别
从 Android 14 开始,targetSdkVersion 低于 23 的应用无法安装。要求应用满足这些最低目标 API 级别要求有助于进步用户的安全性和隐私性。
恶意软件通常会以较旧的 API 级别为目标平台,以绕过在较新版本 Android 中引入的安全和隐私掩护机制。比方,有些恶意软件应用使用 targetSdkVersion 22,以克制受到 Android 6.0 Marshmallow(API 级别 23)在 2015 年引入的运行时权限模子的束缚。这项 Android 14 变动使恶意软件更难以规避安全和隐私权方面的改进限定。实行安装以较低 API 级别为目标平台的应用将导致安装失败,而且 Logcat 中会体现以下消息:
- INSTALL_FAILED_DEPRECATED_SDK_VERSION: App package must target at least SDK version 23, but found 7
复制代码
授予对照片和视频的部门访问权限 (从光彩开辟者平台搬运)
Android 14 引入了所选照片访问权限,可让用户授予应用对其库中特定图片和视频的访问权限,而不是授予对给定范例的全部媒体内容的访问权限。在 Android 13(targetSDK 33)中引入了图片或视频文件的访问权限时,在 Android 14 装备上与应用交互的用户现在可以授予对其照片或视频文件的部门访问权限,即用户可选择只授予应用访问本地媒体文件中的部门图片和视频,具体授予哪些文件由用户本身指定 ,其他文件则不允许应用访问,此变动有利于掩护用户的隐私数据。
哀求权限
起首,根据操纵体系版本在 Android 清单中哀求正确的存储权限:
- <!-- Devices running Android 12L (API level 32) or lower -->
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
- <!-- Devices running Android 13 (API level 33) or higher -->
- <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
- <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
- <!-- To handle the reselection within the app on devices running Android 14
- or higher if your app targets Android 14 (API level 34) or higher. -->
- <uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
复制代码
U 版本(Android 14)在原有图片和视频的权限弹框 允许 和 不允许 的底子上,增长 选择照片和视频 的按钮,用户点击这个按钮后可以授予 App 访问部门图片和视频的权限。
新的授权对话框会展示如下的权限选项:
- 选择照片和视频:Android 14中的新特性,用户选择他们想要提供给应用的特定的照片和视频。
- 全部允许:用户授予对装备上全部照片和视频的完备访问权限。
- 不允许:用户拒绝全部访问。
如果用户选择了 选择照片和视频 选项,而且应用步伐稍后再次哀求 READ_MEDIA_IMAGES 或 READ_MEDIA_VIDEO 权限,体系将体现一个差别的对话框,含有 选择更多 的选项,让用户有时机访问其他照片和视频。
为了资助应用步伐支持新的更改,体系引入了新的权限:READ_MEDIA_VISUAL_USER_SELECTED。根据应用步伐是否使用新权限,体系的活动会有所差别。
注意:
如果应用已使用了体系 照片选择器,则不必要再做适配修改。否则,可以思量使用 照片选择器 。
兼容性影响
该特性只会对 targetSdk>=33 ,且声明使用 READ_MEDIA_VIDEO 和 READ_MEDIA_IMAGES 读取图片和视频文件的应用产生影响。
- targetSdk>=33 而且在 Manifest 中声明使用 U 版本界说的 READ_MEDIA_VISUAL_USER_SELECTED 权限的应用体现App已自动适配该特性,此时应用应该已经按照 “适配引导” 部门的操纵完成了特性的适配。
- <uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
复制代码
如果应用声明 READ_MEDIA_VISUAL_USER_SELECTED 权限,而且用户在体系权限对话框中选择 选择照片和视频 ,则会发生以下活动:
- READ_MEDIA_IMAGES 和 READ_MEDIA_VIDEO 两个权限都会被体系拒绝。
- 应用会被授予 READ_MEDIA_VISUAL_USER_SELECTED 权限,向应用提供部门的、暂时的访问用户照片和视频的权限。
- 如果应用必要访问其他照片大概视频(用户授权外的),必须再一次手动哀求 READ_MEDIA_IMAGES 大概 READ_MEDIA_VIDEO 权限(大概两个都申请)。
注意:
READ_MEDIA_IMAGES 和 READ_MEDIA_VIDEO 是访问用户的照片和视频照片库所需的唯一其他权限。声明 READ_MEDIA_VISUAL_USER_SELECTED 是让权限控制器知道您的应用步伐支持手动重新哀求选择更多照片和视频。
- targetSdk>=33 未适配授予对照片和视频的部门访问权限的 App,U 版本会使用兼容性方案让用户也能在体系提供的 UX 中选择部门照片和视频。这时间应用的功能大概受到影响,如用户授权 App 读取部门照片和视频的权限后, 在 APP 中发送照片时大概会由于在 App 的照片选择器找不到必要的照片而产生迷惑(由于此时用户只给 App 授予了部门照片和视频的访问权限)。
如果应用不声明 READ_MEDIA_VISUAL_USER_SELECTED 权限,而且用户在体系权限对话框中选择 选择照片和视频 ,则会发生以下活动:
- 在应用步伐会话期间授予 READ_MEDIA_IMAGES 和 READ_MEDIA_VIDEO 权限,提供暂时授权,应用可以暂时访问用户选择的照片和视频。当应用移动到背景,大概用户自动关闭应用时,体系终极会拒绝这些权限,此活动与其他一次性权限一样。
- 如果应用步伐稍后必要访问其他照片和视频,则必须再次手动哀求 READ_MEDIA_IMAGES 大概 READ_MEDIA_VIDEO 权限,流程与最初的权限申请流程一样。
U 版本新增应用授予对照片和视频的部门访问权限的逻辑梳理如下:
适配引导
照片和视频的部门访问权限既可以用作读取图片权限(READ_MEDIA_IMAGES),也可以用作读取视频权限(READ_MEDIA_VIDEO)。下面以读取图片(READ_MEDIA_IMAGES)为示例举行阐明。
注意:
以下内容只针对 targetSdk>=33 而且在 Manifest 中声明使用U版本界说的 READ_MEDIA_VISUAL_USER_SELECTED 权限,自动适配该特性的操纵步调。
应用第一次读取图片
用户交互路程的关键适配代码:
- 初次读取图片前申请 READ_MEDIA_IMAGES 权限。
- final int PERMISSION_REQUEST_CODE = 100;
- ......
- context.requestPermissions(new String[]{"android.permission.READ_MEDIA_IMAGES"}, PERMISSION_REQUEST_CODE);
复制代码
2.在 App 的图片选择器中判断当前获取的是全部文件权限还是读取部门图片和视频的权限。
- if (context.checkSelfPermission("android.permission.READ_MEDIA_VISUAL_USER_SELECTED") == PackageManager.PERMISSION_GRANTED)
- {
- // 表示用户授予了App读取部分图片和视频的权限
- // App从系统MediaStore或者公共存储中读取到的图片和视频是不全的,App只能看到用户在系统UX中授予的部分图片和视频
- ......
- // App需要适配新流程
- ......
- } else if (context.checkSelfPermission("android.permission.READ_MEDIA_IMAGES") == PackageManager.PERMISSION_GRANTED) {
- // 表示用户授予了App读取所有图片和视频的权限
- // App从系统MediaStore或者公共存储中读取到的所有图片和视频文件
- // 这时候应用的处理逻辑和基础版本保持不变
- ......
- } else {
- // 表示用户拒绝了App读取所有图片和视频的权限
- // 这时候应用的处理逻辑和基础版本保持不变
- ......
- }
复制代码
3.App 在本身的图片选择器中列出应用可以或许读取到的全部图片,供用户选择。
应用第二次读取图片
用户交互路程的关键适配代码:
- 判断当前用户是否只授予了 App 读取部门图片和视频的权限。
- if (context.checkSelfPermission("android.permission.READ_MEDIA_VISUAL_USER_SELECTED") == PackageManager.PERMISSION_GRANTED
- && context.checkSelfPermission("android.permission.READ_MEDIA_IMAGES") != PackageManager.PERMISSION_GRANTED ) {
- // 表示用户授予了App读取部分图片和视频的权限
- // App从系统MediaStore或者公共存储中读取到的图片和视频是不全的,App只能看到用户在系统UX中授予的部分图片和视频
- // App需要适配新流程
- ......
- }
复制代码
2.在 App 的图片选择器中列出全部可以或许选择的图片(用户提供 App 的部门图片),并提供 添加可访问的照片 的本领,方便用户在必要的时间申请 App 读取更多图片的权限。
3.在 App 的 UX 上,用户选择 添加可访问的照片 时,哀求 READ_MEDIA_IMAGES 权限,通过体系 UX 添加更多的提供 App 访问的图片。
- final int PERMISSION_REQUEST_CODE = 100;
- ......
- context.requestPermissions(new String[]{"android.permission.READ_MEDIA_IMAGES"}, PERMISSION_REQUEST_CODE);
复制代码
4.App 在本身的图片选择器中列出 App 可以或许读取到的最新的全部图片,供用户选择。
应用第2+N次读取图片
在 U 版本上,如果用户在第一次交互和第二次交互中都只提供 App 读取部门图片权限,体系以为用户这个决定以后不再更改。此时 App 申请 READ_MEDIA_IMAGES 权限时的弹框就可以省略,以淘汰用户交互的条理。
- 判断当前用户只授予了 App 读取部门图片和视频的权限。
- if (context.checkSelfPermission("android.permission.READ_MEDIA_VISUAL_USER_SELECTED") == PackageManager.PERMISSION_GRANTED && context.checkSelfPermission("android.permission.READ_MEDIA_IMAGES") != PackageManager.PERMISSION_GRANTED ) {
- // 表示用户授予了App读取部分图片和视频的权限
- // App从系统MediaStore或者公共存储中读取到的图片和视频是不全的,App只能看到用户在系统UX中授予的部分图片和视频
- // App需要适配新流程
- ......
- }
复制代码
2.在 App 的图片选择器中列出全部可以或许选择的图片(用户提供 App 的部门图片),并提供 添加可访问的照片 的本领,方便用户在必要的时间申请 App 读取更多图片的权限。
3.在 App 的 UX 上,用户选择 添加可访问的照片 时,哀求 READ_MEDIA_IMAGES 权限,通过体系 UX 添加更多的提供 App 访问的图片。
- final int PERMISSION_REQUEST_CODE = 100;
- ......
- context.requestPermissions(new String[]{"android.permission.READ_MEDIA_IMAGES"}, PERMISSION_REQUEST_CODE);
复制代码
4.App 在本身的图片选择器中列出 App 可以或许读取到的最新的全部图片,供用户选择。
最佳实践
在官方文档中,Google 保举了使用 READ_MEDIA_VISUAL_USER_SELECTED 权限的几个最佳实践。
- 背景媒体处置处罚实质上必要得到新权限
如果应用举行媒体处置处罚(比方在背景压缩或上传媒体),请注意,READ_MEDIA_IMAGES 和 READ_MEDIA_VIDEO 权限终极会再次被拒绝。我们猛烈发起您添加对 READ_MEDIA_VISUAL_USER_SELECTED 的支持。大概您的应用应打开 InputStream 或使用 ContentResolver 举行查询,查抄本身是否有权访问特定照片或视频。
- 请勿永世存储权限状态
请勿永世存储权限状态(包罗 SharedPreferences 或 DataStore )。存储的状态大概与实际状态差别步。权限状态大概会在以下环境下发生更改:权限重新设置、应用步伐休眠、用户在应用设置中发起更改,大概应用进入背景。请应用使用 ContextCompat.checkSelfPermission() 查抄存储权限。
- 不要冒充拥有对照片和视频的完备访问权限
根据 Android 14 中引入的更改,您的应用大概只对装备的照片库举行部门访问权限。如果应用在使用 ContentResolver 举行查询时存储了 MediaStore 数据,则存储大概不是最新的。
- 应始终使用 ContentResolver 查询 MediaStore,而不是依赖于存储的缓存。
- 当应用在前台运行时,将效果生存在内存中。
- 将 URI 访问权限视为暂时访问权限
如果用户在体系权限对话框中选择 选择照片和视频 ,您的应用对所选择照片和视频的访问权限终极将逾期。无论产生何种授权,应用应始终处置处罚无法访问任何 URI 的环境。
参考:https://developer.android.com/develop/background-work/services/foreground-services?hl=zh-cn
照片和视频的部门访问权限 从光彩开辟者服务平台搬运,我感觉他们写的比力完备以及具体。
适配/操持规范-Google U版本(Android 14)应用兼容性适配引导 | 光彩开辟者服务平台 (honor.com)
官网:
授予对照片和视频的部门访问权限 | Android Developers
|