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
では、PendingIntent の代わりに OnAlarmListener を受け取る AlarmManager.setExactAndAllowWhileIdle の新しいバリアントが導入されています。この新しいコールバック ベースのメカニズムは、ソケット接続を維持するメッセージング アプリなど、定期的なタスクを実行するために継続的なウェイクロックに依存しているアプリに最適です。
プライバシー
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 now supports a hybrid APK signature scheme to future-proof your app's signing identity against the potential threat of attacks that make use of quantum computing. This feature introduces a new APK Signature Scheme, which lets you pair a classical signing key (such as RSA or EC) with a new post-quantum cryptography (PQC) algorithm (ML-DSA).
This hybrid approach ensures your app remains secure against future quantum attacks while maintaining full backward compatibility with older Android versions and devices that rely on classical signature verification.
Impact on developers
- Apps using Play App Signing: If you use Play App Signing, you can wait for Google Play to give you the option to upgrade a hybrid signature using a PQC key generated by Google Play, ensuring your app is protected without requiring manual key management.
- Apps using self-managed keys: Developers who manage their own signing keys can utilize updated Android build tools (like apksigner) to rotate to a hybrid identity, combining a PQC key with a new classical key. (You must create a new classical key, you cannot reuse the older one.)
接続
Android 17 では、デバイスとアプリの接続を強化するための次の機能が追加されています。
制約付き衛星ネットワーク
低帯域幅の衛星ネットワークでアプリが効果的に機能するように最適化を実装します。
ユーザー エクスペリエンスとシステム UI
Android 17 には、ユーザー エクスペリエンスを向上させるための次の変更が含まれています。
専用のアシスタント音量ストリーム
Android 17 では、アシスタント アプリ専用のアシスタント音量ストリームが導入され、
USAGE_ASSISTANTで再生できるようになりました。この変更により、アシスタントの音声が標準のメディア
ストリームから分離され、ユーザーは両方の音量を個別に制御できるようになります。これにより、アシスタントの応答の可聴性を維持しながらメディアの再生をミュートしたり、その逆の操作を行ったりできます。
新しい MODE_ASSISTANT_CONVERSATION 音声
モードにアクセスできるアシスタントアプリは、音量制御の一貫性をさらに向上させることができます。アシスタント アプリはこのモードを使用して、アクティブなアシスタント
セッションに関するヒントをシステムに提供できます。これにより、アクティブな USAGE_ASSISTANT
再生の外部または接続された Bluetooth 周辺機器を使用して、アシスタント ストリームを制御できます。
引き継ぎ
Handoff は、Android 17 に搭載される新しい機能と API です。アプリ デベロッパーは、この機能と API を統合して、ユーザーにクロスデバイスの継続性を提供できます。ユーザーは、1 つの Android デバイスでアプリ アクティビティを開始し、別の Android デバイスに移行できます。Handoff は、ユーザーのデバイスのバックグラウンドで実行され、受信デバイスのランチャーやタスクバーなどのさまざまなエントリ ポイントを介して、ユーザーの近くにある他のデバイスで使用可能なアクティビティを表示します。
受信デバイスに同じネイティブ Android アプリがインストールされていて使用可能な場合、アプリは Handoff を指定してそのアプリを起動できます。アプリからアプリへのフローでは、ユーザーは指定されたアクティビティにディープリンクされます。または、アプリからウェブへの Handoff をフォールバック オプションとして提供するか、URL Handoff で直接実装することもできます。
Handoff のサポートは、アクティビティごとに実装されます。Handoff を有効にするには、アクティビティの
メソッドを呼び出します。setHandoffEnabled()受信デバイスで再作成されたアクティビティが適切な状態を復元できるように、Handoff
とともに追加のデータを渡す必要がある場合があります。Implement the
onHandoffActivityDataRequested()
コールバックを実装して、
HandoffActivityDataオブジェクトを返します。これには、Handoff が受信デバイスで
アクティビティを処理して再作成する方法を指定する詳細情報が含まれます。
ライブアップデート - セマンティック カラー 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
Downlink Time Difference of Arrival (DL-TDoA) ranging lets a device determine its position relative to multiple anchors by measuring the relative arrival times of signals.
The following snippet demonstrates how to initialize the Ranging Manager, verify device capabilities, and start a DL-TDoA session:
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
}
}
}
Out-of-Band (OOB) Configurations
The following snippet provides an example of DL-TDoA OOB configuration data for Wi-Fi and BLE:
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
};
If you can't use an OOB configuration because it is missing, or if you need to
change default values that aren't in the OOB config, you can build parameters
with DlTdoaRangingParams.Builder as shown in the following snippet. You can use
these parameters in place of 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();