Android 17 では、デベロッパー向けに優れた新しい機能と API が導入されました。以下のセクションでは、これらの機能の概要を説明し、関連する API を試すための情報を提供します。
新しい API、変更された API、削除された API の一覧については、API 差分 レポートをご覧ください。新しい API について詳しくは、Android API リファレンスをご覧ください。新しい API は、見つけやすいようにハイライト表示されています。
プラットフォームの変更がアプリに影響する可能性がある領域も確認してください。詳しくは次のページをご覧ください。
コア機能
Android 17 では、Android のコア機能に関連する次の新機能が追加されています。
新しい ProfilingManager トリガー
Android 17 では、パフォーマンスの問題をデバッグするための詳細なデータを収集できるように、ProfilingManager に
いくつかの新しいシステム トリガーが追加されています。
新しいトリガーは次のとおりです。
TRIGGER_TYPE_COLD_START: アプリのコールド スタート時にトリガーが発生します。レスポンスでコールスタック サンプルとシステム トレースの両方が提供されます。TRIGGER_TYPE_OOM: アプリがOutOfMemoryErrorをスローしたときにトリガーが発生し、レスポンスで Java ヒープダンプが提供されます。TRIGGER_TYPE_KILL_EXCESSIVE_CPU_USAGE: アプリが異常な CPU 使用率の高さにより強制終了されたときにトリガーが発生し、レスポンスでコールスタック サンプルが提供されます。TRIGGER_TYPE_ANOMALY: バインダー呼び出しの過多やメモリ使用量の過多など、システム パフォーマンスの異常を検出します。
システム トリガーの設定方法については、 トリガーベースのプロファイリングとプロファイリング データの取得と分析 に関するドキュメントをご覧ください。
アプリの異常のプロファイリング トリガー
Android 17 では、リソースを大量に消費する動作や互換性の低下の可能性をモニタリングするオンデバイス異常検出サービスが導入されています。ProfilingManager と統合されたこのサービスにより、アプリはシステムが検出した特定のイベントによってトリガーされるプロファイリング
アーティファクトを受け取ることができます。
TRIGGER_TYPE_ANOMALY トリガーを使用して、バインダー呼び出しの過多やメモリ使用量の過多など、システム パフォーマンスの問題
を検出します。アプリが OS で定義されたメモリ制限を超えると、異常トリガーにより、デベロッパーはアプリ固有のヒープダンプを受け取って、メモリの問題を特定して修正できます。また、バインダー スパムが過剰な場合は、異常トリガーにより、バインダー トランザクションのスタック サンプリング プロファイルが提供されます。
この API コールバックは、システムによって強制される前に発生します。たとえば、メモリ制限を超えたためにアプリがシステムによって強制終了される前に、デベロッパーがデバッグデータを収集するのに役立ちます。
val profilingManager =
applicationContext.getSystemService(ProfilingManager::class.java)
val triggers = ArrayList<ProfilingTrigger>()
triggers.add(ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_ANOMALY))
val mainExecutor: Executor = Executors.newSingleThreadExecutor()
val resultCallback = Consumer<ProfilingResult> { profilingResult ->
if (profilingResult.errorCode != ProfilingResult.ERROR_NONE) {
// upload profile result to server for further analysis
setupProfileUploadWorker(profilingResult.resultFilePath)
}
profilingManager.registerForAllProfilingResults(mainExecutor,
resultCallback)
profilingManager.addProfilingTriggers(triggers)
}
JobDebugInfo API
Android 17 では、JobScheduler ジョブのデバッグに役立つ新しい JobDebugInfo API が導入されました。ジョブが実行されない理由、実行時間、その他の集計情報などを確認できます。
拡張された JobDebugInfo API の最初のメソッドは
getPendingJobReasonStats() です。このメソッドは、ジョブが
実行保留状態になった理由と、それぞれの累積保留
期間のマップを返します。このメソッドは、getPendingJobReasonsHistory() と
getPendingJobReasons() メソッドを結合して、スケジュールされた
ジョブが想定どおりに実行されない理由を把握できるようにします。また、期間とジョブの理由の両方を 1 つのメソッドで取得できるようにすることで、情報取得を簡素化します。
たとえば、指定された jobId に対して、メソッドは PENDING_JOB_REASON_CONSTRAINT_CHARGING と 60000 ミリ秒の期間を返すことがあります。これは、充電制約が満たされていないため、ジョブが 60000 ミリ秒保留されたことを示します。
allow-while-idle アラームのリスナー サポートによりウェイクロックを削減
Android 17
引入了 AlarmManager.setExactAndAllowWhileIdle 的新变体,该变体
接受 OnAlarmListener 而不是 PendingIntent。这种基于回调的新机制非常适合目前依赖于连续唤醒锁来执行定期任务的应用,例如维护套接字连接的消息传递应用。
プライバシー
Android 17 には、ユーザーのプライバシーを強化するための次の新機能が搭載されています。
Encrypted Client Hello(ECH)プラットフォームのサポート
Android 17 では、ネットワーク通信のプライバシーを大幅に強化する Encrypted Client Hello(ECH)のプラットフォーム サポートが導入されています。ECH は、最初の TLS handshake 中に Server Name Indication(SNI)を暗号化する TLS 1.3 拡張機能です。この暗号化により、ネットワーク仲介者がアプリの接続先ドメインを特定することが難しくなり、ユーザーのプライバシーを保護できます。
プラットフォームに、ネットワーク ライブラリが ECH を実装するために必要な API が含まれるようになりました。これには、ECH 構成を含む HTTPS DNS レコードをクエリする DnsResolver の新機能と、ドメインに接続するときにこれらの構成を渡して ECH を有効にする Conscrypt の SSLEngines と SSLSockets の新しいメソッドが含まれます。デベロッパーは、ネットワーク セキュリティ構成ファイル内の新しい <domainEncryption> 要素を使用して、ECH の設定(機会的有効化や使用の義務付けなど)を構成できます。この設定は、グローバルに適用することも、ドメインごとに適用することもできます。
HttpEngine、WebView、OkHttp などの一般的なネットワーキング ライブラリは、今後のアップデートでこれらのプラットフォーム API を統合することが見込まれており、アプリが ECH を採用してユーザーのプライバシーを強化することが容易になります。
詳細については、暗号化されたクライアント Hello のドキュメントをご覧ください。
Android 連絡先選択ツール
Android 連絡先選択ツールは、ユーザーがアプリと連絡先を共有するための標準化されたブラウジング可能なインターフェースです。Android 17(API レベル 37)以降を搭載したデバイスで利用できるこの選択ツールは、広範な READ_CONTACTS 権限に代わるプライバシーの保護に配慮した選択肢となります。アプリは、ユーザーのアドレス帳全体へのアクセスをリクエストするのではなく、電話番号やメールアドレスなど、必要なデータフィールドを指定し、ユーザーが共有する特定の連絡先を選択します。これにより、アプリは選択したデータへの読み取りアクセスのみを許可され、UI を構築または維持することなく、組み込みの検索、プロファイルの切り替え、複数選択機能により、一貫したユーザー エクスペリエンスを提供しながら、きめ細かい制御を実現できます。
詳しくは、連絡先選択ツールのドキュメントをご覧ください。
セキュリティ
Android 17 では、デバイスとアプリのセキュリティを強化するための次の新機能が追加されています。
Android の高度な保護機能モード(AAPM)
Android Advanced Protection Mode offers Android users a powerful new set of security features, marking a significant step in safeguarding users—particularly those at higher risk—from sophisticated attacks. Designed as an opt-in feature, AAPM is activated with a single configuration setting that users can turn on at any time to apply an opinionated set of security protections.
These core configurations include blocking app installation from unknown sources
(sideloading), restricting USB data signaling, and mandating Google Play Protect
scanning, which significantly reduces the device's attack surface area.
Developers can integrate with this feature using the
AdvancedProtectionManager API to detect the mode's status, enabling
applications to automatically adopt a hardened security posture or restrict
high-risk functionality when a user has opted in.
PQC APK 署名
Android 现在支持混合 APK 签名方案,以保护应用的签名身份免受利用量子计算的攻击的潜在威胁。此功能引入了一种新的 APK 签名方案,可让您将经典签名密钥(例如 RSA 或 EC)与新的后量子加密 (PQC) 算法 (ML-DSA) 配对。
这种混合方法可确保您的应用在未来免受量子攻击,同时与依赖于经典签名验证的旧版 Android 和设备保持完全的向后兼容性。
对开发者的影响
- 使用 Play 应用签名的应用:如果您使用 Play 应用签名,可以等待 Google Play 为您提供使用 Google Play 生成的 PQC 密钥升级混合签名的选项,从而确保您的应用受到保护,而无需手动管理密钥。
- 使用自行管理的密钥的应用:自行管理签名密钥的开发者可以利用更新后的 Android build 工具(例如 apksigner)轮换到混合身份,将 PQC 密钥与新的经典密钥相结合。(您必须创建新的经典密钥,无法重复使用旧密钥。)
接続
Android 17 では、デバイスとアプリの接続を強化するための次の機能が追加されています。
制約付き衛星ネットワーク
低帯域幅の衛星ネットワークでアプリが効果的に機能するように最適化を実装します。
ユーザー エクスペリエンスとシステム UI
Android 17 には、ユーザー エクスペリエンスを向上させるための次の変更が含まれています。
専用のアシスタント音量ストリーム
Android 17 では、アシスタント アプリ専用のアシスタント音量ストリームが導入され、
USAGE_ASSISTANTで再生できるようになりました。この変更により、アシスタントの音声が標準のメディア
ストリームから分離され、ユーザーは両方の音量を個別に制御できるようになります。これにより、アシスタントの応答の可聴性を維持しながらメディアの再生をミュートしたり、その逆の操作を行ったりできます。
新しい MODE_ASSISTANT_CONVERSATION 音声
モードにアクセスできるアシスタントアプリは、音量制御の一貫性をさらに向上させることができます。アシスタント アプリはこのモードを使用して、アクティブなアシスタント
セッションに関するヒントをシステムに提供できます。これにより、アクティブな USAGE_ASSISTANT
再生の外部または接続された Bluetooth 周辺機器を使用して、アシスタント ストリームを制御できます。
引き継ぎ
切换是 Android 17 中新增的一项功能和 API,应用开发者可以将其集成到应用中,以便为用户提供跨设备连续性。它允许用户在一个 Android 设备上启动应用 activity,然后将其转移到另一个 Android 设备。Handoff 在用户设备的后台运行,并通过各种入口点(例如接收设备上的启动器和任务栏)显示用户附近其他设备上的可用活动。
应用可以指定 Handoff 来启动相同的原生 Android 应用(如果该应用已安装在接收设备上且可供使用)。在此应用到应用流程中,用户通过深层链接跳转到指定 activity。或者,应用到网站切换功能可以作为后备选项提供,也可以通过网址切换功能直接实现。
切换支持是按 activity 实现的。如需启用 Handoff,请针对 activity 调用 setHandoffEnabled() 方法。可能需要随切换传递其他数据,以便接收设备上重新创建的 activity 可以恢复适当的状态。实现 onHandoffActivityDataRequested() 回调以返回 HandoffActivityData 对象,该对象包含用于指定 Handoff 应如何处理并在接收设备上重新创建 activity 的详细信息。
ライブアップデート - セマンティック カラー API
在 Android 17 中,实时更新启动了语义着色 API,以支持具有通用含义的颜色。
以下类支持语义着色:
NotificationNotification.MetricNotification.ProgressStyle.PointNotification.ProgressStyle.Segment
填色游戏
- 绿色:与安全相关。此颜色应在以下情况下使用:让别人知道您处于安全状态。
- 橙色:用于表示警告和标记物理危险。在用户需要注意以设置更好的保护设置的情况下,应使用此颜色。
- 红色:通常表示危险、停止。它应在需要人们紧急关注的情况下显示。
- 蓝色:中性颜色,适用于信息性内容,应与其他内容区分开来。
以下示例展示了如何将语义样式应用于通知中的文本:
val ssb = SpannableStringBuilder()
.append("Colors: ")
.append("NONE", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_UNSPECIFIED), 0)
.append(", ")
.append("INFO", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_INFO), 0)
.append(", ")
.append("SAFE", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_SAFE), 0)
.append(", ")
.append("CAUTION", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_CAUTION), 0)
.append(", ")
.append("DANGER", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_DANGER), 0)
Notification.Builder(context, channelId)
.setSmallIcon(R.drawable.ic_icon)
.setContentTitle("Hello World!")
.setContentText(ssb)
.setOngoing(true)
.setRequestPromotedOngoing(true)
Android 17 用の UWB ダウンリンク TDoA API
ダウンリンク到達時間差(DL-TDoA)測距により、デバイスは信号の相対到達時間を測定することで、複数のアンカーに対する相対位置を特定できます。
次のスニペットは、Ranging Manager を初期化し、デバイスの機能を検証して、DL-TDoA セッションを開始する方法を示しています。
Kotlin
class RangingApp {
fun initDlTdoa(context: Context) {
// Initialize the Ranging Manager
val rangingManager = context.getSystemService(RangingManager::class.java)
// Register for device capabilities
val capabilitiesCallback = object : RangingManager.RangingCapabilitiesCallback {
override fun onRangingCapabilities(capabilities: RangingCapabilities) {
// Make sure Dl-TDoA is supported before starting the session
if (capabilities.uwbCapabilities != null && capabilities.uwbCapabilities!!.isDlTdoaSupported) {
startDlTDoASession(context)
}
}
}
rangingManager.registerCapabilitiesCallback(Executors.newSingleThreadExecutor(), capabilitiesCallback)
}
fun startDlTDoASession(context: Context) {
// Initialize the Ranging Manager
val rangingManager = context.getSystemService(RangingManager::class.java)
// Create session and configure parameters
val executor = Executors.newSingleThreadExecutor()
val rangingSession = rangingManager.createRangingSession(executor, RangingSessionCallback())
val rangingRoundIndexes = byteArrayOf(0)
val config: ByteArray = byteArrayOf() // OOB config data
val params = DlTdoaRangingParams.createFromFiraConfigPacket(config, rangingRoundIndexes)
val rangingDevice = RangingDevice.Builder().build()
val rawTagDevice = RawRangingDevice.Builder()
.setRangingDevice(rangingDevice)
.setDlTdoaRangingParams(params)
.build()
val dtTagConfig = RawDtTagRangingConfig.Builder(rawTagDevice).build()
val preference = RangingPreference.Builder(DEVICE_ROLE_DT_TAG, dtTagConfig)
.setSessionConfig(SessionConfig.Builder().build())
.build()
// Start the ranging session
rangingSession.start(preference)
}
}
private class RangingSessionCallback : RangingSession.Callback {
override fun onDlTdoaResults(peer: RangingDevice, measurement: DlTdoaMeasurement) {
// Process measurement results here
}
}
Java
public class RangingApp {
public void initDlTdoa(Context context) {
// Initialize the Ranging Manager
RangingManager rangingManager = context.getSystemService(RangingManager.class);
// Register for device capabilities
RangingManager.CapabilitiesCallback capabilitiesCallback = new RangingManager.RangingCapabilitiesCallback() {
@Override
public void onRangingCapabilities(RangingCapabilities capabilities) {
// Make sure Dl-TDoA is supported before starting the session
if (capabilities.getUwbCapabilities() != null && capabilities.getUwbCapabilities().isDlTdoaSupported()) {
startDlTDoASession(context);
}
}
};
rangingManager.registerCapabilitiesCallback(Executors.newSingleThreadExecutor(), capabilitiesCallback);
}
public void startDlTDoASession(Context context) {
RangingManager rangingManager = context.getSystemService(RangingManager.class);
// Create session and configure parameters
Executor executor = Executors.newSingleThreadExecutor();
RangingSession rangingSession = rangingManager.createRangingSession(executor, new RangingSessionCallback());
byte[] rangingRoundIndexes = new byte[] {0};
byte[] config = new byte[0]; // OOB config data
DlTdoaRangingParams params = DlTdoaRangingParams.createFromFiraConfigPacket(config, rangingRoundIndexes);
RangingDevice rangingDevice = new RangingDevice.Builder().build();
RawRangingDevice rawTagDevice = new RawRangingDevice.Builder()
.setRangingDevice(rangingDevice)
.setDlTdoaRangingParams(params)
.build();
RawDtTagRangingConfig dtTagConfig = new RawDtTagRangingConfig.Builder(rawTagDevice).build();
RangingPreference preference = new RangingPreference.Builder(DEVICE_ROLE_DT_TAG, dtTagConfig)
.setSessionConfig(new SessionConfig.Builder().build())
.build();
// Start the ranging session
rangingSession.start(preference);
}
private static class RangingSessionCallback implements RangingSession.Callback {
@Override
public void onDlTdoaResults(RangingDevice peer, DlTdoaMeasurement measurement) {
// Process measurement results here
}
}
}
アウトオブバンド(OOB)構成
次のスニペットは、Wi-Fi と BLE の DL-TDoA OOB 構成データの例を示しています。
Java
// Wifi Configuration
byte[] wifiConfig = {
(byte) 0xDD, (byte) 0x2D, (byte) 0x5A, (byte) 0x18, (byte) 0xFF, // Header
(byte) 0x5F, (byte) 0x19, // FiRa Sub-Element
(byte) 0x02, (byte) 0x00, // Profile ID
(byte) 0x06, (byte) 0x02, (byte) 0x20, (byte) 0x08, // MAC Address
(byte) 0x14, (byte) 0x01, (byte) 0x0C, // Preamble Index
(byte) 0x27, (byte) 0x02, (byte) 0x08, (byte) 0x07, // Vendor ID
(byte) 0x28, (byte) 0x06, (byte) 0xCA, (byte) 0xC8, (byte) 0xA6, (byte) 0xF7, (byte) 0x6F, (byte) 0x08, // Static STS IV
(byte) 0x08, (byte) 0x02, (byte) 0x60, (byte) 0x09, // Slot Duration
(byte) 0x1B, (byte) 0x01, (byte) 0x0A, // Slots per RR
(byte) 0x09, (byte) 0x04, (byte) 0xE8, (byte) 0x03, (byte) 0x00, (byte) 0x00, // Duration
(byte) 0x9F, (byte) 0x04, (byte) 0x67, (byte) 0x45, (byte) 0x23, (byte) 0x01 // Session ID
};
// BLE Configuration
byte[] bleConfig = {
(byte) 0x2D, (byte) 0x16, (byte) 0xF4, (byte) 0xFF, // Header
(byte) 0x5F, (byte) 0x19, // FiRa Sub-Element
(byte) 0x02, (byte) 0x00, // Profile ID
(byte) 0x06, (byte) 0x02, (byte) 0x20, (byte) 0x08, // MAC Address
(byte) 0x14, (byte) 0x01, (byte) 0x0C, // Preamble Index
(byte) 0x27, (byte) 0x02, (byte) 0x08, (byte) 0x07, // Vendor ID
(byte) 0x28, (byte) 0x06, (byte) 0xCA, (byte) 0xC8, (byte) 0xA6, (byte) 0xF7, (byte) 0x6F, (byte) 0x08, // Static STS IV
(byte) 0x08, (byte) 0x02, (byte) 0x60, (byte) 0x09, // Slot Duration
(byte) 0x1B, (byte) 0x01, (byte) 0x0A, // Slots per RR
(byte) 0x09, (byte) 0x04, (byte) 0xE8, (byte) 0x03, (byte) 0x00, (byte) 0x00, // Duration
(byte) 0x9F, (byte) 0x04, (byte) 0x67, (byte) 0x45, (byte) 0x23, (byte) 0x01 // Session ID
};
OOB 構成がないために使用できない場合や、OOB 構成に含まれていないデフォルト値を変更する必要がある場合は、次のスニペットに示すように、DlTdoaRangingParams.Builder を使用してパラメータをビルドできます。これらのパラメータは DlTdoaRangingParams.createFromFiraConfigPacket() の代わりに使用できます。
Kotlin
val dlTdoaParams = DlTdoaRangingParams.Builder(1)
.setComplexChannel(UwbComplexChannel.Builder()
.setChannel(9).setPreambleIndex(10).build())
.setDeviceAddress(deviceAddress)
.setSessionKeyInfo(byteArrayOf(0x01, 0x02, 0x03, 0x04))
.setRangingIntervalMillis(240)
.setSlotDuration(UwbRangingParams.DURATION_2_MS)
.setSlotsPerRangingRound(20)
.setRangingRoundIndexes(byteArrayOf(0x01, 0x05))
.build()
Java
DlTdoaRangingParams dlTdoaParams = new DlTdoaRangingParams.Builder(1)
.setComplexChannel(new UwbComplexChannel.Builder()
.setChannel(9).setPreambleIndex(10).build())
.setDeviceAddress(deviceAddress)
.setSessionKeyInfo(new byte[]{0x01, 0x02, 0x03, 0x04})
.setRangingIntervalMillis(240)
.setSlotDuration(UwbRangingParams.DURATION_2_MS)
.setSlotsPerRangingRound(20)
.setRangingRoundIndexes(new byte[]{0x01, 0x05})
.build();