Android 14 的 8 个重要新特性深度解析
每年一次的Android升级从不缺席。今年的版本名为倒置蛋糕(Upside Down Cake),简称U,对外的版本号是Android 14。
通常来说,升级任务可以从两个角度来考虑:ROM角度和应用程序(App)角度。前者主要关注系统内部的变化,而后者则更关心新功能和行为变化。这篇文章主要关注应用程序角度,即如何处理第三方应用在Android 14升级中的问题。考虑到升级内容非常庞杂,我们首先介绍新功能部分。这样做的原因是新功能往往容易被人们忽视,但它们实际上非常重要。
之所以这样说,是因为与行为变化不同,如果在系统升级后遇到问题,只需查阅文档就可以知道Android 14进行了哪些更改以及如何进行调整。而新功能则是指添加的新功能和接口,不会影响应用程序的原有逻辑,但却能够解决问题、优化体验,并提供独特的能力。从长远来看,这些新功能更具价值。
如果开发者总是忽视了新功能部分,那么应用程序很可能会停留在旧的实现上、旧的方案上,操作系统升级的工作也就变成了被动的升级。因此,建议大家不仅关注行为变化,还要多留意新功能是否可以改善现有方案,从而优化产品体验。
Android 14引入了许多新的API,我已经试用了其中大部分,并且还开源了一些演示示例。本文将以易于理解的语言从设计原理和使用解读的角度带大家深入了解这8个重要的新特性。
- 1.截屏感知(ScreenShot Detection)
- 2.文本高亮(TextView Highlight)
- 3.全新的系统返回设计(New System Back Design)
- 4.支持自定义操作的系统分享(Custom Action on Share Sheet)
- 5.区域偏好(Locale Preferences)
- 6.语法性别(Grammar Gender)
- 7.路径迭代器(Path Iterator)
- 8.安装改善(Package Installer improvement)
1.截屏感知(ScreenShot Detection)
有些应用需要监听用户的截屏操作,以提供反馈或通知。开发者如何实现此功能?
通常,开发者会通过监听存放截屏文件的媒体目录的变化来间接实现。不过,这种方法通常需要运行时级别的读写权限,处理不慎可能会涉及隐私问题。
为解决这个问题,Android 14推出了专用API:ScreenShotCallback。使用该API,开发者无需运行时级别的读写权限,只需在应用安装时声明相应权限即可获授权。
<uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" />
Activity中代码处理如下:
class ScreenShotActivity : AppCompatActivity() {
private val screenCaptureCallback = ScreenCaptureCallback {
// 提醒用户等操作
AlertDialog.Builder(this).show()
}
override fun onStart() {
super.onStart()
registerScreenCaptureCallback(mainExecutor, screenCaptureCallback)
}
override fun onStop() {
super.onStop()
unregisterScreenCaptureCallback(screenCaptureCallback)
}
}
使用改API完成截屏监听效果如下所示:
2.文本高亮(TextView Highlight)
Mail、SMS、Note 类的 App 经常需要将文本中的某些部分设置为高亮显示,传统的方法通常是使用 Spannable。但是,这种方法的代码有点复杂,并且更新高亮不太方便。
HighLight APIs
为了解决这个问题,Android 14 提供了一种专门的 API,叫做 HighLights,它提供了更简单、更灵活的实现方式。
首先,它支持静态设置高亮:
- 使用
Highlights.Builder
构建 HighLights 对象 - 通过
addRange()
方法设置要高亮的范围和样式 - 通过
TextView
的新方法setHighLights()
应用高亮效果
其次,它还支持动态设置高亮:
- 使用
getHighLights()
方法获取已存在的 HighLights 实例 - 修改其样式和范围
- 调用 invalidate() 方法动态更新高亮效果
下面的代码演示了如何将文本中的黄色和绿色部分静态高亮显示,并在点击按钮后将绿色高亮改为深蓝色高亮:
class MainActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
...
val greenPaint = Paint().apply {
color = Color.GREEN
}
with(binding.textview1) {
text = TEXT
val builder = Highlights.Builder()
.addRange(yellowPaint, 0, 3)
.addRange(greenPaint, 14, 24)
.addRange(greenPaint, 25, 32)
highlights = builder.build()
}
binding.changeHighlights.setOnClickListener {
Log.d("HighLights", "changeHighlights tapped & change highlights")
textView1Highlights?.apply {
// Change color
getPaint(1).color = Color.BLUE
// Change ranges
getRanges(1)[0] -= 3
getRanges(1)[1] += 1
for (i in 0 until size) {
Log.d("HighLights", "textView1Highlights'" +
" paint:${getPaint(i).color.toColorString()}")
val range = getRanges(i)
for (j in range.indices) {
Log.d("HighLights", "ranges:${range[j]}")
}
}
}
binding.textview1.invalidate()
}
}
}
代码演示效果如下图所示:
HighLights API是否适应多语言?答案是NO,HighLights API与多语言无关,需自行处理不同语言下的目标高亮范围。
搜索Highlight
除了一般的高亮,Android 14 还引入了专门用于搜索的高亮 API。这些 API 主要与TextView
相关,并包括以下新方法:
searchResultHighlightColor
:设置搜索匹配到的文字高亮颜色。focusedSearchResultHighlightColor
:设置搜索焦点所在位置的高亮颜色。setSearchResultHighlights
:设置搜索到的文字范围进行高亮显示。range focusedSearchResultIndex
:用于搜索焦点的高亮和移动,有三个常量:
-1
:搜索尚未开始或未找到结果。
0
:找到了搜索结果。
1
:聚焦在某个搜索结果上。
下面的代码示例将匹配到的搜索关键字高亮显示为水蓝色,聚焦到匹配结果的高亮显示为灰色。通过模拟 search button
(搜索按钮)和 forward button
(前进按钮)来模拟匹配到的文字范围和搜索焦点的移动。
class TextViewActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
with(binding.textview2) {
searchResultHighlightColor = Color.CYAN
focusedSearchResultHighlightColor = Color.GRAY
}
// 模拟搜索按钮
binding.startSearch.setOnClickListener {
binding.textview2.run {
Log.d("HighLights", "startSearch tapped" +
" and current search Color:${searchResultHighlightColor.toColorString()}"
)
// set searching ranges
setSearchResultHighlights(4, 11, 25, 32)
}
}
binding.changeSearchIndex.setOnClickListener {
binding.textview2.run {
// Set index to first or second
val newSearchIndex = when (focusedSearchResultIndex) {
TextView.FOCUSED_SEARCH_RESULT_INDEX_NONE, 1 -> 0
0 -> 1
else -> TextView.FOCUSED_SEARCH_RESULT_INDEX_NONE
}
binding.textview2.focusedSearchResultIndex = newSearchIndex
}
}
}
}
代码演示效果如下所示:
3.全新的系统返回设计(New System Back Design)
随着屏幕越来越大,我们的手机变得更加多样和灵活,传统的返回按键和虚拟键开始显得不太必要了。那么,如何简化返回操作并统一开发方式就变得非常重要。
(全新返回箭头)New Back Arrow
从 Android 13 开始,对于返回事件的处理进行了统一。如果想要使用这个新特性,首先需要进行两个设置:
- 在清单文件(Manifest)中打开
enableOnBackInvokedCallback
。 - 在 Activity 中注册实现返回逻辑的
OnBackInvokedCallback
。
在 Android 14 中,对系统的返回效果进行了进一步升级,其中一项是对箭头进行的优化,具体包括:
- 添加了箭头的边框和背景,更加明显可见。
- 根据系统的 Material Design 主题自动适配,当主题改变时,箭头的背景颜色也会相应更新。
通过下面的对比,你可以看到 Android 14 的系统返回箭头相较于 Android 13,更加协调和清晰。
即将返回的预览功能(Predictive Back Preview)
第二个方面是添加返回预览功能,让用户能够提前查看即将返回的界面,以便决定是取消还是继续进行返回操作。这个功能目前还在不断完善中,如果你想要体验,需要在开发者选项中手动开启。
步骤如下:进入【设置】>【系统】>【开发者选项】>【预测性返回手势动画(Predictive back animation)】。
在下面的效果图中,你可以看到当触发返回手势时,整个应用界面会被缩小,并展示一个背面画面的预览效果。
4.支持自定义操作的系统分享(Custom Action on Share Sheet)
“”"
现如今,App生态日益丰富,数据分享变得尤为重要。通常情况下,分享界面的呈现取决于App的适配和系统的调度。然而,涉及更多细节和具体操作时,系统或App可能无法及时涵盖,因此自定义分享操作的支持变得十分必要。
Android 14中引入了ChooserAction
类,可在创建标准分享界面时,使用该类展示自定义操作和信息,以提供更丰富的分享菜单:
- 使用
ChooserAction.Builder
创建自定义ChooserAction
对象
a. 指定Icon
b. 指定Title
c. 指定分享菜单点击后目标的PendingIntent
类型的操作 - 使用
Intent#createChooser()
创建标准的Chooser Intent - 将
ChooserAction
实例放入键名为EXTRA_CHOOSER_CUSTOM_ACTIONS
的Bundle中
值得注意的是,传入的参数是ChooserAction
数组,因为可以同时支持多个自定义操作
以下代码展示了设置自定义分享的标准写法:
class ShareSheetActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
val pendingIntent: PendingIntent =PendingIntent.getActivity(
this@ShareSheetActivity,
0,
Intent(Intent.ACTION_WEB_SEARCH).apply {
putExtra(SearchManager.QUERY, "Search on web 🌐.")
},
PendingIntent.FLAG_IMMUTABLE
)
val chooserAction = ChooserAction.Builder(
Icon.createWithResource(this@ShareSheetActivity, R.drawable.ic_launcher_foreground),
"Send to Ellison",
pendingIntent
).build()
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, "This is my text to send.")
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
shareIntent.putExtra(Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS, arrayOf(chooserAction))
startActivity(shareIntent)
}
}
代码演示效果如下:
5.区域偏好(Locale Preferences)
很多人在使用设备时,系统语言与他们日常习惯的语言可能不同。例如,想学习英语的人可能将系统语言设置为英语,但这会带来一些麻烦。比如,日历应用会变成公历,而没有了农历和节假日调休的标记,一周的第一天也变成了周日,这确实不太方便。
幸运的是,在 Android 14 中推出了区域偏好设置,可以改善这种用户体验。
Android 14 允许用户在系统语言之外单独设置地区偏好。用户可以设置温度单位
、一周的第一天
、日历类型
、小时制
等偏好。下图显示了区域偏好设置的入口。对于应用程序来说,只需要使用新的 LocalePreferences API 中提供的方法,获取系统的区域偏好并进行适配即可:
- 使用
getTemperatureUnit()
获取温度单位偏好,是摄氏还是华氏单位; - 使用
getFirstDayOfWeek()
获取一周的第一天偏好,是从周一还是周日开始; - 使用
getHourCycle()
获取小时制偏好,是12小时制还是24小时制,对于12小时制,还可以选择0~11或1~12的表示方式,对于24小时制,可以选择0~23或1~24的表示方式; - 使用
getCalendarType()
获取日历类型偏好,是公历还是农历等。
需要注意的是,目前的 LocalePreferences API 尚未集成到 AOSP SDK 中,因此需要导入 AndroidX SDK。
dependencies {
implementation "androidx.core:core:1.12.0-alpha04"
}
6.语法性别(Grammar Gender)
就像汉语中的“他”、“她”、“它”,英语中的"He"、“She”、"it"一样,很多语言在语法上会根据性别和对象的不同而产生差异,不仅仅限于名词,还包括形容词和动词等,更加复杂。而这些语言的使用人数高达30亿人,如果应用程序中的文本只使用通用的、中性的表述,就会显得不够准确。如果不进行区分,甚至对女性使用男性化的表述方式,用户体验将更加糟糕。
因此,如果应用程序的界面语言能正确反映用户的语法性别,就可以提高用户的好感度和互动性,实现更加个性化和自然的用户体验。
这就引入了Android 14推出的重要新功能:语法性别(Grammar Gender)。具体而言,Android 14新增了专用的语法性别API:GrammaticalInflectionManager,用于获取和设置针对单个应用程序的性别偏好。
getApplicationGrammaticalGender()
:用于获取语法性别偏好,返回的是Configuration
类中的整型常量。有以下几种类型:GRAMMATICAL_GENDER_NOT_SPECIFIED, 0
:尚未指定性别偏好,将使用默认的values
资源。GRAMMATICAL_GENDER_NEUTRAL, 1
:指定中性、客观的资源文本,例如values-fr
资源。GRAMMATICAL_GENDER_FEMININE, 2
:指定针对女性的资源文本,例如values-fr-feminine
资源。GRAMMATICAL_GENDER_MASCULINE, 3
:指定针对男性的资源文本,例如values-fr-masculine
资源。
setRequestedApplicationGrammaticalGender()
:将上述常量类型动态设置为性别偏好。
动态设置性别偏好后,活动(Activity)会发生重绘,因为它本质上也属于Configuration
的范畴。为了避免这种重绘,Android 14还提供了针对该特性的专用配置变更属性:grammaticalGender
,可以在清单(Manifest)文件中进行配置。
<manifest ...>
<application...>
<activity
...
android:configChanges="grammaticalGender">
</activity>
</application>
</manifest>
class GenderActivity : AppCompatActivity() {
override fun onConfigurationChanged(newConfig: Configuration) {
// Resources will be updated with new configuration
val newText = resources.getString(R.string.example_string)
Log.d("Gender", "onConfigurationChanged()" +
" new text:${resources.getString(R.string.example_string)}"
)
binding.textview.text = newText
}
}
然后,在onConfigurationChanged()
方法中可以获取新的语法性别文本,并手动刷新目标TextView的显示。下面的演示效果图在GrammarGender
发生变化后,性别相关的表述也会自动刷新,而且没有出现闪烁的情况。
7.路径迭代器(Path Iterator)
有些开发者在绘制曲线和动画时使用了Path API来执行移动、连线等操作。但是,目前无法追溯一个Path实例进行了多少操作,因为缺乏相应的API。如果需要这样的功能,你必须自行缓存和管理这些操作数据。
为了解决这个问题,Android 14引入了PathIterator
类。
使用方法非常简单,只需通过Path的新方法getPathIterator()
获取PathIterator
实例,然后逐个遍历Path的变化片段(Segment),以查询Path的操作历史和各个点(Point)的数据。
getVerb()
方法返回操作的类型,如PathIterator.VERB_MOVE
表示移动操作,PathIterator.VERB_LINE
表示连线操作,以此类推。
getPoints()
方法返回当前操作的相关点的坐标。
getVerb()
PathIterator.VERB_MOVE,0
PathIterator.VERB_LINE,1
PathIterator.VERB_QUAD,2
PathIterator.VERB_CONIC,3
PathIterator.VERB_CUBIC,4
PathIterator.VERB_CLOSE,5
PathIterator.VERB_DONE,6
getPoints()
下面的代码示例展示了如何获取路径迭代器并打印其信息。
class PathActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
val path = Path().apply {
moveTo(1.0f, 1.0f)
lineTo(2.0f, 2.0f)
close()
}
val pathIterator = path.pathIterator
for (segment in pathIterator) {
Log.i("Path", "segment action: ${segment.verb.toPathAction()}")
for (f in segment.points) {
Log.i("Path", "point: $f")
}
}
}
...
}
打印信息如下:
2023-06-24 Path I segment action: VERB_MOVE
2023-06-24 Path I point: 1.0
2023-06-24 Path I point: 1.0
...
2023-06-24 Path I segment action: VERB_LINE
2023-06-24 Path I point: 1.0
2023-06-24 Path I point: 1.0
2023-06-24 Path I point: 2.0
2023-06-24 Path I point: 2.0
...
2023-06-24 Path I segment action: VERB_CLOSE
2023-06-24 Path I point: 1.0
...
8.安装改善(Package Installer improvement)
Android 14带来了三个与应用安装相关的重要新特性,旨在提升用户的安装体验。
首先是“下载前请求安装批准”。在之前的Android版本中,当我们需要安装一个应用时,会要求用户授予REQUEST_INSTALL_PACKAGES
权限。然而,这种方式存在一个问题:安装批准对话框会在下载完成并启动安装会话后才弹出。这意味着,如果用户最终选择不安装应用,之前的下载流量和等待时间就被白白浪费了。为了解决这个问题,Android 14引入了一种新的方法——requestUserPreapproval()
。我们可以在应用提交下载/安装请求之前,提前请求用户的安装批准。一旦得到批准,我们就可以在后台继续下载并安装应用,而不会再打扰用户。
第二个新特性是“对未来更新负责”。在很多用户的设备上,可能安装了多个应用市场。当我们安装了一个应用后,其他市场可能会提醒我们有可用的更新。然而,如果更新来自于与我们最初安装应用不同的市场,可能会存在不兼容或不可靠的风险。为了解决这个问题,Android 14推出了setRequestUpdateOwnership()
方法。通过使用这个方法,我们可以向系统表明我们打算负责将来针对我们安装的应用的更新。系统将只允许我们所属的市场自动进行应用的更新。为了使用这个特性,我们需要声明一个特定的权限android.permission.ENFORCE_UPDATE_OWNERSHIP
,这个权限会在应用安装时被授予。之后,如果其他市场想要负责我们的应用更新,必须得到用户的明确批准。
第三个新特性是“在较少干扰的时机更新应用”。在自动更新或手动更新多个应用的过程中,如果我们正在使用其中的一个应用,可能会被更新中断,操作也可能被终止,严重的情况下甚至会导致正在编辑的数据丢失。为了改善这种体验,Android 14引入了安装约束InstallConstraints
API。通过使用InstallConstraints.Builder
,我们可以设置一些约束条件,比如是否要求应用不在前台运行、设备是否处于空闲状态、设备是否正在通话中等等。然后,我们可以调用PackageInstaller
的新方法commitSessionAfterInstallConstraintsAreMet()
,以确保只有在用户不再与应用进行受限交互时才进行应用的更新。这样,我们就可以避免更新干扰我们正在使用的应用,提供更好的用户体验。
结论
在Android 14中,我们介绍了8个重要的新特性,这显示出Android在发展到成熟的第14个版本时,仍然关注用户体验和开发过程中的痛点。
首先,我们添加了一个专用的ScreenShotCallback
,用于规范监听截屏的开发方式。
其次,我们引入了全新的API,简化了Highlight
的实现方式。
我们重新设计了返回箭头,并引入了支持目标界面的预览功能(Predictive Back Preview
),以统一和加强Android平台上的返回导航体验。
还有支持自定义分享操作的标准分享功能,以满足丰富和灵活的分享需求。
我们引入了全新的区域设置(Locale Preferences
)来改善用户习惯的体验。
另外,我们引入了全新的、独立的语法性别(Grammar Gender
)来提高文本表述的准确度。
为方便开发者对Path
历史进行回溯,我们引入了特定的API(Path Iterator
)。
通过改进Package Installer
,我们全面提升了应用安装和更新方面的细节体验。
总体而言,这些新特性相对简单易懂,但建议大家尝试一下,以便在开发相应需求时能够充分利用Android 14提供的官方新特性。