Register now for Android Dev Summit 2019!

Android Q 功能和 API

Android Q 为用户和开发者引入了强大的新功能。本文重点介绍面向开发者的新功能。

要了解新版 API,请阅读 API 差异报告或访问 Android API 参考文档。为醒目起见,将突出显示新版 API。此外,请务必查阅 Android Q 行为变更(针对以 Android Q 为目标平台的应用所有应用)以及隐私权变更,以了解平台变更可能给您的应用带来哪些方面的影响。

安全增强功能

Android Q 引入了若干安全功能,详见以下各节摘要说明:

改进了生物识别身份验证对话框

Android Q 对 Android 9 中增加的统一生物识别身份验证对话框进行了以下改进:

指定用户确认要求

您现在可以提供一个提示,以告知系统在用户使用隐式生物识别模式完成身份验证后无需要求用户进行确认。例如,您可以告知系统,在用户使用面孔身份验证完成身份验证后无需进一步确认。

默认情况下,系统会要求用户进行确认。通常,用户希望确认敏感或高风险的操作(例如,购买商品)。但是,如果您的应用存在某些低风险操作,您可以将 false 传递到 setConfirmationRequired() 方法,以此来提供不要求用户确认的提示。由于此标记作为提示传递到系统,因此如果用户更改了针对生物识别身份验证的系统设置,则系统可能会忽略相应的值。

无需用户确认的面孔身份验证示例。

图 1. 无需用户确认的面孔身份验证

要求用户确认的面孔身份验证示例。

图 2. 需要用户确认的面孔身份验证

改进了对设备凭据的回退支持

您现在可以告知系统,如果用户因某种原因而无法使用其生物识别输入设备进行身份验证,则可以使用设备 PIN 码、图案或密码来进行身份验证。要启用此回退支持,请使用 setDeviceCredentialAllowed() 方法。

如果您的应用目前使用 createConfirmDeviceCredentialIntent() 回退到设备凭据,请改为使用新方法。

检查设备的生物识别功能

现在,您可以先通过使用 BiometricManager 类中的 canAuthenticate() 方法,检查设备是否支持生物识别身份验证,然后再调用 BiometricPrompt

直接从 APK 运行嵌入式 DEX 代码

您现在可以告知平台直接从应用的 APK 文件中运行嵌入式 DEX 代码。如果攻击者曾设法篡改了设备上本地编译的代码,则此选项有助于防止此类攻击。

要启用此功能,请在应用清单文件的 <application> 元素中将 android:useEmbeddedDex 属性的值设为 true。您还必须编译一个 APK,其中要包含 ART 可以直接访问的未压缩 DEX 代码。将以下选项添加到 Gradle 或 Bazel 配置文件,以编译包含未压缩 DEX 代码的 APK:

Gradle

aaptOptions {
       noCompress 'dex'
    }
    

Bazel

    android_binary(
       ...,
       nocompress_extensions = [“.dex”],
    )
    

TLS 1.3 支持

现在,平台的 TLS 实现支持 TLS 1.3。TLS 1.3 是 TLS 标准的主要修订版本,它提升了性能和安全性。我们的基准测试数据表明,与 TLS 1.2 相比,使用 TLS 1.3 可以将建立安全连接的速度提高 40%。

默认情况下,系统会为所有 TLS 连接启用 TLS 1.3。您可以通过调用 SSLContext.getInstance("TLSv1.2") 来获取已停用 TLS 1.3 的 SSLContext。您还可以对相关对象调用 setEnabledProtocols(),从而为每个连接启用或停用协议版本。

以下是有关 TLS 1.3 实现的一些重要的详细信息:

  • TLS 1.3 加密套件不可自定义。在启用 TLS 1.3 后,受支持的 TLS 1.3 加密套件会始终保持启用状态,并且系统会忽略所有试图通过调用 setEnabledCipherSuites() 将其停用的行为。
  • 在协商 TLS 1.3 时,系统会在将会话添加到会话缓存之前调用 HandshakeCompletedListeners(这与 TLS 1.2 和之前的其他版本不同)。
  • SSLEngine 实例会在之前原本会抛出 SSLHandshakeException 的部分情况下抛出 SSLProtocolException
  • 不支持 0-RTT 模式。

公共 Conscrypt API

现在,Conscrypt 安全提供程序包含适用于 TLS 功能的公共 API。过去,用户可以通过反射来访问此功能。但是,由于在 Android P 中增加了关于调用非公共 API 的限制,因此这已在 Android Q 中加入了灰名单,并将在未来版本中进一步受限。

此更新在 android.net.ssl 下增加了一组类,这些类包含用于访问通用 javax.net.ssl API 不提供的功能的静态方法。这些类的名称为相关 javax.net.ssl 类的复数,用户可以由此推断是否为这些类。例如,在 javax.net.ssl.SSLSocket 实例中运行的代码可以使用新的 android.net.ssl.SSLSockets 类中的方法。

连接功能

Android Q 包含一些与网络和连接相关的改进。

WLAN 网络连接 API

Android Q 增加了对点对点连接的支持。借助此功能,应用可以使用 WifiNetworkSpecifier 描述所请求网络的属性,以此来提示用户更改设备连接到的接入点。点对点连接用于“非网络提供”目的,例如 Chromecast 和 Google Home 硬件等辅助设备的引导配置。

使用此 API 时,您需要遵循以下流程:

  1. 使用 WifiNetworkSpecifier.Builder 创建 WLAN 网络说明符。

  2. 设置网络过滤器以匹配要连接到的网络以及所需凭据。

  3. 在每个请求中确定 SSIDSSID patternBSSIDBSSID pattern 的组合以设置网络过滤器,但要遵循以下要求:

    • 每个请求都应该至少提供 SSIDSSID patternBSSIDBSSID pattern 中的一个
    • 每个请求都只能设置 SSIDSSID pattern 中的一个
    • 每个请求都只能设置 BSSIDBSSID pattern 中的一个
  4. 将说明符以及一个 NetworkCallback 实例添加到该网络请求中,以跟踪该请求的状态。

    如果用户接受该请求且成功连接到网络,则系统将对回调对象调用 NetworkCallback.onAvailable()。如果用户拒绝该请求或未成功连接到网络,则系统将对回调对象调用 NetworkCallback.onUnavailable()

点对点连接不需要位置权限和 WLAN 权限。如果您发起连接到对等设备的请求,便会在同一设备上启动一个对话框,让相应设备的用户可以通过此对话框来接受连接请求。

绕过用户批准

用户应某个特定应用的请求批准连接到某个网络后,设备便会存储用户针对相应接入点的批准响应。如果该应用再次发出连接到此接入点的特定请求,则设备会跳过用户批准阶段,并自动连接到此网络。在连接到 API 请求的网络后,如果用户选择取消保存该网络,则系统会移除该应用和网络组合对应的已存储批准响应,相应应用将来发出的任何请求都需要由用户再次批准。如果应用发出非特定(例如使用 SSID 或 BSSID pattern)请求,则用户需要批准相应请求。

代码示例

以下代码示例显示了如何使用前缀为“test”的 SSID 和 OUI 为“10:03:23”的 BSSID 连接到开放网络。

Kotlin

    val specifier = WifiNetworkSpecifier.Builder()
        .setSsidPattern(PatternMatcher("test", PatternMatcher.PATTERN_PREFIX))
        .setBssidPattern(MacAddress.fromString("10:03:23:00:00:00"), MacAddress.fromString("ff:ff:ff:00:00:00"))
        .build()

    val request = NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
        .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        .setNetworkSpecifier(specifier)
        .build()

    val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    val networkCallback = object : ConnectivityManager.NetworkCallback() {
        ...
        override fun onAvailable(network: Network?) {
            // do success processing here..
        }

        override fun onUnavailable() {
            // do failure processing here..
        }
        ...
    }
    connectivityManager.requestNetwork(request, networkCallback)
    ...
    // Release the request when done.
    connectivityManager.unregisterNetworkCallback(networkCallback)
    

Java

    final NetworkSpecifier specifier =
      new WifiNetworkSpecifier.Builder()
      .setSsidPattern(new PatternMatcher("test", PatterMatcher.PATTERN_PREFIX))
      .setBssidPattern(MacAddress.fromString("10:03:23:00:00:00"), MacAddress.fromString("ff:ff:ff:00:00:00"))
      .build();

    final NetworkRequest request =
      new NetworkRequest.Builder()
      .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
      .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
      .setNetworkSpecifier(specifier)
      .build();

    final ConnectivityManager connectivityManager = (ConnectivityManager)
      context.getSystemService(Context.CONNECTIVITY_SERVICE);

    final NetworkCallback networkCallback = new NetworkCallback() {
      ...
      @Override
      void onAvailable(...) {
          // do success processing here..
      }

      @Override
      void onUnavailable(...) {
          // do failure processing here..
      }
      ...
    };
    connectivityManager.requestNetwork(request, networkCallback);
    ...
    // Release the request when done.
    connectivityManager.unregisterNetworkCallback(networkCallback);
    

WLAN 网络建议 API

Android Q 扩大了支持范围,现在允许应用为设备添加网络凭据,以自动连接到 WLAN 接入点。您可以使用 WifiNetworkSuggestion 提供关于要连接到哪个网络的建议。平台最终会根据您的应用和其他应用的输入来选择要接受的接入点。

以下代码示例显示了如何为一个开放网络、一个 WPA2 网络和一个 WPA3 网络提供凭据:

Kotlin

    val suggestion1 = WifiNetworkSuggestion.Builder()
            .setSsid("test111111")
            .setIsAppInteractionRequired() // Optional (Needs location permission)
            .build()

    val suggestion2 = WifiNetworkSuggestion.Builder()
            .setSsid("test222222")
            .setWpa2Passphrase("test123456")
            .setIsAppInteractionRequired() // Optional (Needs location permission)
            .build()

    val suggestion3 = WifiNetworkSuggestion.Builder()
            .setSsid("test333333")
            .setWpa3Passphrase("test6789")
            .setIsAppInteractionRequired() // Optional (Needs location permission)
            .build()

    val suggestionsList = listOf(suggestion1, suggestion2, suggestion3)

    val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager

    val status = wifiManager.addNetworkSuggestions(suggestionsList);
    if (status != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
        // do error handling here
    }

    // Optional (Wait for post connection broadcast to one of your suggestions)
    val intentFilter = IntentFilter(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);

    val broadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            if (!intent.action.equals(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)) {
                return;
            }
            // do post connect processing here
        }
    };
    context.registerReceiver(broadcastReceiver, intentFilter);
    

Java

    final WifiNetworkSuggestion suggestion1 =
      new WifiNetworkSuggestion.Builder()
      .setSsid("test111111")
      .setIsAppInteractionRequired() // Optional (Needs location permission)
      .build()

    final WifiNetworkSuggestion suggestion2 =
      new WifiNetworkSuggestion.Builder()
      .setSsid("test222222")
      .setWpa2Passphrase("test123456")
      .setIsAppInteractionRequired() // Optional (Needs location permission)
      .build()

    final WifiNetworkSuggestion suggestion3 =
      new WifiNetworkSuggestion.Builder()
      .setSsid("test333333")
      .setWpa3Passphrase("test6789")
      .setIsAppInteractionRequired() // Optional (Needs location permission)
      .build()

    final List<wifinetworksuggestion> suggestionsList =
      new ArrayList<wifinetworksuggestion> {{
        add(suggestion1);
        add(suggestion2);
        add(suggestion3);
      }};

    final WifiManager wifiManager =
      (WifiManager) context.getSystemService(Context.WIFI_SERVICE);

    final int status = wifiManager.addNetworkSuggestions(suggestionsList);
    if (status != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
    // do error handling here…
    }

    // Optional (Wait for post connection broadcast to one of your suggestions)
    final IntentFilter intentFilter =
      new IntentFilter(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);

    final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
      @Override
      public void onReceive(Context context, Intent intent) {
        if (!intent.getAction().equals(
          WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)) {
          return;
        }
        // do post connect processing here..
      }
    };
    context.registerReceiver(broadcastReceiver, intentFilter);
    

来自应用的建议必须先得到用户批准,然后平台才会发起到建议的网络的连接。当平台第一次在扫描结果中找到与应用提供的其中一个建议相匹配的网络时,将由用户响应通知进行批准。当平台连接到建议的其中某个网络时,设置会显示相关文本以将网络连接归因于提出建议的相应应用。

处理用户断开连接的行为

如果用户在应用连接到建议的某个网络时使用 WLAN 选择器明确断开与该网络的连接,则该网络会被列入黑名单 24 小时。在该网络被列入黑名单期间,即使应用移除并重新添加与该网络对应的网络建议,系统也不会考虑自动连接该网络。

更改应用的审批状态

用户拒绝接收网络建议通知时会从应用中移除 CHANGE_WIFI_STATE 权限。用户稍后可以通过转到 WLAN 控制菜单(依次转到设置 > 应用和通知 > 特殊应用权限 > WLAN 控制 > 应用名称)对此作出批准。

改进了 WLAN 高性能和低延迟模式

借助 Android Q,您可以为底层调制解调器提供提示,以最大限度地缩短延迟。

Android Q 扩展了 WLAN Lock API,以有效地支持高性能和低延迟模式。系统会针对高性能和低延迟模式停用 WLAN 节能模式,并且您可以在低延迟模式下启用进一步的延迟优化(具体取决于调制解调器支持)。

仅当获取锁的应用在前台运行且屏幕处于开启状态时才能启用低延迟模式。低延迟模式对实时移动游戏应用尤其有用。

DNS 解析器中的专用查找

Android Q 增加了对“通过传输层安全协议 (TLS) 执行 DNS”和进行专用 DNS 查找的支持。以前,平台 DNS 解析器支持 A 类解析,它可以根据域名来解析 IP 地址,而无需与通过此 IP 提供的服务有关的具体信息。经过此次更新,它现在还支持 SRVNAPTR 查找。

Android Q 可以为开发者提供标准明文查找和“通过传输层安全协议 (TLS) 执行 DNS”模式。

WLAN Easy Connect

借助 Android Q,您可以利用 Easy Connect 为对等设备配置 WLAN 凭据,以替代已弃用的 WPS。应用可以使用 ACTION_PROCESS_WIFI_EASY_CONNECT_URI intent 将 Easy Connect 集成到其设置和配置流程中。此 intent 需要 URI。调用应用可以通过各种方法来检索 URI,包括扫描贴纸或显示屏中的二维码,或扫描蓝牙 LE 或 NFC 广告。

URI 一经可用,您就可以使用 ACTION_PROCESS_WIFI_EASY_CONNECT_URI intent 来配置对等设备的 WLAN 凭据。这样一来,用户就可以选择 WLAN 网络以共享并安全地传输相关凭据。

Easy Connect 不需要位置权限或 WLAN 权限。

Wi-Fi Direct connection API

WifiP2pConfigWifiP2pManager API 类在 Android Q 中有更新,以支持利用预先确定的信息快速与 WLAN 直连建立连接的功能。此信息通过边信道进行共享,例如蓝牙或 NFC。

以下代码示例显示了如何使用预先确定的信息来创建群组:

Kotlin

    val manager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager
    val channel = manager.initialize(this, mainLooper, null)

    // prefer 5G band for this group
    val config = WifiP2pConfig.Builder()
        .setNetworkName("networkName")
        .setPassphrase("passphrase")
        .enablePersistentMode(false)
        .setGroupOperatingBand(WifiP2pConfig.GROUP_OWNER_BAND_5GHZ)
        .build()

    // create a non-persistent group on 5GHz
    manager.createGroup(channel, config, null)
    

Java

    WifiP2pManager manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
    Channel channel = manager.initialize(this, getMainLooper(), null);

    // prefer 5G band for this group
    WifiP2pConfig config = new WifiP2pConfig.Builder()
    .setNetworkName("networkName")
    .setPassphrase("passphrase")
    .enablePersistentMode(false)
    .setGroupOperatingBand(WifiP2pConfig.GROUP_OWNER_BAND_5GHZ)
    .build();

    // create a non-persistent group on 5GHz
    manager.createGroup(channel, config, null);
    

要使用凭据加入群组,请将 manager.createGroup() 替换为以下内容:

Kotlin

    manager.connect(channel, config, null)
    

Java

    manager.connect(channel, config, null);
    

蓝牙 LE 连接导向型频道 (CoC)

借助 Android Q,您的应用可以使用 BLE CoC 连接在两个 BLE 设备之间传输较大的数据流。此接口抽象化处理了蓝牙和连接机制,以简化实现。

电话功能

Android Q 包含一些与电话相关的改进。

通话质量方面的改进

Android Q 增加了在支持相关功能的设备上收集进行中的 IP 多媒体子系统 (IMS) 通话质量相关信息的功能,包括通过网络接听和拨打电话的质量。

选接电话和来电显示

Android Q 让您的应用可以将用户通讯录中不存在号码的来电标识为潜在骚扰电话,以及代表用户拒接骚扰电话而不响铃。系统会在通话记录中将这些已屏蔽的来电的相关信息记录为已屏蔽的来电,以便用户更清楚地了解何时有过未接来电。使用此新版 API,不需要从用户那里获取 READ_CALL_LOG 权限就可以提供选接电话和来电显示功能。

Call Redirection Service API

Android Q 更改了来电 Intent 的处理方式。我们弃用了 NEW_OUTGOING_CALL 广播,并将其替换为 CallRedirectionService API。CallRedirectionService 提供了相关接口,以供您修改 Android 平台拨打的去电。例如,第三方应用可能会取消通话并通过 VoIP 对其进行重新路由。

在外部存储设备中创建文件的相关改进

除了引入分区存储隐私权行为变更之外,Android Q 还在写入文件方面提供了更大的灵活性,添加了可以帮助您影响这些文件在外部存储设备上的保存位置的功能。

新媒体文件的待处理状态

Android Q 引入了 IS_PENDING 标记,通过此标记,您的应用可以在媒体文件写入磁盘时进行独占访问。

以下代码段显示了在应用中创建新图片时如何使用 IS_PENDING 标记。

Kotlin

    val values = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, "IMG1024.JPG")
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        put(MediaStore.Images.Media.IS_PENDING, 1)
    }

    val resolver = context.getContentResolver()
    val collection = MediaStore.Images.Media
            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
    val item = resolver.insert(collection, values)

    resolver.openFileDescriptor(item, "w", null).use { pfd ->
        // Write data into the pending image.
    }

    // Now that we're finished, release the "pending" status, and allow other apps
    // to view the image.
    values.clear()
    values.put(MediaStore.Images.Media.IS_PENDING, 0)
    resolver.update(item, values, null, null)
    

Java

    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.DISPLAY_NAME, "IMG1024.JPG");
    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
    values.put(MediaStore.Images.Media.IS_PENDING, 1);

    ContentResolver resolver = context.getContentResolver();
    Uri collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
    Uri item = resolver.insert(collection, values);

    try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(item, "w", null)) {
        // Write data into the pending image.
    } catch (IOException e) {
        e.printStackTrace();
    }

    // Now that we're finished, release the "pending" status, and allow other apps
    // to view the image.
    values.clear();
    values.put(MediaStore.Images.Media.IS_PENDING, 0);
    resolver.update(item, values, null, null);
    

对存储位置的影响

Android Q 引入了多种功能,可以帮助您整理您的应用存储在外部存储设备上的文件。

目录提示

当您的应用将媒体存储在搭载 Android Q 的设备上时,系统默认按照媒体的类型对其进行整理。例如,默认情况下,新图片文件会存储在“pictures”目录下。

如果您的应用知道应存储文件的具体位置(例如 Pictures/MyVacationPictures),您可以设置 MediaColumns.RELATIVE_PATH,以便提示系统在何处存储新写入的文件。同样,您可以在调用 update() 期间通过更改 MediaColumns.RELATIVE_PATHMediaColumns.DISPLAY_NAME 来移动磁盘上的文件。

设备选择

在 Android 9(API 级别 28)及更低版本中,保存到外部存储设备上的所有文件都显示在名为 external 的单个卷下。但是,Android Q 为每个外部存储设备都提供唯一的卷名称。这一新的命名系统可帮助您高效地整理内容并将内容编入索引,还可让您控制新内容的存储位置。

主要共享存储设备始终称为 VOLUME_EXTERNAL_PRIMARY。您可以通过调用 MediaStore.getExternalVolumeNames() 发现其他卷。

要查询、插入、更新或删除特定卷,请将卷名称传递到 MediaStore API 中的任何 getContentUri() 方法,如以下代码段中所示:

    // Publish an audio file onto a specific external storage device.
    val values = ContentValues().apply {
        put(MediaStore.Audio.Media.RELATIVE_PATH, "Music/My Album/My Song")
        put(MediaStore.Audio.Media.DISPLAY_NAME, "My Song.mp3")
    }

    // Assumes that the storage device of interest is the 2nd one
    // that your app recognizes.
    val volumeNames = MediaStore.getExternalVolumeNames(context)
    val selectedVolumeName = volumeNames[1]
    val collection = MediaStore.Audio.Media.getContentUri(selectedVolumeName)
    val item = resolver.insert(collection, values)
    

媒体和图形

Android Q 引入了以下媒体和图形方面的新功能和 API:

捕获播放的音频

Android Q 可让应用捕获其他应用播放的音频。要了解所有详情,请参阅捕获播放的音频

原生 MIDI API

借助 Android Native MIDI API (AMidi),应用开发者可以使用 C/C++ 代码发送和接收 MIDI 数据、与 C/C++ 音频/控制逻辑进行更紧密的集成以及最大限度地减少对 JNI 的需求。

有关详情,请参阅 Android Native MIDI API

MediaCodecInfo 方面的改进

MediaCodecInfo 中有一些新方法可以显示有关编解码器的更多信息:

isSoftwareOnly()
如果编解码器仅在软件中运行,则返回 true。软件编解码器并不能保证渲染性能。
isHardwareAccelerated()
如果编解码器由硬件加速,则返回 true。
isVendor()
如果编解码器由设备供应商提供,则返回 true;如果由 Android 平台提供,则返回 false。
isAlias()
MediaCodecList 可能针对使用备用编解码器名称(别名)的同一底层编解码器包含额外的条目。如果此条目中的编解码器是另一个编解码器的别名,则此方法会返回 true。

此外,MediaCodec.getCanonicalName() 会针对通过别名创建的编解码器返回底层编解码器名称。

性能点

“性能点”表示编解码器以特定高度、宽度和帧速率渲染视频的能力。例如,UHD_60 性能点表示以每秒 60 帧的速度渲染超高清视频(3840x2160 像素)。

方法 MediaCodecInfo.VideoCapabilities.getSupportedPerformancePoints() 会返回编解码器可以渲染或捕获的 PerformancePoint 条目列表。

您可以通过调用 PerformancePoint.covers(PerformancePoint) 检查给定的 PerformancePoint 是否会覆盖另一个性能点。例如,UHD_60.covers(UHD_50) 会返回 true。

我们为所有硬件加速的编解码器都提供了性能点列表。如果编解码器连标准性能点的最低值都不能满足,则此列表可能为空。

请注意,已升级到 Android Q 但未更新供应商映像的设备是没有性能点数据的,因为此数据来自供应商 HAL。在这种情况下,getSupportedPerformancePoints() 会返回 null。

ANGLE

在 Android Q 发布后,Android 开发者和合作伙伴可以选择使用 ANGLE 运行应用;ANGLE 是 Chrome 组织中的一个项目,能够将 ES 置于 Vulkan 上层,而不必使用供应商提供的 ES 驱动程序。

有关详情,请参阅 ANGLE

Thermal API

当设备过热时,它们可能会限制 CPU 和/或 GPU,而这可能会以意想不到的方式影响应用和游戏。使用复杂图形、大量计算或持续网络活动的应用更有可能遇到问题,并且问题可能因芯片组和核心频率、集成级别以及设备封装和设备类型而异。

现在,在 Android Q 中,应用和游戏可以使用 Thermal API 监控设备变化情况,并在设备过热时采取措施维持低电耗状态,使设备恢复到正常温度。应用在 PowerManager注册监听器,系统通过该监听器报告持续的热状态,热状态的范围从轻度和中度到重度、危急、紧急和关机。

当设备报告热应力时,各应用和游戏可以减少正在进行的活动,以此来帮助减少各个方面的耗电量。例如,影音在线播放应用可以降低分辨率/比特率或减少网络流量;相机应用可以停用闪光灯或密集型图像增强;游戏可以降低帧速率或减少多边形曲面细分;媒体应用可以降低扬声器音量;地图应用可以关闭 GPS。

Thermal API 需要新的设备 HAL 层,目前在搭载 Android Q 的 Pixel 设备上受支持。我们正在与设备制造商合作伙伴合作,以期尽快为生态系统提供广泛支持。

摄像头和图片

Android Q 引入了摄像头和图片方面的以下新功能:

单色摄像头支持

Android 9(API 等级 28)首次引入了单色摄像头功能。Android Q 为单色摄像头支持增加了几项增强功能:

  • 新增了对 Y8 流格式的支持,以提高内存效率。
  • 支持单色原始 DNG 捕获。
  • 引入了 MONO 和 NIR CFA 枚举,以区分常规单色摄像头和近红外摄像头。

您可以使用此功能来捕捉原生单色图片。逻辑多摄像头设备可以使用单色摄像头作为物理子摄像头,以获取更出色的低光图片质量。

动态深度格式

从 Android Q 开始,摄像头可以使用名为“动态深度格式”(DDF) 的新架构将图片的深度数据存储在单独的文件中。应用可以请求 JPG 图片及其深度元数据,以便在后期处理中利用这些信息来应用所需的模糊处理,而无需修改原始图片数据。

要了解新格式的规范,请参阅动态深度格式

高效率图片文件格式

高效率图片文件 (HEIF) 格式是一种标准的图片和视频格式,与其他文件格式相比,这种格式的编码质量更高,文件更小。

如需详细了解此文件格式,请参阅 HEIC

多摄像头方面的改进

Android 9(API 级别 28)中引入了将多摄像头融合成单个逻辑摄像头的功能,Android Q 对此功能进行了改进。Camera2 API 中添加了以下内容:

无障碍服务 API

Android Q 引入了以下新的无障碍服务功能和 API:

AccessibilityNodeInfo 输入键标记

在 Android Q 中,AccessibilityNodeInfo 得到了增强,现在增加了一个新标记,用于指示其是否代表文本输入键的。您可以使用 AccessibilityNodeInfo.isTextEntryKey() 方法来访问此标记。

无障碍对话框语音反馈

当无障碍服务要求用户重复按无障碍快捷方式以启动服务时,此对话框现在可以在服务请求时附带文字转语音提示。

物理键盘的无障碍快捷方式

在 Android Q 中,用户现在可以通过物理键盘来触发无障碍快捷方式,只需按 Control+Alt+Z 即可。

软键盘控制器增强功能

在 Android Q 中,无障碍服务现在可以请求显示软键盘,即使设备检测到连接了硬键盘也不例外。用户可以替换此行为。

用户定义的无障碍服务超时

Android Q 引入了 API 方法 AccessibilityManager.getRecommendedTimeoutMillis(),用于为用户针对互动式和非互动式的无障碍界面元素定义的超时提供支持。返回值受用户偏好设置和无障碍服务 API 的影响。

自动填充方面的改进

Android Q 包含对自动填充服务的以下改进。

您现在可以使用 FillRequest.FLAG_COMPATIBILITY_MODE_REQUEST 标记确定是否通过兼容性模式生成了自动填充请求。

同时保存用户名和密码

借助 SaveInfo.FLAG_DELAY_SAVE 标记,应用现在可以使用多个 Activity 显示用户名、密码和其他字段。

用户与保存界面的互动

您现在可以在保存对话框中显示和隐藏密码字段,只需在此对话框中设置操作监听器,并更改相应密码远程视图的可见性即可。

支持更新数据集

现在,自动填充功能可以更新现有密码。例如,如果用户已经存储了一个密码,然后又保存了一个新密码,则自动填充功能现在会提示用户更新现有密码,而不是保存新密码。

字段分类方面的改进

Android Q 包含对 Field Classification API 的以下改进。

UserData.Builder 构造函数

我们更改了 UserData.Builder 构造函数,现在可以更好地契合 Builder 模式。

允许将一个值映射到多种类别 ID

在使用 Android Q 中的 UserData.Builder 时,您现在可以将一个值映射到多种类别 ID。在以前的版本中,如果一个值被添加多次,系统就会抛出异常。

改进了对信用卡号码的支持

现在,字段分类可以检测四位数字作为信用卡号码的最后四位数字。

支持特定于应用的字段分类

Android Q 增加了 FillResponse.setUserData(),让您能够在会话期间设置特定于应用的用户数据。这有助于自动填充服务检测包含特定于应用的内容的字段的类型。

界面和系统控件

Android Q 提供以下界面方面的改进:

支持 JVMTI PopFrame 功能

Android Q 增加了对 Android JVMTI 实现中的 can_pop_frames 功能的支持。在调试时,此功能让您能够在断点暂停并调整函数的局部变量、全局变量或实现,然后重新运行函数。有关详情,请参阅 Oracle 的 Pop Frame 参考页面

Surface Control API

Android Q 提供了一个 SurfaceControl API,用于对系统合成器 (SurfaceFlinger) 进行底层访问。对于大多数用户而言,SurfaceView 是使用此合成器的正确方法。SurfaceControl API 在某些情况下很有用,例如:

  • 同步多个表面
  • 跨进程的表面嵌入
  • 底层生命周期管理

SurfaceControl API 在 SDK 和 NDK 绑定中都可用。NDK 实现包含用于与合成器手动交换缓冲区的 API。这为遇到 BufferQueue 限制的用户提供了一种替代方案。

WebView 挂起渲染程序检测

Android Q 引入了一个新的 WebViewRenderProcessClient 抽象类,应用可以使用该抽象类检测 WebView 是否无响应。要使用此类,请执行以下操作:

  1. 定义您自己的子类,并实现其 onRenderProcessResponsive()onRenderProcessUnresponsive() 方法。
  2. WebViewRenderProcessClient 的实例附加到一个或多个 WebView 对象上。
  3. 如果 WebView 无响应,系统将调用客户端的 onRenderProcessUnresponsive() 方法,在调用时会传递 WebViewWebViewRenderProcess(如果 WebView 是单进程,WebViewRenderProcess 参数将为 null)。您的应用可以执行适当的操作,例如向用户显示一个对话框,以询问其是否要暂停渲染流程。

如果 WebView 仍然无响应,则系统会定期调用 onRenderProcessUnresponsive()(频率不高于每 5 秒一次),但不会执行其他任何操作。如果 WebView 再次无响应,系统只调用 onRenderProcessResponsive() 一次。

设置面板

Android Q 引入了“设置面板”,这是一种 API,让应用能够在自身环境中向用户显示设置。这可以避免用户转到设置更改 NFC移动数据等设置,以便使用此应用。

图 1. 用户尝试在设备未连接到网络时打开网页。Chrome 弹出互联网连接设置面板…

图 2. 用户可以开启 WLAN 并选择网络,而无需离开 Chrome 应用。

例如,假设用户打开了网络浏览器,而其设备已开启飞行模式。在 Android Q 之前的版本中,此应用只能显示一条通用消息,要求用户打开设置以恢复连接。而借助 Android Q,浏览器应用便可以显示一个内嵌面板,其中会显示各种主要连接设置,例如飞行模式、WLAN(包括附近的网络)和移动数据。借助此面板,用户无需离开应用即可恢复连接。

要显示设置面板,请发出具有某个新 Settings.Panel 操作的 intent:

Kotlin

    val panelIntent = Intent(Settings.Panel.settings_panel_type)
    startActivityForResult(panelIntent)
    

Java

    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 处理正在进行的通话

Kotlin

Android Q 对 Kotlin 开发进行了以下更新。

libcore API 的可空性注释

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一文。

NDK

Android Q 包含以下 NDK 方面的变更。

改进了文件描述符所有权的调试

Android Q 增加了 fdsan,它可以帮助您更轻松地查找和修复文件描述符所有权方面的问题。

与错误处理文件描述符所有权相关的错误(通常表现为“use-after-close”和“double-close”)类似于内存分配“use-after-free”和“double-free”错误,但通常更难以诊断和修复。“fdsan”会尝试通过强制执行文件描述符所有权来检测和/或防止文件描述符误管理。

要详细了解与这些问题相关的崩溃,请参阅 fdsan 检测到的错误。要详细了解 fdsan,请参阅关于 fdsan 的 Googlesource 页面

ELF TLS

使用 API 级别 29 及更高版本的 NDK 编译的应用无需再使用 emutls,但可以改为使用 ELF TLS。我们增加了对动态和静态链接器的支持,以支持处理线程局部变量的新方法。

对于针对 API 级别 28 及更低版本编译的应用,我们实现了针对 libgcc/compiler-rt 的改进,以便解决一些 emutls 问题。

有关详情,请参阅面向 NDK 开发者的 Android 变更

运行时

Android Q 包含以下运行时方面的变更。

触发基于 Mallinfo 的垃圾回收

当小型平台 Java 对象引用 C++ 堆中的大型对象时,通常只有在系统已回收并(举例而言)最终确定 Java 对象后,才能回收 C++ 对象。在之前的版本中,平台会估算与 Java 对象相关联的许多 C++ 对象的大小。这种估算并不总是准确,并且偶尔会导致内存使用量大大增加,因为平台无法在应该进行垃圾回收时完成回收。

在 Android Q 中,垃圾回收器 (GC) 会跟踪系统 malloc() 分配的堆的总大小,以确保 malloc() 分配的大型堆始终包含在可触发 GC 的计算中。因此,与 Java 执行交错大量 C++ 分配的应用可能会出现垃圾回收频率提高的现象。其他应用的频率则可能会略有下降。

测试和调试

Android Q 包含以下测试和调试方面的改进。

改进了设备上系统跟踪功能

现在,您在执行设备上系统跟踪时可以指定跟踪的记录大小和持续时间限制。在您指定任一值后,系统便会执行长期跟踪,并在记录跟踪时定期将跟踪缓冲区复制到目标文件。在达到您指定的记录大小或持续时间限制后,跟踪便会完成。

请使用这些附加参数来测试除了您使用标准跟踪进行测试的用例之外的其他用例。例如,您可能正在诊断某个性能错误,而此错误仅在您的应用长时间运行后才会发生。在这种情况下,您可以记录为期一整天的长期跟踪,然后分析 CPU 调度程序、磁盘活动、应用线程以及报告中的其他数据,以帮助您确定造成此错误的原因。

TextClassifier 改进

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() 选择停用系统生成的回复和操作。