深度解读 Android 14 的 8 个重要新特性~
一年一度的 Android 升级永不缺席,今年的代号叫
Upside Down Cake
,倒置蛋糕,简称U
,对外版本为 Android 14。
一般来说,升级任务分为 ROM 角度和 App 角度,前者比较关心系统内部实现的变化,后者则更加关心对外的 New Features 和 Behavior Changes。本文主要聚焦在 App 角度,即作为 3rd Party App 如何去看待 14 系统升级。由于升级内容过于庞杂,本次先介绍 New Features 部分,原因是作为 New Features、很容易被大家忽略,实则更重要。
理由是 New Features 不像 Behavior Changes:OS 升级之后如果运行上出了问题,调查下文档就知道 14 变更了什么、如何改。而 New Features 作为新的功能、API,并不会影响 App 原来的逻辑,但实实在在地解决了痛点、优化了体验、提供了一个又一个特色能力,从长远来讲是更有价值的。
如果开发者总是忽略 New Features 部分,那么 App 难免停留在旧的实现上、旧的方案上,OSV 工作也变成了改一改、能跑就行的被动升级。建议大家在关注 Behavior Changes 以外多留意下 New Features 是否可以改善现有的方案,优化产品体验。
14 推出的新 API,大部分我都试过,并开源了 DEMO。本文将从设计的理由、使用解读等角度,带大家切实感受这重要的 8 个新特性:
ScreenShot Detection,截屏感知 TextView Highlight,文本高亮 New System Back Design,全新的系统返回设计 Custom Action on Share Sheet,支持自定义操作的系统分享 Locale Preferences,区域偏好 Grammar Gender,语法性别 Path Iterator,路径迭代器 Package Installer improvement,安装改善
1. ScreenShot Detection
部分 App 常常需要监听用户的截屏操作,进行发送反馈的提醒等,往常是使用哪些手段来实现呢?
一般来说,开发者会通过监听存放截屏文件的媒体目录的变化来迂回实现,这往往需要 Runtime 级别的读写权限,而且稍有不慎还可能牵扯到隐私问题。
那么 Android 14 为了规范这种开发需求,推出了专用 API,即 ScreenShotCallback
。它无需无需 Runtime 级别的读写权限,申明专用权限即可,在 App 安装的时候即被授予:
<uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" />
使用的方式来说,以 Activity
为单位进行注册和注销截屏 ScreenCaptureCallback。并且建议在 onStart() 里注册、onStop() 里注销。
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 成功地监听到了截屏操作,并执行了预期的 Dialog 提醒。
需要注意的是,该 API 只能监听电源键方式发起的截屏时机,无法监听到 adb、代码等方式发起的截屏,毕竟它不算是用户的操作。而且不会将截图带过来,App 如果需要图片数据的话,仍需要去读取。
相关文章推荐:Android 14 新 API:直接监听截屏操作,不用再观察媒体文件了~
2. TextView Highlight
HighLights API
对于做 Mail、SMS、Note 类的 App 难免遇到设置文本 Highlight 的需求,而传统的实现办法无非是 Spannable
。但这种方式的代码稍显复杂、而且无法方便地更新高亮。
那么 14 里针对这个痛点提供了专门的 API 即 HighLights
,提供了更加简单、灵活的实现。
首先,支持静态设置高亮:
1. 通过 Highlights.Builder 构建 HighLights 对象
2. 通过 addRange() 设置 Paint 和对应 Range 数组即可
3. 通过 TextView 新方法 setHighLights() 反映高亮
其次,支持动态设置高亮:
调用新方法 getHighLights() 获取已有 HighLights 实例 更改其 Paint 和 Range 属性 调用 invalidate 动态更新高亮
通过如下的代码进行黄色和绿色的静态高亮设置,以及点击 Button 之后动态更新绿色高亮为深蓝色高亮:
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 成功地设置了黄色和绿色的高亮。并且在点击 CHANGE button 之后,动态更改了上面 TextView HighLights 的颜色为蓝色。
可能有人会问这个 HighLights API 能自适应多语言吗?
答案是 NO,事实上 HighLights API 和多语言无关,不同语言下要自行处理目标高亮的 range。
相关文章推荐:Android 14 新功能之 HighLights:快速实现文本高亮~
Search Highlight
除了一般的高亮以外,Android 14 还推出了针对搜索的高亮 API,主要是使用如下 TextView 的相关新方法:
searchResultHighlightColor
设置搜索匹配到的高亮focusedSearchResultHighlightColor
设置聚焦到的高亮setSearchResultHighlights
设置搜索到的文字 rangefocusedSearchResultIndex
针对搜索焦点高亮和移动,index 常量:
-1:没有开始搜索/搜索不到结果 0:匹配到搜索结果 1:聚焦到某个搜索结果
如下代码设置匹配到搜索关键字的高亮为水蓝色,聚焦到该匹配的高亮是灰色,并用 search button 模拟匹配到的文字 range、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
}
}
}
}
如下的 GIF 可以看到,匹配到搜索的时候,文本颜色变成了水蓝色,当聚焦到该匹配之后变成灰色,默认情况下这是无高亮。
相关文章推荐:Android 14 新功能之 TextView 搜索结果高亮和焦点移动~
3. New System Back Design
随着屏幕越来越大,交互方式的愈加多元、灵活,传统的 Back 按键、虚拟键显得越来越冗余。那么如何简化返回操作、统一返回的开发就显得尤为重要。
New Back Arrow
事实上,自 Android 13 开始即针对 Back 事件的处理进行了统一,想要使用该新特性的好,首先需要做两个设置:
Manifest 中开启 enableOnBackInvokedCallback
注册实现 Back 逻辑的 OnBackInvokedCallback
到 Activity 中
Android 14 针对系统的 Back 效果进行了进一步的升级,第一块则是优化 arrow,包括:
增加了 Arrow 边框、背景,更加明显 自适应系统 Material Design
主题,theme 改变的同时 Arrow 的背景色跟着刷新:
如下的对比,可以看到 14 的系统返回 Arrow 相对 13 更加协调、清晰。
Predictive Back Preview
第二块就是添加返回预览,让用户可以提前查看目标界面,决定取消或继续返回操作。而这块功能尚在完善当中,需要体验首先得在开发者选项中手动开启。
设置 > 系统 > 开发者选项 > 预测性返回手势动画(Predictive back animation)
如下 GIF 即可以看到 Back 手势触发后,App 整体缩小、背面画面展示的预览效果。
相关文章推荐:Android 14 之返回界面升级:预览目标界面 + 全新返回箭头
4. Custom Action on Share Sheet
如今 App 生态越来越丰富,数据分享也变得极为需要。一般来说,分享界面的呈现由 App 的适配以及系统的调度有关。可是很多更加细节、具体的操作,系统或 App 可能无法及时 cover,这时候支持自定义的分享操作就显得十分必要。
Android 14 里新增了 ChooserAction
类,当用 Android ShareSheet
创建标准的分享界面时,可以用该 Class展示的自定义操作和信息,来提供更丰富的分享菜单:
使用 ChooserAction.Builder
创建自定义 ChooserAction
指定 Icon 指定 title 指定分享菜单点击后目标的 PendingIntent
类型的 Action
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)
}
}
下图可以看到在系统默认的 Copy 旁边多出了一个 Send to Ellison 的自定义分享项,点击之后将执行预设的 PendingIntent 的 action。
相关 API 文档:
https://developer.android.com/reference/android/service/chooser/ChooserAction.Builder https://developer.android.com/reference/android/service/chooser/ChooserAction https://www.droidcon.com/2023/04/18/sharesheet-custom-actions-android14/
5. Locale Preferences
有相当部分的一群人使用的设备语言和他的日常习惯是不同的。譬如想要学习英语的人可能将系统语言设置成英语,但会造成不少麻烦,比如日历 App 会变成公历类型,没了农历、也没了节假日调休标记、一周首日还变成了周日,着实不太方便。
那么 Android 14 推出的区域偏好设置可以帮 App 改善用户的这个体验。
Android 14 可以供用户在系统语言以外独立地设置地区偏好:设置诸如温度单位、一周首日、日历类型、小时周期等类型。下图可以看到区域偏好的设置入口。
对 App 来说,只需要使用新 API LocalePreferences
中提供的方法,获取系统的区域偏好做适配即可:
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"
}
相关文章推荐:Android 14 新功能:区域偏好 Regional Preferences
6. Grammar Gender
如同汉语里的他、她、它,英语里的 He、She、it 一样,很多语言都存在依据性别不同、对象不同而造成的语法差异,甚至不仅限于名词,还涉及到形容词、动词等,复杂得多。而这部分语言的使用人群多达 30 亿,如果 App 文本只使用通用的、中性的表述,则显得不够准确。假使不加区分,甚至对女性使用男性化的表述方式,则体验更为糟糕。
所以如果 App 的界面语言能正确反映用户的语法性别,则就可以提高用户好感度、互动度,达到更个性化、更自然的用户体验。
这便要提到 Android 14 推出的重要新特性:语法性别 Grammar Gender。具体的,Android 14 新增了专用的语法性别 API:GrammaticalInflectionManager
,用其可以获取和设置针对单个 App 的性别偏:
getApplicationGrammaticalGender()
:获取语法性别偏好,返回的是Configuration
类中的 int 型常量,有这么几种类型: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()
:将上述常量类型动态设置到性别偏好
动态设置 Gender 偏好之后,Activity 会发生重绘,因为它本质上也属于 Configuration 的范畴。为了避免这种重绘,14 也提供了针对该特性的专用配置变更 attr:grammaticalGender,可以在 Manifest 中配置。
<manifest ...>
<application...>
<activity
...
android:configChanges="grammaticalGender">
</activity>
</application>
</manifest>
接着,可以在 onConfigurationChanged()
里获取新的语法性别文本,手动刷新目标 TextView 的展示。
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
}
}
如下的 GIF 显示了 GrammarGender 变化之后,针对性别的表述也在自动刷新,且没有发生闪烁。
相关文章推荐:Android 14 新特性:语法性别 Grammatical Gender
7. Path Iterator
部分开发者使用过 Path API 执行移动、连线等操作去绘制曲线、动画,但你无法去回溯某个 Path 实例到底进行了多少操作,没有这样的 API,实在需要的话,你得自行缓存和管理这些操作数据。
Android 14 针对此痛点新增了 PathIterator
类。
使用办法很简单,通过 Path 的新方法 getPathIterator()
获得 PathIterator 实例,接着逐个遍历 Path 变化片段 Segment
,可以查询 Path 的操作历史以及各 Point 数据:
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
...
相关 API 文档:
https://developer.android.com/reference/android/graphics/PathIterator
8. Package Installer improvement
Request install approval before downloading
早期的 Android 版本里针对需要安装 APK 的 App 推出了 REQUEST_INSTALL_PACKAGES
权限,但要等到下载完成、安装 Session
发起之后,该安装权限的批准 Dialog 才会被 PackageInstaller
弹出。这显得稍稍滞后,因为如果用户最终拒绝安装,前面的下载流量和等待时间可以说是浪费了。
那么 PackageInstaller 则推出新方法来规避这个漏洞,即 requestUserPreapproval()
:
可让 App 在提交下载/安装之前,请求用户批准、获得授权 用户批准后,App 可在后台下载并安装应用,不会再次干扰用户。
requestUserPreapproval(
PackageInstaller.PreapprovalDetails details,
IntentSender statusReceiver
)
requestUserPreapproval 函数接收如下两个参数,需要留意一下:
PreapprovalDetails
,新增的预授权的信息 Class,由 PreapprovalDetails.Builder 构建,需要指定预安装的 App 的 Icon、名称、包名等细节IntentSender
,当授权通过过用来发起安装 Session 的 Sender
相关 API 文档:
https://developer.android.com/reference/android/content/pm/PackageInstaller.PreapprovalDetails
Claim responsibility for future updates
安装相关的第 2 个新特性叫做 Claim responsibility for future updates,什么意思呢?
很多用户的 Android 设备中很可能不止一个应用市场,当安装了一个 App 之后,其他的市场可能提醒 App 有可用更新,但是跟安装源头不一样的话,可能存在不兼容、不正规等风险。
那么借助 Android 14 新的 setRequestUpdateOwnership()
方法,安装程序可以向系统表明它打算负责将被安装的应用未来的更新,那么系统将仅允许该市场为应用自动安装更新。
此特权需要申明专用的 permission:
android.permission.ENFORCE_UPDATE_OWNERSHIP,也是 normal 级别,安装即被授予
此后,如果其他安装程序想要负责该 App 的更新都必须获得用户的明确批准,才能进行。一旦用户同意了从其他来源安装更新,那么元市场将失去自动更新的所有权。
相关 API 文档:
https://developer.android.google.cn/reference/android/content/pm/PackageInstaller.SessionParams#setRequestUpdateOwnership(boolean)
https://developer.android.google.cn/reference/android/Manifest.permission#ENFORCE_UPDATE_OWNERSHIP
Update apps at less-disruptive times
安装相关的第 3 个新特性叫做:Update apps at less-disruptive times。
假使用户开启了自动更新或者手动更新一堆 App 的途中,碰巧在使用其中的某个 App,那么该 App 的进程很有可能会被更新打断、操作终止,严重的还会导致编辑中的数据发生丢失。
为了避免因为更新导致正在运行的应用进程被终止这种差的体验,Android 14 引入了安装约束 InstallConstraints
API 让安装程序可以确保其应用更新在适当的时机进行。
使用 InstallConstraints.Builder 构建,设置诸如是否要求不在前台 setAppNotForegroundRequired()
、是否要求设备在 IDLE 状态setDeviceIdleRequired()
、是否要求设备不在通话中setNotInCallRequired()
等等接着调用 PackageInstaller 的新方法 commitSessionAfterInstallConstraintsAreMet()
传入上述 InstallConstraints 配置实例,来确保仅在用户不再与相应应用进行约束条件的互动时才进行更新
相关 API 文档:
https://developer.android.com/reference/android/content/pm/PackageInstaller.InstallConstraints
结语
至此,已经介绍完了这 8 个重要的 Android 14 新特性,可以看到 Android 发展到 14 这么成熟的版本,还是不忘关注用户体验上的细节、开发方式的痛点:
添加专用 ScreenShotCallback
来规范监听截屏的开发方式添加全新 API 来简化 Hightlight
的实现方式重新设计 Back Arrow
和支持目标界面的预览Predictive Back Preview
来统一、加强 Android 平台上 Back 导航的体验支持 Custom Share Action
的标准分享,来满足丰富、灵活的分享需求引入全新的区域设置 Locale Preferences
来改善用户习惯的体验引入全新的、独立的语法性别 Grammar Gender
来提高文本表述的准确度引入特定 API Path Interator
来方便开发者对 Path 历史进行回溯通过改善 Package Installer
来全方位提升 App 安装、更新方面的细节体验
整体上来说这几个新特性比较简单、易懂,但还是建议大家去尝试,便于在开发相应需求的时候,记得 Android 14 官方有个新特性可以适用。
Android 14 Sample
https://github.com/ellisonchan/AndroidUDemo
相关技术文章
Android 14 之返回界面升级:预览目标界面 + 全新返回箭头 Android 14 新功能之 TextView 搜索结果高亮和焦点移动~ Android 14 新功能之 HighLights:快速实现文本高亮 Android 14 新 API:直接监听截屏操作,不用再观察媒体文件了 Android 14 新特性:语法性别 Grammatical Gender Android 14 新功能:区域偏好 Regional Preferences
相关技术文档
https://developer.android.com/about/versions/14 https://developer.android.google.cn/about/versions/14/features