准备工作
首先将我们项目中的 targetSdkVersion
和compileSdkVersion
升至 33。
影响Android 13上所有应用
1.通知受限
对新安装的应用的影响:
如果用户在搭载 Android 13 或更高版本的设备上安装您的应用,应用的通知默认处于关闭状态。在您请求新的权限且用户向您的应用授予该权限之前,您的应用都将无法发送通知。
- 如果您的应用以 Android 13 或更高版本为目标平台,应用将可以完全自行控制权限对话框的显示时间。您可以借此机会向用户说明应用需要此权限的原因,进而鼓励他们授予该权限。
- 如果您的应用以 12L(API 级别 32)或更低版本为目标平台,在您创建通知渠道后您的应用首次启动 activity 时,或在您的应用启动一个 activity,然后创建它的第一个通知渠道时,系统会显示该权限对话框。这通常是在应用启动时。
对现有应用更新的影响:
为了最大限度地减少与通知权限相关的中断,当用户将其设备升级到Android 13或更高版本后,系统会自动向所有符合条件的应用预先授予相应权限。换言之,这些应用可以继续向用户发送通知,而用户不会看到运行时权限提示。
适配方法:
应用以Android 13或更高版本为目标平台,需要在应用程序manifest
文件中声明POST_NOTIFICATIONS
权限,并完成与请求其他运行时权限类似的流程。
<manifest ...>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
</manifest>
请求权限前,可以使用areNotificationsEnabled()
方法检查用户是否已启用通知。Android 13上做权限申请,Android 13一下可以引导设置页开启。
官方文档建议等到用户熟悉您的应用之后,再请求授予权限。下面列举了几个适合显示通知权限提示的时机:
- 用户点按“提醒铃铛”按钮时。
- 用户选择关注他人的社交媒体帐号时。
- 用户提交外卖订单时。
下图是关于请求通知权限的建议用户驱动工作流程。仅当 shouldShowRequestPermissionRationale()
返回true时,才有必要显示中间屏幕。
2.intent过滤器会屏蔽不匹配的intent
当您的应用向以Android 13或更高版本为目标平台的其他应用的导出组件发送 intent
时,仅当该intent
与接收应用中的<intent-filter>
元素匹配时,系统才会传送该 intent
。换言之,系统会屏蔽所有不匹配的intent
,但以下情况除外:
- 目标组件未声明任何
<intent-filter>
。 - 同一应用内发送的
intent
。 - 由系统发送的
intent
。 - 具有ROOT权限的进程发送的
intent
。
如果接收方应用升级到Android 13或更高版本,仅当intent
与其声明的 元素匹配时,源自外部应用的所有intent
才会传送到导出组件,而不考虑发送应用的目标SDK版本。
所以我们需要检查应用内是否有通过Intent方式启动其他App或发送广播,同时检查action
、data
等信息是否准确。
影响以Android 13或更高版本为目标平台的应用
1.GestureDetector
升到33后,发现编译失败。一看是kotlin实现的GestureDetector.OnGestureListener
接口报错,提示onScroll
方法的MotionEvent
参数不会为空。原因是Android 13上这里的代码多了一个@NonNull
的注解。
本以为删除可空的“?”就正常了,但发现在低版本手机上会报错:
Fatal Exception: java.lang.NullPointerException:
Parameter specified as non-null is null: method android.view.GestureDetector.onTouchEvent, parameter e1
因为低版本手机上,这里拿到的MotionEvent
为空。但是在kotlin中,变量不可空时,它会使用checkNotNullParameter
方法校验,如果为null就会发生上面的异常。
所以如果你是用java实现的,不会存在这个问题。那么解决方法之一就是改用java实现。或者可以加上@Suppress("NOTHING_TO_OVERRIDE", "ACCIDENTAL_OVERRIDE")
这样就不会生成checkNotNullParameter
校验代码。
如果适配过程中有类似问题,不要盲目删除“?”。需要注意一下低版本的运行情况。
2.读取媒体文件权限
对于目标版本为Android 13的应用,细化了READ_EXTERNAL_STORAGE
权限,使用READ_MEDIA_IMAGE
、READ_MEDIA_VIDEO
、READ_MEDIA_AUDIO
替代READ_EXTERNAL_STORAGE
。
如果用户之前向您的应用授予了 READ_EXTERNAL_STORAGE
权限,系统会自动向您的应用授予细化的这三个媒体权限。
适配说明:
<manifest ...>
<!-- 按需添加 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<application ...>
...
</application>
</manifest>
代码部分可以做个判断,例如:
String requestPermission;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestPermission = Manifest.permission.READ_MEDIA_IMAGES;
} else {
requestPermission = Manifest.permission.READ_EXTERNAL_STORAGE;
}
另外文档建议,如果你的应用只需要访问图片、照片和视频,可以考虑使用照片选择器,而不是声明 READ_MEDIA_IMAGES
和 READ_MEDIA_VIDEO
权限。
3.访问附近Wi-Fi设备受限
面向Android 13 (API级别33)或更高版本并管理Wi-Fi连接的应用程序必须请求
NEARBY_WIFI_DEVICES
运行时权限。这种权限使应用程序访问附近的Wi-Fi设备变得更加容易;在以前版本的Android上,这些应用程序需要声明ACCESS_FINE_LOCATION
权限。
需要NEARBY_WIFI_DEVICES
权限的API方法:
- WifiManager:
startLocalOnlyHotspot()
- WifiAwareManager:
attach()
- WifiAwareSession:
publish()
、subscribe()
- WifiP2pManager:
addLocalService()
、connect()
、createGroup()
、discoverPeers()
、discoverServices()
、requestDeviceInfo()
、requestGroupInfo()
、requestPeers()
- WifiRttManager:
startRanging()
适配说明:
<manifest ...>
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="32" />
</manifest>
- 如果你的应用不会通过Wi-Fi API推导物理位置,请将
usesPermissionFlags
属性设为neverForLocation
。同时将ACCESS_FINE_LOCATION
权限的最高SDK版本设置为32 - 如果你使用了
WifiManager
的getScanResults()
或startScan()
,在Android 13还是需要ACCESS_FINE_LOCATION
权限,所以去除android:maxSdkVersion="32"
。
4.广告 ID
使用 Google Play 服务广告 ID 且以 Android 13(API 级别 33)及更高版本为目标平台的应用必须在其清单文件中声明常规AD_ID权限,如下所示:
<manifest ...>
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
<application ...>
...
</application>
</manifest>
如果您的应用以 Android 13 或更高版本为目标平台且未声明此权限,系统会自动移除广告 ID 并将其替换为一串零。
5.废弃方法
PackageManager
中的getPackageInfo
、getApplicationInfo
、resolveActivity
等方法。
以常用的getPackageInfo
方法举例:
public static PackageInfo getPackageInfoCompat(PackageManager packageManager, String packageName, int flag)
throws PackageManager.NameNotFoundException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(flag));
} else {
return packageManager.getPackageInfo(packageName, flag);
}
}
Intent
中的getSerializableExtra
、getParcelableExtra
和Bundle
中的getSerializable
、getParcelable
等方法。
以getParcelableExtra
举例说明:
public static <T extends Parcelable> T getParcelableExtraCompat(Intent intent, String name, Class<T> clazz) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return intent.getParcelableExtra(name, clazz);
} else {
return intent.getParcelableExtra(name);
}
}
如果你的代码是Kotlin写的,当然也可以将此方法用Kotlin的拓展方法实现,便于替换。
-
WebSettings
中的setAppCacheEnabled
、setAppCacheMaxSize
、setAppCachePath
方法被移除。setAppCacheEnabled(false)
可以用setCacheMode(WebSettings.LOAD_NO_CACHE)
替代。setAppCacheEnabled(true)
可以用setCacheMode(WebSettings.LOAD_DEFAULT)
替代。 -
对于以 Android 13(API 级别 33)或更高版本为目标平台的应用,
WebSettings
中的setForceDark
方法已废弃,调用该方法无效。WebView 现在始终会根据应用的主题属性
isLightTheme
来设置媒体查询prefers-color-scheme
。换句话说,如果isLightTheme
为 true 或未指定,则prefers-color-scheme
为 light;否则为 dark。此行为意味着,系统会自动应用 Web 内容的浅色或深色样式(如果相应内容支持应用主题)。对于大多数应用,新行为应自动应用适当的应用样式,不过,您应测试应用以检查是否存在可能已手动控制深色模式设置的情况。
如果您仍需要自定义应用的颜色主题行为,请改用
setAlgorithmicDarkeningAllowed()
方法。为了向后兼容以前的 Android 版本,我们建议使用 AndroidX 中的等效setAlgorithmicDarkeningAllowed()
方法。
其他新功能及API
1.更安全地导出上下文注册的接收器
Android 13 允许您指定您应用中的特定广播接收器是否应被导出以及是否对设备上的其他应用可见。如果导出广播接收器,其他应用将可以向您的应用发送不受保护的广播。此导出配置在以 Android 13 或更高版本为目标平台的应用中可用,有助于防止一个主要的应用漏洞来源。在以前的 Android 版本中,设备上的任何应用都可以向动态注册的接收器发送不受保护的广播,除非该接收器受签名权限的保护。
以Android 13或更高版本为目标平台的应用,必须为每个广播接收器指定 RECEIVER_EXPORTED
或 RECEIVER_NOT_EXPORTED
。否则,当您尝试注册广播接收器时,系统会抛出SecurityException
。
Caused by: java.lang.SecurityException: com.xxx.xxx: One of RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED should be specified when a receiver isn't being registered exclusively for system broadcasts
当然这个目前不是强制适配的,需要在开发者选项 -> 应用兼容变更中开启DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED
。这个安全增强措施默认是关闭的,所以暂时无影响。
适配方法:
// 这个广播接收器能够接收来自其他应用程序的广播。
context.registerReceiver(sharedBroadcastReceiver, intentFilter,
RECEIVER_EXPORTED);
// 这个私有广播接收器不能够接收来自其他应用的广播。
context.registerReceiver(privateBroadcastReceiver, intentFilter,
RECEIVER_NOT_EXPORTED);
因为我们项目之前统一将普通广播替换为了本地广播(LocalBroadcastManager
),所以不受影响。
2.预测性返回手势
Android 13(API 级别 33)针对手机、大屏设备和可折叠设备等 Android 设备引入了预测性返回手势。该功能的发布历程跨度将达多年;完全实现后,该功能可让用户在完全完成某个返回手势之前就能预览此手势完成后的目的地或其他结果,以便用户能够决定是继续完成手势还是留在当前视图中。
参照文档添加对预测性返回手势的支持以及运行官方Codelab发现在Vivo、OPPO、小米上都不起作用,可能被阉割了。。。使用模拟器正常。目前此功能在开发者选项中供测试使用。官方计划在未来的Android版本中向用户提供此界面。
Android 12L开始,发现官方不断地在大屏设备和可折叠设备上提供优化支持。可以看到未来大屏的适配也是一个趋势。
3.各应用语言偏好设定
在许多情况下,多语言用户会将其系统语言设置为某一种语言(例如英语),但又想为特定应用选择其他语言(例如荷兰语、中文或印地语)。为了帮助应用为这些用户提供更好的体验,Android 13 针对支持多种语言的应用引入了以下功能:
-
系统设置:用户可以在这个集中位置为每个应用选择首选语言。
您的应用必须在应用的清单中声明
android:localeConfig
属性,以告知系统它支持多种语言。 -
其他 API:借助这些公共 API(例如
LocaleManager
中的setApplicationLocales()
和getApplicationLocales()
方法),应用可以在运行时设置不同于系统语言的其他语言。这些 API 会自动与系统设置同步;因此,使用这些 API 创建自定义应用内语言选择器的应用将确保用户获得一致的用户体验,无论他们在何处选择语言偏好设置。公共 API 还有助于减少样板代码量、支持拆分 APK,并且支持应用自动备份,以存储应用级的用户语言设置。
同样,试了几部国产手机此功能都被阉割,所以没有过多的尝试。详情参见文档:各应用语言偏好设定
4.更快断字
断字让分行的文本更易于阅读,并且有助于使界面更具自适应性。从Android 13开始,断字性能提升了高达200%,因此您可以在 TextView 中启用更快断字功能,而几乎不会影响渲染性能。如需启用更快断字功能,请在
setHyphenationFrequency()
中使用fullFast
或normalFast
频率。
使用方法:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// 适用于聊天消息
textView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL_FAST);
// 标准连词符
textView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL_FAST)
}
下图是使用fullFast
属性前后对比:
5.带主题的应用图标
从 Android 13 开始,您可以选择启用带主题的应用图标。借助此功能,用户可以调节受支持的Android应用图标色调,以继承所选壁纸和其他主题的配色。
如需支持此功能,您的应用必须提供自适应图标和单色应用图标,并通过清单中的 <adaptive-icon>
元素指向该单色应用图标。如果用户启用了带主题的应用图标(换句话说,在系统设置中开启了带主题的图标切换开关),而启动器支持此功能,则系统将使用用户选择的壁纸和主题来确定色调颜色,然后该颜色将应用于单色应用图标。
适配方法很简单,只需要额外添加<monochrome/>
单色应用图标配置就可以支持这个功能。
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
测试了几个部手机都不支持此功能,所以只能使用模拟器测试效果。模拟器效果图:
有关Android 13的适配内容主要就这么多,更多的变更还是需要我们去阅读官方文档。链接我也贴到了文末。
最后,如果本文对你有所帮助,欢迎收藏点赞。感谢!
参考
-
官方Android13文档
-
GestureDetector.OnGestureListener overridden methods are not working in Android API 33
-
OPPO - Android 13 应用兼容性适配指导