Android Q 平台包含一些行为变更,这些变更可能会影响您的应用。以下行为变更将影响在 Android Q 上运行的所有应用,无论其采用哪种 targetSdkVersion
都不例外。您应该测试您的应用,然后根据需要进行更改以适当地支持这些变更(如果适用)。
此外,请务必查看仅影响以 Android Q 为目标平台的应用的行为变更列表。
注意:除了此页面所列的变更以外,Android Q 还引入了大量变更和限制以增强用户隐私保护。有关详情,请参阅隐私权页面。
为了帮助确保应用稳定性和兼容性,Android 平台开始限制您的应用可在 Android 9(API 级别 28)中使用哪些非 SDK 接口。Android Q 包含更新后的受限非 SDK 接口列表(基于与 Android 开发者之间的协作以及最新的内部测试)。我们的目标是在限制使用非 SDK 接口之前确保有可用的公开替代方案。
如果您不打算以 Android Q 为目标平台,那么其中一些变更可能不会立即对您产生影响。虽然您目前可以使用灰名单中的一些非 SDK 接口(取决于您应用的目标 API 级别),但如果您使用任何非 SDK 方法或字段,则应用无法运行的风险终归较高。
如果您不确定自己的应用是否使用了非 SDK 接口,则可以测试该应用进行确认。如果您的应用依赖于非 SDK 接口,则应该开始计划迁移到 SDK 替代方案。不过,我们知道某些应用具有使用非 SDK 接口的有效用例。如果您无法为应用中的某项功能找到使用非 SDK 接口的替代方案,则应该请求新的公共 API。
要了解详情,请参阅非 SDK 接口在 Android Q 中的受限情况出现变化以及针对非 SDK 接口的限制。
下表说明了当您的应用尝试访问黑名单中的非 SDK 接口时可能会出现的预期行为。
访问方式 | 结果 |
---|---|
Dalvik 指令引用某个字段 | 抛出 NoSuchFieldError |
Dalvik 指令引用某个方法 | 抛出 NoSuchMethodError |
通过 Class.getDeclaredField() 或 Class.getField() 进行反射 |
抛出 NoSuchFieldException |
通过 Class.getDeclaredMethod() 、Class.getMethod() 进行反射 |
抛出 NoSuchMethodException |
通过 Class.getDeclaredFields() 、Class.getFields() 进行反射 |
结果中未获取到非 SDK 成员 |
通过 Class.getDeclaredMethods() 、Class.getMethods() 进行反射 |
结果中未获取到非 SDK 成员 |
通过 env->GetFieldID() 进行 JNI 调用 |
返回 NULL ,抛出 NoSuchFieldError |
通过 env->GetMethodID() 进行 JNI 调用 |
返回 NULL ,抛出 NoSuchMethodError |
从 Android Q 开始,用户可以在设备中启用手势导航。如果用户启用手势导航,则会影响设备上的所有应用,无论应用是否以 Android Q 为目标平台,都是如此。例如,如果用户从屏幕边缘向内滑动,系统会将该手势解读为“返回”导航,除非应用针对屏幕的相应部分明确替换该手势。
为了确保您的应用与手势导航兼容,您需要将应用内容扩展到屏幕边缘,并适当地处理存在冲突的手势。有关信息,请参阅手势导航文档。
Android Q 包含以下 NDK 方面的变更。
Android 6.0(API 级别 23)已禁止在共享对象中使用文本重定位。代码必须按原样加载,且不得修改。此变更可以缩短应用的加载时间并提高安全性。
在 Android Q 测试版 1 和 2 中,SELinux 对以 Android 8.0(API 级别 26)及更高版本为目标平台的应用强制执行此限制。从 Android Q 测试版 3 开始,将对以 Android Q(API 级别 29)及更高版本为目标平台的应用强制执行此限制。如果这些应用继续使用包含文本重定位的共享对象,则出现故障的风险较高。
从 Android Q 开始,多个路径不再采用常规文件形式,而是采用符号链接形式。如果应用一直以来依赖的都是采用常规文件形式的路径,则可能会出现故障:
/system/lib/libc.so
-> /apex/com.android.runtime/lib/bionic/libc.so
/system/lib/libm.so
-> /apex/com.android.runtime/lib/bionic/libm.so
/system/lib/libdl.so
-> /apex/com.android.runtime/lib/bionic/libdl.so
/system/bin/linker
-> /apex/com.android.runtime/bin/linker
这些变更也会影响文件的 64 位版本,对于这些版本,会将 lib/
替换为 lib64/
。
为了确保兼容性,新符号链接会基于旧路径提供,例如 /system/lib/libc.so
现在是指向 /apex/com.android.runtime/lib/bionic/libc.so
的符号链接,等等。因此,dlopen(“/system/lib/libc.so”)
会继续工作,但当应用尝试通过读取 /proc/self/maps
或类似项来检测已加载的库时,将会发现不同之处。这并不常见,但我们发现一些应用会将这种做法作为对抗黑客攻击的一项举措。如果是这样,则应该将新的 /apex/…
路径添加为 Bionic 文件的有效路径。
从 Android Q 开始,系统二进制文件和库会映射到只执行(不可读取)内存,作为应对代码重用攻击的安全强化技术。有意或意外读入已标记为只执行的内存段会抛出 SIGSEGV,无论此读入行为是来自错误、漏洞还是有意的内存自省都不例外。
您可以通过检查 /data/tombstones/
中的相关 tombstone 文件来确定崩溃是否由变更改所导致。与只执行相关的崩溃包含以下中止消息:
Cause: execute-only (no-read) memory access error; likely due to data in .text.
要解决此问题,开发者可以通过调用 mprotect()
将只执行内存段标记为“读取+执行”,例如用于执行内存检查。不过,我们强烈建议您事后将其重新设为只执行,因为这样可以更好地保护您的应用和用户。
对 ptrace
的调用不会受到影响,因此 ptrace
调试也不会受到影响。
Android Q 包含以下安全性方面的变更。
以 Android Q 为目标平台的不受信任的应用无法再针对应用主目录中的文件调用 exec()
。这种从可写应用的主目录执行文件的行为违反了 W^X。应用应该仅加载嵌入到应用的 APK 文件中的二进制代码。
此外,以 Android Q 为目标平台的应用无法针对已执行 dlopen()
的文件中的可执行代码进行内存中修改。这包括含有文本重定位的所有共享对象 (.so
) 文件。
在 Android Q 中,以下与 WLAN 直连相关的广播不再具有粘性。
WIFI_P2P_CONNECTION_CHANGED_ACTION
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
如果您的应用依赖于在注册时接收这些广播(因为其之前一直具有粘性),请在初始化时使用适当的 get()
方法获取信息。
Android Q 扩大了支持范围,现在可以使用 WLAN 感知数据路径轻松创建 TCP/UDP 套接字。要创建连接到 ServerSocket
的 TCP/UDP 套接字,客户端设备需要知道服务器的 IPv6 地址和端口。这在之前需要通过频外方式进行通信(例如使用 BT 或 WLAN 感知第 2 层消息传递),或者使用其他协议(例如 mDNS)通过频内方式发现。而借助 Android Q,可以将此类消息作为网络设置的一部分进行传递。
服务器可以执行以下任一操作:
ServerSocket
并设置或获取要使用的端口。以下代码示例显示了如何将端口信息指定为网络请求的一部分:
KotlinJava更多
val ss = ServerSocket()
val ns = WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle)
.setPskPassphrase("some-password")
.setPort(ss.localPort)
.build()
val myNetworkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
.setNetworkSpecifier(ns)
.build()
ServerSocket ss = new ServerSocket();
WifiAwareNetworkSpecifier ns = new WifiAwareNetworkSpecifier
.Builder(discoverySession, peerHandle)
.setPskPassphrase(“some-password”)
.setPort(ss.getLocalPort())
.build();
NetworkRequest myNetworkRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
.setNetworkSpecifier(ns)
.build();
然后,客户端会执行 WLAN 感知网络请求来获取服务器提供的 IPv6 和端口:
KotlinJava更多
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
...
}
override fun onLinkPropertiesChanged(network: Network,
linkProperties: LinkProperties) {
...
}
override fun onCapabilitiesChanged(network: Network,
networkCapabilities: NetworkCapabilities) {
...
val ti = networkCapabilities.transportInfo
if (ti is WifiAwareNetworkInfo) {
val peerAddress = ti.peerIpv6Addr
val peerPort = ti.port
}
}
override fun onLost(network: Network) {
...
}
};
connMgr.requestNetwork(networkRequest, callback)
callback = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
...
}
@Override
public void onLinkPropertiesChanged(Network network,
LinkProperties linkProperties) {
...
}
@Override
Public void onCapabilitiesChanged(Network network,
NetworkCapabilities networkCapabilities) {
...
TransportInfo ti = networkCapabilities.getTransportInfo();
if (ti instanceof WifiAwareNetworkInfo) {
WifiAwareNetworkInfo info = (WifiAwareNetworkInfo) ti;
Inet6Address peerAddress = info.getPeerIpv6Addr();
int peerPort = info.getPort();
}
}
@Override
public void onLost(Network network) {
...
}
};
connMgr.requestNetwork(networkRequest, callback);
在 Android Q(Go 版本)设备上运行的应用无法获得 SYSTEM_ALERT_WINDOW
权限。这是因为绘制叠加层窗口会使用过多的内存,这对低内存 Android 设备的性能十分有害。
如果在搭载 Android 9 或更低版本的 Go 版设备上运行的应用获得了 SYSTEM_ALERT_WINDOW
权限,则即使设备升级到 Android Q 也会保留此权限。不过,尚不具有此权限的应用在设备升级后便无法获得此权限了。
如果 Go 设备上的应用发送具有 ACTION_MANAGE_OVERLAY_PERMISSION
操作的 intent,则系统会自动拒绝此请求,并将用户转到设置屏幕,上面会显示不允许授予此权限,原因是它会减慢设备的运行速度。如果 Go 设备上的应用调用 Settings.canDrawOverlays()
,则此方法始终返回 false。同样,这些限制不适用于在设备升级到 Android Q 之前便已收到 SYSTEM_ALERT_WINDOW
权限的应用。
在 Android Q 中,当用户首次运行以 Android 6.0(API 级别 23)以下的版本为目标平台的任何应用时,Android 平台会向用户发出警告。如果此应用要求用户授予权限,则系统会先向用户提供调整应用权限的机会,然后才会允许此应用首次运行。
由于 Google Play 的目标 API 方面的要求,用户只有在运行最近未更新的应用时才会看到这些警告。对于通过其他商店分发的应用,我们也将于 2019 年引入类似的目标 API 方面的要求。要详细了解这些要求,请参阅在 2019 年扩展目标 API 级别方面的要求。
以下 SHA-2 CBC 加密套件已从平台中移除:
TLS_RSA_WITH_AES_128_CBC_SHA256
TLS_RSA_WITH_AES_256_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
这些加密套件不如使用 GCM 的类似加密套件安全,并且大多数服务器要么同时支持这些加密套件的 GCM 变体和 CBC 变体,要么二者均不支持。
注意:应用和库应该让其所需的加密套件集与 getSupportedCipherSuites()
中返回的值相交,以便提前防范加密套件日后遭到移除。
Android Q 引入了与应用使用情况相关的以下行为变更:
UsageStats 应用使用情况的改进 -- 当在分屏或画中画模式下使用应用时,Android Q 现在能够使用 UsageStats 准确地跟踪应用使用情况。此外,Android Q 现在可以跟踪免安装应用的使用情况。
按应用开启灰度模式 -- Android Q 现在可以将应用设为灰度显示模式。
按应用开启干扰模式 -- Android Q 现在可以选择性地将应用设为“干扰模式”,此时系统会禁止显示其通知,并且不会将其显示为推荐的应用。
暂停和播放 -- 在 Android Q 中,暂停的应用无法再播放音频。
如果运行 Android Q 的应用将 null
传递给 setSSLSocketFactory()
,现在会出现 IllegalArgumentException
。在以前的版本中,将 null
传递给 setSSLSocketFactory()
与传入当前的默认 SSL 套接字工厂效果相同。
android.preference
库现已弃用。开发者应该改为使用 AndroidX preference 库,这是Android Jetpack 的一部分。如需获取其他有助于迁移和开发的资源,请查看经过更新的设置指南以及我们的公开示例应用和参考文档。
Android Q 对 java.util.zip
软件包(用于处理 ZIP 文件)中的类做出了以下变更。这些变更会让库的行为在 Android 和使用 java.util.zip
的其他平台之间更加一致。
在以前的版本中,如果在调用 end()
之后调用 Inflater
类中的某些方法,这些方法会抛出 IllegalStateException
。在 Android Q 中,这些方法会抛出 NullPointerException
。
如果所提供的 ZIP 文件不包含任何文件,则 ZipFile
的构造函数(采用的参数类型为 File
、int
和 Charset
)不再抛出 ZipException
。
如果 ZipOutputStream
中的 finish()
方法尝试为不包含任何文件的 ZIP 文件写入输出流,此方法不再抛出 ZipException
。
很多使用摄像头的应用都会假定如果设备采用纵向配置,则物理设备也会处于纵向,正如摄像头方向中所述。在过去可以做出这样的假定,但在推出新型设备(例如可折叠设备)后,这发生了变化。针对这些设备做出这样的假定可能导致相机取景器的显示产生错误的旋转和/或缩放。
以 API 级别 24 或更高级别为目标平台的应用应该明确设置 android:resizeableActivity
,并提供必要的功能来处理多窗口操作。
从 Android Q 开始,只有在发生重大充电事件之后拔下设备电源插头,SystemHealthManager
才会重置其电池用量统计信息。一般来说,重大充电事件指的是设备电池已充满,或者设备电量从几乎耗尽变为即将充满。
在 Android Q 之前,无论何时拔下设备电源插头,无论电池电量有多微小的变化,电池用量统计信息都会重置。
与旧版 Android 相比,从 Android Q 测试版 5 开始,以 Android 9(API 级别 28)或更低版本为目标平台的应用在存储工作方式方面默认没有任何变化。当您更新现有应用以使用分区存储时,即使您的应用以 Android 9 或更低版本为目标平台,您也可以使用新的 requestLegacyExternalStorage
清单属性为 Android Q 设备上的应用启用这种新行为。
为了让用户更好地控制自己的文件,减少文件混乱情况,Android Q 更改了应用对设备外部存储设备中的文件(例如存储在路径 /sdcard
下的文件)的访问方式。Android Q 会继续使用 READ_EXTERNAL_STORAGE
和 WRITE_EXTERNAL_STORAGE
权限,这些权限与面向用户的存储运行时权限相对应。不过,默认情况下,以 Android Q 为目标平台的应用(以及选择接受这些变更的应用)在访问外部存储设备中的文件时会进入过滤视图。此类应用只能查看特定于应用的目录和特定类型的媒体,因此应用无需请求任何其他用户权限。
注意:在早期测试版(READ_MEDIA_IMAGES
、READ_MEDIA_AUDIO
和 READ_MEDIA_VIDEO
)中引入的特定于媒体集合的权限现已过时。
本指南介绍了过滤视图中包含的文件,以及如何更新应用,使其可以继续共享、访问和修改保存在外部存储设备中的文件。本指南还介绍了与照片中的位置信息、从原生代码访问媒体文件以及在内容查询中使用列名称相关的几个注意事项。
要详细了解 Android Q 中针对外部存储的变更,请参阅关于在外部存储中创建文件的相关改进的部分。
注意:如果您在使用此功能时遇到问题,请使用此常用表向我们发送错误报告。
注意:访问外部存储设备中的文件时进入的这一视图在早期测试版本中称为“沙盒视图”。
默认情况下,如果应用以 Android Q 为目标平台,则在访问外部存储设备中的文件时会进入过滤视图。应用可以使用 Context.getExternalFilesDir()
将专用于自己的文件存储在特定于自己的目录中。
具有过滤视图的应用对其创建的文件始终拥有读/写权限,无论文件位于特定于此应用的目录以内还是以外。您的应用无需声明任何存储权限即可访问这些文件。
只有在满足以下两个条件时,您的应用才能访问其他应用创建的文件:
READ_EXTERNAL_STORAGE
权限。这些文件位于以下其中一个明确定义的媒体集合中:
MediaStore.Images
中。MediaStore.Video
中。MediaStore.Audio
中。为了访问另一应用创建的任何其他文件(包括“downloads”目录下的文件),您的应用必须使用存储访问框架,用户可以通过该框架选择特定文件。
注意:访问外部存储设备中的文件时会进入过滤视图的应用不具有对 /sdcard/DCIM/IMG1024.JPG
等路径的直接内核访问权限。要访问此类文件,应用必须使用 MediaStore
,并调用 openFile()
等方法。
过滤视图还施加了以下媒体相关数据限制:
ACCESS_MEDIA_LOCATION
权限,否则图片文件中的 Exif 元数据会被遮盖。如需了解详情,请参阅关于如何访问照片中的位置信息的部分。DATA
列都会被遮盖。MediaStore.Files
表格会自行过滤,仅显示照片、视频和音频文件。例如,该表格不会再显示 PDF 文件。要在原生代码中访问媒体文件,请在基于 Java 或基于 Kotlin 的代码中使用 MediaStore
检索相应文件,然后将对应的文件描述符传递到原生代码。如需了解详情,请参阅关于如何从原生代码访问媒体文件的部分。
如果应用在访问外部存储设备中的文件时会进入过滤视图,那么在卸载该应用后,系统会清除特定于该应用的目录中的所有文件。要在卸载后保留这些文件,请将其保存到 MediaStore
中的某个目录下。
警告:明年,所有应用的主要平台版本都需要分区存储,无论其采用哪种目标 SDK 级别。因此,您应该提前确保您的应用支持分区存储。为此,请确保在运行您应用的 Android Q 设备上启用该行为。
大多数已遵循存储最佳做法的应用只需做出很小的改动即可支持分区存储。在您的应用完全兼容或经过测试之前,您可以根据应用的目标 SDK 级别或名为 requestLegacyExternalStorage
的新清单属性,临时选择停用分区存储行为:
以 Android 9(API 级别 28)或更低版本为目标平台。
如果您以 Android Q 为目标平台,请在应用的清单文件中将 requestLegacyExternalStorage
的值设为 true
:
...
如果某个应用在安装时启用了旧版外部存储,则该应用仍会保持此模式,直到将其卸载为止。无论设备之后是否升级为运行 Android Q,或者应用之后更新为以 Android Q 为目标平台,此兼容性行为均仍适用。
注意:要测试以 Android 9 或更低版本为目标平台的应用是否支持分区存储,您可以通过将 requestLegacyExternalStorage
的值设为 false
,选择启用相应行为。
在没有可移动外部存储设备的设备上,请使用以下命令启用虚拟磁盘以供测试:
adb shell sm set-virtual-disk true
下表总结了在访问外部存储设备中的文件时会进入过滤视图的应用访问文件的方式:
文件位置 | 所需权限 | 访问方法 (*) | 卸载应用时是否移除文件? |
---|---|---|---|
特定于应用的目录 | 无 | getExternalFilesDir() |
是 |
媒体集合 (照片、视频、音频) |
READ_EXTERNAL_STORAGE (仅当 访问其他应用的文件时) |
MediaStore |
否 |
下载内容 (文档和 电子书籍) |
无 | 存储访问框架 (加载系统的文件选择器) |
否 |
*您可以使用存储访问框架访问上表中显示的每个位置,而无需请求任何权限。
本部分针对几种特定类型的基于媒体的应用提供了建议,帮助这些应用适应以 Android Q 为目标平台的应用中发生的存储行为变更。
除非您的应用需要访问不在特定于该应用的目录或 MediaStore
中的文件,否则最好使用过滤视图。
某些应用允许用户彼此分享媒体文件。例如,用户可以通过社交媒体应用与朋友分享照片和视频。
要访问用户希望共享的媒体文件,请使用 MediaStore
API。利用 Android Q 中引入的改进,您可以使用此 API 存储用户通过应用收到的任何文件。
如果您提供一组配套应用(例如短信应用和个人资料应用),请使用 content://
URI 设置文件共享。我们已经建议将此工作流作为一项安全最佳做法。
某些应用将文档用作存储单元,用户可以在其中输入可能要与同伴分享或要导入其他文档的数据。例如,用户打开企业办公文档或打开另存为 EPUB 文件的图书。
在这些情况下,通过调用 ACTION_OPEN_DOCUMENT
intent 使用户能选择要打开的文件,此 intent 会打开系统的文件选择器应用。要仅显示您的应用所支持类型的文件,请在您的 intent 中包含 Intent.EXTRA_MIME_TYPES
extra。
GitHub 上的 ActionOpenDocument 示例说明了在征得用户同意后如何使用 ACTION_OPEN_DOCUMENT
打开文件。
文件管理和媒体创建应用通常会管理目录层次结构中的文件组。这些应用可以调用 ACTION_OPEN_DOCUMENT_TREE
intent,以允许用户授予对整个目录树的访问权限。此类应用可以修改所选目录及其任何子目录中的任何文件。
使用此界面,用户可以从任何已安装的 DocumentsProvider
实例访问文件,而任何受本地支持或基于云的解决方案都支持这些实例。
GitHub 上的 ActionOpenDocumentTree 示例说明了在征得用户同意后如何使用 ACTION_OPEN_DOCUMENT_TREE
打开目录树。
注意:在使用 ACTION_OPEN_DOCUMENT_TREE
时,您的应用只能访问用户选择的目录中的文件。您无权访问用户选择的目录之外的其他应用的文件。借助这种由用户控制的访问权限,用户可以准确地选择自己愿意与您的应用共享哪些内容。
本部分提供了有关在外部存储设备中加载和存储媒体文件的最佳做法,以便让您的应用能够在 Android Q 中继续提供良好的用户体验。
注意:如果应用在访问外部存储设备中的文件时需要进入过滤视图,那么在请求存储运行时权限时,仅当给定文件位于特定于此应用的目录中或以下其中一个媒体集合中时,此应用才能访问相应文件:
MediaStore.Audio
MediaStore.Images
MediaStore.Video
即使具有存储权限,此类访问外部存储设备的原始文件系统视图的应用也只能访问此应用的特定于软件包的原始路径。如果应用试图使用原始文件系统视图打开其特定于软件包的路径之外的文件,则会发生错误:
FileNotFoundException
。EPERM
内核错误。不要使用已弃用的 DATA
列加载媒体文件。请改为从 ContentResolver
调用以下其中一种方法:
loadThumbnail()
,并传递要加载的缩略图的大小。openFileDescriptor()
。query()
。注意:您可以通过调用 MediaStore.setIncludePending()
查看待处理媒体文件集。
以下代码段展示了如何访问媒体文件:
// Load thumbnail of a specific media item.
val mediaThumbnail = resolver.loadThumbnail(item, Size(640, 480), null)
// Open a specific media item.
resolver.openFileDescriptor(item, mode).use { pfd ->
// ...
}
// Find all videos on a given storage device, including pending files.
val collection = MediaStore.Video.Media.getContentUri(volumeName)
val collectionWithPending = MediaStore.setIncludePending(collection)
resolver.query(collectionWithPending, null, null, null).use { c ->
// ...
}
从原生代码访问
您可能会遇到您的应用需要在原生代码中使用特定媒体文件的情况,例如其他应用与您的应用共享的文件,或用户的媒体合集中的媒体文件。在这些情况下,请在基于 Java 或基于 Kotlin 的代码中找到相应媒体文件,然后将与此文件相关的文件描述符传递到原生代码。
以下代码段演示了如何将媒体对象的文件描述符传递到应用的原生代码:
KotlinJava更多
val contentUri: Uri =
ContentUris.withAppendedId(
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
cursor.getLong(BaseColumns._ID))
val fileOpenMode = "r"
val parcelFd = resolver.openFileDescriptor(uri, fileOpenMode)
val fd = parcelFd?.detachFd()
// Pass the integer value "fd" into your native code. Remember to call
// close(2) on the file descriptor when you're done using it.
Uri contentUri = ContentUris.withAppendedId(
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
cursor.getLong(Integer.parseInt(BaseColumns._ID)));
String fileOpenMode = "r";
ParcelFileDescriptor parcelFd = resolver.openFileDescriptor(uri, fileOpenMode);
if (parcelFd != null) {
int fd = parcelFd.detachFd();
// Pass the integer value "fd" into your native code. Remember to call
// close(2) on the file descriptor when you're done using it.
}
要详细了解如何在原生代码中访问文件,请观看 2018 年 Android 开发者峰会的 Files for Miles 演讲(从15:20 处开始观看)。
注意:预计以下行为将在未来的 Android Q 测试版中生效。
要修改另一个应用最初保存到外部存储设备的给定媒体文件,请捕获平台抛出的 RecoverableSecurityException
。然后,您可以请求用户授予您的应用对此特定内容的写入权限,如以下代码段所示:
KotlinJava更多
try {
// ...
} catch (rse: RecoverableSecurityException) {
val requestAccessIntentSender = rse.userAction.actionIntent.intentSender
// In your code, handle IntentSender.SendIntentException.
startIntentSenderForResult(requestAccessIntentSender, your-request-code,
null, 0, 0, 0, null)
}
try {
// ...
} catch (RecoverableSecurityException rse) {
IntentSender requestAccessIntentSender = rse.getUserAction()
.getActionIntent().getIntentSender();
// In your code, handle IntentSender.SendIntentException.
startIntentSenderForResult(requestAccessIntentSender, your-request-code,
null, 0, 0, 0, null);
}
一些照片在其 Exif 元数据中包含位置信息,以便用户查看照片的拍摄地点。由于此类位置信息很敏感,因此如果您的应用在访问外部存储设备中的文件时会进入过滤视图,Android Q 会默认对您的应用隐藏此类信息。这种位置信息限制与适用于相机功能的限制不同。
如果您的应用需要访问照片的位置信息,请完成以下步骤:
ACCESS_MEDIA_LOCATION
权限添加到应用清单中。MediaStore
对象中调用 setRequireOriginal()
,在调用时传入照片的 URI。以下代码段是此流程的一个示例:
KotlinJava更多
// Get location data from the ExifInterface class.
val photoUri = MediaStore.setRequireOriginal(photoUri)
contentResolver.openInputStream(photoUri).use { stream ->
ExifInterface(stream).run {
// If lat/long is null, fall back to the coordinates (0, 0).
val latLong = ?: doubleArrayOf(0.0, 0.0)
}
}
Uri photoUri = Uri.withAppendedPath(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
cursor.getString(idColumnIndex));
final double[] latLong;
// Get location data from the ExifInterface class.
photoUri = MediaStore.setRequireOriginal(photoUri);
InputStream stream = getContentResolver().openInputStream(photoUri);
if (stream != null) {
ExifInterface exifInterface = new ExifInterface(stream);
double[] returnedLatLong = exifInterface.getLatLong();
// If lat/long is null, fall back to the coordinates (0, 0).
latLong = returnedLatLong != null ? returnedLatLong : new double[2];
// Don't reuse the stream associated with the instance of "ExifInterface".
stream.close();
} else {
// Failed to load the stream, so return the coordinates (0, 0).
latLong = new double[2];
}
如果您应用的代码使用列名称投影(例如 mime_type AS MimeType
),请注意,Android Q 需要使用 MediaStore
API 中定义的列名称。
如果您的代码依赖于需要 Android API 中未定义的列名称的库(例如 MimeType
),请使用 CursorWrapper
在应用的进程中动态转换列名称。
从 Android Q 测试版 5 开始,此项变更具有以下特性:
欢迎您提供相关反馈! 在 Android Q 测试版计划期间,欢迎报告您在使用此功能时发现的问题。
图 1. 用户尝试在设备未连接到网络时打开网页。Chrome 弹出互联网连接设置面板…
图 2. 用户可以开启 WLAN 并选择网络,而无需离开 Chrome 应用。
例如,假设用户打开了网络浏览器,而其设备已开启飞行模式。在 Android Q 之前的版本中,此应用只能显示一条通用消息,要求用户打开设置以恢复连接。而借助 Android Q,浏览器应用便可以显示一个内嵌面板,其中会显示各种主要连接设置,例如飞行模式、WLAN(包括附近的网络)和移动数据。借助此面板,用户无需离开应用即可恢复连接。
要显示设置面板,请发出具有某个新 Settings.Panel
操作的 intent:
KotlinJava更多
val panelIntent = Intent(Settings.Panel.settings_panel_type)
startActivityForResult(panelIntent)
Intent panelIntent = new Intent(Settings.Panel.settings_panel_type);
startActivityForResult(panelIntent);
settings_panel_type
可以是下列项之一:
ACTION_INTERNET_CONNECTIVITY
显示与互联网连接相关的设置,例如飞行模式、WLAN 和移动数据。
ACTION_WIFI
:
显示 WLAN 设置,但不显示其他连接设置。这对于需要 WLAN 连接以执行大容量上传或下载的应用非常有用。
ACTION_NFC
显示与近距离无线通信 (NFC) 相关的所有设置。
ACTION_VOLUME
显示所有音频流的音量设置。
我们计划针对此功能引入一个 AndroidX 封装容器。在搭载 Android 9(API 级别 28)或更低级别的设备上调用时,此封装容器会在设置应用中打开最合适的页面。
Android Q 为共享功能提供了多项改进。要了解所有详情,请参阅 Android Q 中共享功能方面的改进。
Android Q 提供全新的深色主题背景,既会应用于 Android 系统界面,也会应用于设备上运行的应用。要了解所有详情,请参阅深色主题背景。
Android Q 引入了一个新的 XML 清单属性 foregroundServiceType
,您可以将其包含在多项特定服务的定义中。虽然很少适用,但您可以为一项特定服务分配多个前台服务类型。
下表显示了不同的前台服务类型,以及适合在其中声明特定类型的服务:
前台服务类型 | 应声明相应类型的服务的示例使用情形 |
---|---|
connectedDevice |
监控穿戴式设备健身跟踪器 |
dataSync |
从网络下载文件 |
location |
延续用户发起的操作 |
mediaPlayback |
播放有声读物、播客或音乐 |
mediaProjection |
简短地录屏 |
phoneCall |
处理正在进行的通话 |
Android Q 对 Kotlin 开发进行了以下更新。
Android Q 改进了 SDK 中针对 libcore API 的可空性注释的覆盖范围。借助这些注释,在 Android Studio 中使用 Kotlin 或 Java 可空性分析的应用开发者可以在与这些 API 互动时获取非 Null 信息。
通常,Kotlin 中的为空性合同违规行为会导致编译错误。为确保与现有代码兼容,所有新注释都仅限于 @RecentlyNullable
和 @RecentlyNonNull
。这意味着为空性违规行为会引发警告,而不是错误。
此外,Android 9 中添加的所有 @RecentlyNullable
或 @RecentlyNonNull
注释都会分别更改为 @Nullable
和 @NonNull
。这意味着为空性违规行为现在会引发错误,而不是警告。
要详细了解注释方面的变更,请参阅 Android 开发者博客中的 Android Pie SDK 现已更适用于 Kotlin一文。
Android Q 包含以下 NDK 方面的变更。
Android Q 增加了 fdsan,它可以帮助您更轻松地查找和修复文件描述符所有权方面的问题。
与错误处理文件描述符所有权相关的错误(通常表现为“use-after-close”和“double-close”)类似于内存分配“use-after-free”和“double-free”错误,但通常更难以诊断和修复。“fdsan”会尝试通过强制执行文件描述符所有权来检测和/或防止文件描述符误管理。
要详细了解与这些问题相关的崩溃,请参阅 fdsan 检测到的错误。要详细了解 fdsan,请参阅关于 fdsan 的 Googlesource 页面。
使用 API 级别 29 及更高版本的 NDK 编译的应用无需再使用 emutls
,但可以改为使用 ELF TLS。我们增加了对动态和静态链接器的支持,以支持处理线程局部变量的新方法。
对于针对 API 级别 28 及更低版本编译的应用,我们实现了针对 libgcc/compiler-rt
的改进,以便解决一些 emutls
问题。
有关详情,请参阅面向 NDK 开发者的 Android 变更。
Android Q 包含以下运行时方面的变更。
当小型平台 Java 对象引用 C++ 堆中的大型对象时,通常只有在系统已回收并(举例而言)最终确定 Java 对象后,才能回收 C++ 对象。在之前的版本中,平台会估算与 Java 对象相关联的许多 C++ 对象的大小。这种估算并不总是准确,并且偶尔会导致内存使用量大大增加,因为平台无法在应该进行垃圾回收时完成回收。
在 Android Q 中,垃圾回收器 (GC) 会跟踪系统 malloc()
分配的堆的总大小,以确保 malloc()
分配的大型堆始终包含在可触发 GC 的计算中。因此,与 Java 执行交错大量 C++ 分配的应用可能会出现垃圾回收频率提高的现象。其他应用的频率则可能会略有下降。
Android Q 包含以下测试和调试方面的改进。
现在,您在执行设备上系统跟踪时可以指定跟踪的记录大小和持续时间限制。在您指定任一值后,系统便会执行长期跟踪,并在记录跟踪时定期将跟踪缓冲区复制到目标文件。在达到您指定的记录大小或持续时间限制后,跟踪便会完成。
请使用这些附加参数来测试除了您使用标准跟踪进行测试的用例之外的其他用例。例如,您可能正在诊断某个性能错误,而此错误仅在您的应用长时间运行后才会发生。在这种情况下,您可以记录为期一整天的长期跟踪,然后分析 CPU 调度程序、磁盘活动、应用线程以及报告中的其他数据,以帮助您确定造成此错误的原因。
Android Q 在 TextClassifier
接口中提供了其他文本分类功能。
TextClassifier
现在具有 detectLanguage()
方法。此方法的工作方式与现有分类方法类似,即接收 TextLanguage.Request
对象并返回 TextLanguage
对象。
新的 TextLanguage
对象包含一系列有序对。每个有序对都包含所请求文本示例的语言区域和相应的置信度得分。
TextClassifier
现在具有 suggestConversationActions()
方法。此方法的工作方式与现有分类方法类似,即接收 ConversationActions.Request
对象并返回 ConversationActions
对象。
新的 ConversationActions
对象包含一系列 ConversationAction
对象。每个 ConversationAction
对象都包含建议采取的可行操作及其置信度得分。
Android 9 引入了在通知中显示建议回复的功能。从 Android Q 开始,通知中还可以包含基于 intent 的建议操作。此外,现在系统可以自动生成这些建议。应用仍然可以提供它们自己的建议,或选择停用系统生成的建议。
用于生成这些回复的 API 是 TextClassifier
的一部分,且已在 Android Q 中直接提供给开发者。如需了解详情,请参阅关于 TextClassifier 改进的部分。
如果您的应用提供自己的建议,则平台不会生成任何自动建议。如果您不希望应用的通知显示任何建议回复或操作,可以通过使用 setAllowGeneratedReplies()
和 setAllowSystemGeneratedContextualActions()
选择停用系统生成的回复和操作。