Android
 Computer >> コンピューター >  >> システム >> Android

AOSP 16 Bluetooth スキャナの説明:完全な技術ガイド

AOSP 16 Bluetooth スキャナの説明:完全な技術ガイド

ああ、Bluetooth。私たち皆が嫌がるテクノロジー。それは、いつもつながろうとしているのに... つながらない友人のようなものです。

何年もの間、Android 開発者は Bluetooth との劇的な、しばしば悲劇的なロマンスの中に閉じ込められてきました。私たちはその癖と格闘し、ただ働いてくれるよう懇願し、その謎の接続切断に静かに涙を流してきました。

でも、状況は良くなりつつあると言ったらどうなるでしょうか? Android 16 で、Bluetooth の神がついに私たちに微笑んでくれたと言ったらどうなるでしょうか?それは夢ではありません、友よ。それは AOSP 16 Bluetooth スキャナであり、私たちの疲れた開発者に新たな希望をもたらすために登場しました。

このハンドブックでは、私たちは旅に出ます。 AOSP 16 の新しい Bluetooth 機能の中心への旅。私たちは笑い、泣き(今回は喜びからだといいのですが)、これらの新しい力を永久に行使する方法を学びます。パッシブ スキャンの魔法、ボンド喪失の理由のドラマ、そして通常の手間をかけずにサービス UUID を取得できるまったくの便利さを探っていきます。

この壮大な物語が終わるまでに、次のことができるようになります。

  • 非常に効率的で、ほとんど心霊的な Bluetooth スキャナを構築します。

  • 経験豊富な探偵のように、接続の問題をデバッグします。

  • 新しく見つけた Bluetooth の習得で友人や同僚を驚かせましょう。

前提条件:

本題に入る前に、Android 開発と Kotlin の基本を理解しておくことをお勧めします。 2 台のデバイスを相互に通信させようとして、最終的にコンピュータを窓から投げ捨てたくなったことがあるなら、あなたには十分な資格があります。

お気に入りの飲み物を用意し、コーディング ケープを着て、Bluetooth の覚醒に備えましょう!

目次

<オル>
  • Android における Bluetooth の簡単な歴史

  • AOSP 16 の新機能:三銃士

  • 詳細 #1:パッシブ スキャン

  • BluetoothLeScanner について

  • 実践:初めてのパッシブ スキャナの構築

  • 詳細 #2:Bluetooth ボンド喪失の理由

  • 詳細 #3:広告からのサービス UUID

  • 高度なトピック:スキャン ゲームのレベルアップ

  • 現実世界の使用例:Bluetooth が実用化される場所

  • API バージョンのチェック:アプリをクラッシュさせない方法

  • テストとデバッグ:楽しい部分 (誰も言わなかった)

  • パフォーマンスとベスト プラクティス:良き Bluetooth 市民になる方法

  • 結論:未来は受動的です (それで問題ありません)

  • Bluetooth の簡単な歴史 (または:心配するのをやめて電波を愛する方法を学んだ方法)

    暗黒時代:クラシック Bluetooth

    当初はクラシック Bluetooth がありました。それは、騒々しく騒々しいパーティーのゲストをデジタル化したものでした。大量のデータ (お気に入りの曲をスピーカーに流すなど) を運ぶことができましたが、バッテリーを大量に消費していました。オーディオのストリーミングには最適でしたが、小規模で頻度の少ないデータ転送には最適でしょうか?まるで観葉植物に水をやるのに消防ホースを使うようなものだった。やりすぎで、正直言って少し面倒です。

    この時代の開発者は、BluetoothAdapter、BluetoothDevice、そして恐ろしい BluetoothSocket との格闘に日々を費やしていました。当時は非常に不確実性が高く、簡単な接続に数秒かかることもあれば、コーヒーを淹れに行くこともできたとしましょう。そしてバッテリーの消耗は?ユーザーは、鉛の風船よりも早く携帯電話の電力レベルが急激に低下するのを目の当たりにするでしょう。

    ルネッサンス:Bluetooth Low Energy (BLE) の登場

    そして、Android 4.3 では、Bluetooth Low Energy (BLE) という新しいヒーローが登場しました。これはあなたのお父さんの Bluetooth ではありませんでした。 BLE は洗練されており、効率的で、神秘的でした。これは、短期間のデータのバースト用に設計されており、一気飲みするのではなく、高級ワインのように電力を飲み込むように設計されています。

    BLE はまさにクールな存在でした。それは、心拍数モニター、スマート ウォッチ、そして 1 個のコイン型電池で何か月も動作できる 100 万個の IoT デバイスなど、まったく新しい可能性の世界を私たちにもたらしてくれました。それはゲームチェンジャーでした。

    しかし、大きな力とともに、大きな複雑さも生まれました。私たちは、GATT、GAP、サービス、特性について全く新しい言語を学ぶ必要がありました。それは、単純な脚本の執筆から本格的なオペラの作曲に移行するようなものでした。可能性は膨大でしたが、学習曲線は急峻でした。

    問題児:スキャン

    そしてスキャンがありました。これらの新しい電力を吸い出すデバイスを見つける行為。 BLE の初期の頃、スキャンはまだ西部開拓時代のようなものでした。それは活発で騒々しいプロセスでした。あなたの携帯電話は虚空に向かって「誰かいる?」と叫び、返事を待ちます。これは機能しましたが、特にアプリが長時間スキャンする必要がある場合には、依然としてかなりの電力消費でした。

    これは開発者の典型的なジレンマでした。デバイスを見つける必要があるが、ランチタイムまでにユーザーの携帯電話が故障する原因にはなりたくないのです。私たちは何年もの間、発見の必要性とバッテリー寿命の切実な嘆願とのバランスをとりながら、この綱渡りをしてきました。

    これが AOSP 16 が生まれた世界です。世界はより良いスキャン方法を求めています。ヒーローを受け入れる準備ができた世界。そしてそのヒーローは、パッシブ スキャンです。しかし、それについてはもう少し後で説明します...

    AOSP 16 の新機能は何ですか? (ネタバレ:実にクールです)

    さて、良いことにいきましょう。 AOSP 16 で Android チームはどのようなピカピカの新しいおもちゃを提供してくれましたか?かなりの数であることがわかりました。ただし、プレゼントの包装を開ける前に、新しい配達スケジュールについて話しましょう。それさえも今は少し変わっているからです。

    2 つのリリースの物語

    衝撃的な展開で、Android は 2025 年に 2 つのメジャー API リリースを発表することを決定しました。まず、メイン イベントである Android 16 (美味しいペストリーを好まない人がいないためコードネーム「バクラバ」) が第 2 四半期に登場しました。これは、あなたが知り、愛してきた (または恐れていた) 行動の変化をすべて盛り込んだ、伝統的なビッグバン リリースです。

    しかし、第 4 四半期には、サプライズの第 2 幕がやってきます。それはマイナー リリースであり、そこで私たちの新しい Bluetooth グッズが堂々と登場します。このリリースはすべて新しい機能と API に関するものであり、アプリを破壊するような恐ろしい変更はありません。これは、支払いを済ませた後に無料のデザートをもらうようなものです。

    Bluetooth の三銃士

    では、この第 4 四半期リリースは Bluetooth パーティに何をもたらしたのでしょうか?質問してよかったです。 Bluetooth の問題から私たちを救ってくれる 3 人の新しいヒーローが登場しました。私は彼らを... 三銃士と呼んでいます。

    機能

    要点

    注意する必要がある理由

    パッシブ スキャン

    Bluetooth デバイスに大声で話しかけることなく、Bluetooth デバイスの存在を聞く機能。

    これで、あなたのアプリは静かでバッテリー節約の忍者になることができます。

    保証金喪失の理由

    最後に、Bluetooth 接続が切断される理由について少しまとめます。

    推測ゲームをやめて、実際に接続の問題をデバッグできます。

    広告からのサービス UUID

    デバイスの重要な統計情報をその広告から直接取得します。

    これは Bluetooth デバイスのスピードデートのようなものです。より高速で効率的な接続。

    これらは単なる小さな調整ではありません。これらは生活の質の向上であり、Bluetooth 対応アプリの構築およびデバッグ方法を根本的に変えることになります。まるで Android チームが私たちの助けを求める声に本当に耳を傾けてくれたかのようです。 (わかっています、私もショックを受けています。)

    次のいくつかのセクションでは、これらの各新機能について詳しく説明します。コードを詳しく調べ、ユースケースを調査し、その力を活用する方法を学びます。それで、最初の銃士に会う準備をしてください。パッシブ スキャンとして知られる強力で静かなタイプです。

    詳細 #1:パッシブ スキャン

    あなたが図書館にいると想像してください。あなたは友達を探していますが、友達がどこにいるのかわかりません。 2 つのオプションがあります:

    • アクティブ スキャン: あなたは図書館の真ん中に立って、「ねえ、スティーブ!ここにいるの?」と叫びます。これは効果的ですが、騒音が大きく、混乱を招くため、図書館員 (この例えでは、ユーザーのバッテリーに相当します) によって追い出される可能性があります。

    • パッシブ スキャン: あなたは静かに図書館を歩き回り、友人の独特のゼイゼイするような笑い声を聞きます。あなたは何も言いません。ただ聞いてください。これはステルスかつ効率的で、ソーシャル (または実際の) バッテリーを消耗しません。

    何年もの間、Android の Bluetooth スキャンは図書館で大騒ぎされてきました。しかし、AOSP 16 を使用すると、私たちはついに静かな聞き手になることができます。これがパッシブ スキャンの魔法です。

    アクティブ vs. パッシブ:技術対決

    BLE の世界では、デバイスは「アドバタイズメント」と呼ばれる小さな情報パケットを送信します。それは彼らのやり方で、「おい、私はここにいる、そしてこれが私の仕事だ!」

    • アクティブ スキャン: 電話機がアクティブ スキャンを実行すると、アドバタイズメントを受信し、SCAN_REQ (スキャン リクエスト) を送り返します。要は「もっと教えて!」ってことです。その後、周辺機器は追加情報を含む SCAN_RSP (スキャン応答) で応答します。

    • パッシブ スキャン: パッシブ スキャンを使用すると、携帯電話で広告が聞こえます...それだけです。何も送り返されません。最初の広告に注目して次に進みます。これは一方通行の会話です。

    なぜパッシブにするのか?沈黙の力

    では、なぜこれがそれほど大きな問題なのでしょうか? 2 つの言葉:消費電力。携帯電話の無線が何か (SCAN_REQ など) を送信する必要があるたびに、エネルギーが消費されます。アプリが常にデバイスをスキャンしている場合、それらの小さな送信が積み重なり、ユーザーのバッテリーが代償を支払います。

    パッシブ スキャンに切り替えることで、ラジオにただ聞くように指示することになります。話すことはなく、ただ聞くだけです。これにより、スキャンに必要な電力が大幅に削減され、長期間にわたって近くのデバイスを監視する必要があるアプリに最適なソリューションになります。

    コード:Bluetooth 忍者になる方法

    では、この新たに発見されたステルス モードを実装するにはどうすればよいでしょうか?驚くほど簡単です。すべては、スキャンを開始するときに使用する ScanSettings によって決まります。

    以前は、次のようなことを行っていたかもしれません:

    val settings = ScanSettings.Builder()
     .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
     .build()
    

    AOSP 16 では、新しいオプションが追加されました。パッシブ スキャンを有効にするには、スキャン タイプを設定するだけです。

    // This is the magic line!
    .setScanMode(ScanSettings.SCAN_TYPE_PASSIVE)
    

    待ってください、そんなはずはありません。ドキュメントには、SCAN_TYPE_PASSIVE はスキャン モードではなくスキャン タイプであると記載されています。そして、あなたは正しいです!ごめんなさい、ちょっと興奮しすぎてしまいました。これを行う正しい方法は、スキャン モードをパッシブに設定することです。もう一度試してみましょう。

    val settings = ScanSettings.Builder()
     // The actual magic line!
     .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC) // This is the closest to passive
     .build()
    

    ちょっと待って、それも正しくありません。どうやら配線を混線してしまったようだ。公式の巻物を調べてみましょう...ああ、これです! Android 16 QPR2 では、ScanSettings.Builder に新しいメソッドが追加されました。これは setScanMode ではなく、まったく新しい設定です。

    これをきっぱり正しくしましょう。パッシブ スキャンを有効にする正しい方法は次のとおりです。

    // Available in Android 16 QPR2 and later
    val settings = ScanSettings.Builder()
     // This is the REAL magic line, I promise!
     .setScanType(ScanSettings.SCAN_TYPE_PASSIVE) 
     .build()
    

    そして、それができました。この 1 行で、アプリは騒々しくバッテリーを食い荒らす観光客から、静かで効率的な Bluetooth 忍者に変わりました。ユーザーのバッテリーはあなたに感謝するでしょう。

    もちろん、トレードオフがあります。 SCAN_REQ を送信していないため、SCAN_RSP から追加のデータを取得することはできません。ただし、多くのユースケースでは、最初のアドバタイズメントだけで十分です。節電効果はそれだけの価値があります。

    サイレント スキャンの技術を習得したので、次のパズルのピース、BluetoothLeScanner 自体を理解することに進みましょう。

    BluetoothLeScanner (番組の主役) を理解する

    Bluetooth スキャンの技術を真に習得するには、まず主要な武器である BluetoothLeScanner を理解する必要があります。ゴーストバスターズの PKE メーターと考えてください。これは、私たちの周囲に漂う目に見えないエネルギー (私たちの場合は BLE 広告) を検出するために使用するツールです。しかし、このゴーストハンティング ガジェットは実際にどのように機能するのでしょうか?

    アーキテクチャ:カーテンの裏側を覗く

    大まかに言えば、プロセスは非常に簡単です。あなたのアプリは、独自の小さな世界で快適に暮らしており、いくつかの BLE デバイスを見つけようと決定します。 BluetoothLeScanner のインスタンスを取得し、「おい、何かを探してくれ」と言います。

    内部では多くのことが起こっています。 BluetoothLeScanner は、Android Bluetooth スタック (コード名は「Fluoride」で、歯科医がとても誇りに思っているように聞こえます) と通信します。次に、スタックはデバイスの Bluetooth コントローラー (電波の送受信を行う実際のハードウェア) と通信します。これは、「見た目よりも複雑である」という典型的なケースです。

    アルファベットのスープ:GATT、GAP、そしてその仲間たち

    BLE の世界に足を踏み入れると、すぐに大量の頭字語に遭遇することになります。慌てないで!彼らは見た目ほど怖くない。理解する必要がある最も重要な 2 つは、GAP と GATT です。

    • GAP (汎用アクセス プロファイル): これはすべて、デバイスが相互に検出して接続する方法に関するものです。 GAP をナイトクラブの用心棒と考えてください。誰が誰と話すことができるかはそれによって決まります。広告 (「ここにいます!」と叫ぶデバイス) とスキャン (それらの叫びをリッスンするアプリ) を管理します。当社の BluetoothLeScanner は、GAP の分野で重要な役割を果たしています。

    • GATT (汎用属性プロファイル): 2 つのデバイスが接続されると、GATT が引き継ぎます。データを交換する方法を定義します。 GATT をナイトクラブ内で行われる実際の会話と考えてください。重要なのはサービス、特性、記述子です。デバイスには、「心拍数測定特性」を含む「心拍数サービス」が搭載されている場合があります。アプリはこれらの特性から読み取りまたは書き込みを行って、必要なデータを取得します。

    スキャンという目的のため、私たちは主に GAP の世界に住んでいます。私たちはクラブの外に立って、興味深い広告を聞いているのです。

    スキャンのライフサイクル:3 幕からなる劇的な劇

    Bluetooth スキャンの寿命は、シンプルでありながらエレガントなドラマです。

    • 第一幕: 準備。アプリがスキャンの時期を判断します。 BluetoothLeScanner を取得し、一連の ScanFilters (特定のデバイスのみを検索するため) と ScanSettings (新しいパッシブ モードなどのスキャン方法を定義するため) を作成し、ScanCallback を定義します。

    • 第 2 幕: スキャン。アプリは startScan() を呼び出します。 Bluetooth ラジオが起動し、フィルターに一致する広告を聞きます。見つかった場合は、ScanCallback の onScanResult() メソッドを介してアプリに報告します。

    • 第 3 幕: 終わり。アプリが十分な情報を取得したとき (または、さらに重要なことに、探しているものが見つかったとき)、stopScan() を呼び出します。ラジオの電源が切れ、すべてが再び静かになります。完了したら必ずスキャンを停止することが重要です。不正スキャンは、ユーザーからの「バッテリーが 1 時間以内に切れる」という苦情の最大の原因です。

    それが BluetoothLeScanner です。これは、BLE 発見の世界への入り口です。これは強力で複雑ですが、新しい Android リリースがリリースされるたびに、学習が進み、よりスマートかつ効率的になってきています。ツールを理解したので、実際に手を動かして最初のパッシブ スキャナーを構築してみましょう!

    実践:初めてのパッシブ スキャナーの構築

    理論は素晴らしいですが、正直に言うと、私たちは開発者です。私たちは実践することによって(またはスタック オーバーフローからコピー&ペーストすることによって)学びます。さあ、腕まくりして Android Studio を起動して、何かを構築しましょう。新しく発見したパッシブ スキャン機能を使用して近くの BLE デバイスを見つけるシンプルなアプリを作成します。

    ステップ 1:許可の審問

    Kotlin を 1 行書く前に、Android のパーミッションの神をなだめる必要があります。これは神聖であり、しばしばイライラする儀式です。 Bluetooth スキャンのルールは、長年にわたって少しずつ変化してきました。

    まず、09 を開きます。 次の内容を追加します。

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <!-- For Android 12 (API 31) and above -->
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <!-- For older versions, you needed location permissions -->
    <!-- You might still need this if you support older devices -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    

    上で宣言した権限を見ると、Android の Bluetooth 権限モデルの進化がリアルタイムで進行していることがわかります。

    最初の 2 つの権限、1821 、古参の衛兵です。これらは Android の初期の頃から存在しており、基本的な Bluetooth 機能とデバイスを検出する機能を提供します。次に、31 があります。 これは Android 12 (API 31) で導入され、プライバシーに対する Google の考え方に大きな変化をもたらしました。

    はい、それはわかりますね。古き良き時代(Android 12 以前)、Google は Bluetooth デバイスを見つけることはユーザーの正確な位置を知ることと基本的に同じであると判断しました。結局のところ、どの Bluetooth ビーコンが近くにあるかを確認できれば、自分の位置を三角測量することができます。しかし、ヘッドフォンを見つけるためだけに場所を尋ねるのは少し不気味でもありました。これにより、ユーザーは正確な位置情報を要求する単純な Bluetooth スキャナ アプリを見て、当然のことながら疑いを抱くという厄介な状況が発生しました。

    ありがたいことに、Android 12 では 45 が導入されました。 許可するほうがはるかに賢明です。この権限により、アプリは位置情報へのアクセスを要求することなく Bluetooth デバイスをスキャンできるようになり、ユーザーの観点からすると非常に理にかなっています。実行時にこの権限をリクエストする必要はありますが、少なくとも、単純なガジェット ファインダー アプリがユーザーの居住地を知りたい理由をユーザーに説明する必要はありません。

    ただし、位置情報アクセスに関する最後の 2 つの権限に注意してください。それらは古いシステムの名残です。 Android 11 以下を実行する古いデバイスをサポートする必要があるアプリを構築している場合は、下位互換性のためにマニフェストにこれらの位置情報のアクセス許可を保持する必要があります。最新のデバイスでは、59 許可だけで機能します。

    ステップ 2:コードが目覚める

    さて、楽しい部分に移りましょう。ここでは、アクティビティまたはフラグメントにパッシブ スキャナを実装する方法を詳しく説明します。

    スキャナを入手

    まず、BluetoothLeScanner のインスタンスを取得する必要があります。

    private val bluetoothAdapter: BluetoothAdapter? by lazy {
     val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
     bluetoothManager.adapter
    }
    private val bleScanner: BluetoothLeScanner? by lazy {
     bluetoothAdapter?.bluetoothLeScanner
    }
    

    上記のコードで何が起こっているのかを詳しく見てみましょう。 Kotlin の 64 を使用しています。 委任。これは、「実際に必要になるまでこのオブジェクトを作成しないでください」という派手な方法です。 Bluetooth アダプタの取得にはシステム コールが含まれるため、これは良い習慣であり、実際に使用しなければその作業を行う意味がありません。

    まず、75 を取得します。 システムサービスから。 84 について考えてみましょう デバイス上のすべての Bluetooth へのゲートキーパーとして機能します。このマネージャーから 97 を取得します。 、デバイスの物理 Bluetooth ハードウェアを表します。 null 可能 (107) として宣言していることに注意してください。 ) なぜなら、信じられないかもしれませんが、すべての Android デバイスが Bluetooth を備えているわけではないからです。一部のタブレットや無名のデバイスにはハードウェアが搭載されていない可能性があるため、その可能性に備えておく必要があります。

    アダプターを入手したら、110 を要求できます。 。これは、スキャンを実行するために使用する実際のオブジェクトです。繰り返しますが、安全な呼び出し演算子 (124) を使用しています。 ) アダプターがヌル (Bluetooth ハードウェアなし) の場合、そこからスキャナーを取得することは間違いなくできないためです。この防御的なプログラミングは偏執的に見えるかもしれませんが、これが、不可解にクラッシュするアプリと、エッジ ケースを適切に処理するアプリを区別するものです。

    コールバックを定義する

    ここで魔法が起こります。 ScanCallback は、スキャン結果をリッスンするオブジェクトです。 onScanResult と onScanFailed の 2 つのメソッドをオーバーライドする必要があります。

    private val scanCallback = object : ScanCallback() {
     override fun onScanResult(callbackType: Int, result: ScanResult) {
     // We found a device! 
     // The 'result' object contains the device, RSSI, and advertisement data.
     Log.d("BleScanner", "Found device: ${result.device.address}, RSSI: ${result.rssi}")
     }
     override fun onScanFailed(errorCode: Int) {
     // This is the universe's way of telling you to take a break.
     // Or that something went horribly wrong.
     Log.e("BleScanner", "Scan failed with error code: $errorCode")
     }
    }
    

    134 上で定義したのは、Bluetooth の世界におけるアプリの耳です。スキャナーはデバイスを検出すると、情報をどこかに保存するだけでなく、このコールバック オブジェクトを通じてアプリに積極的にコールバックします。これは古典的なイベント駆動型プログラミングであり、Android がメイン スレッドをブロックせずにアプリの応答性を維持する方法です。

    146 このメソッドは、スキャナーがフィルターに一致するデバイス (フィルターを使用していない場合は任意のデバイス) を検出するたびに呼び出されます。 158 パラメータは情報の宝庫です。 167 が含まれています オブジェクト (デバイスの MAC アドレスと名前が含まれます)、RSSI 値 (受信信号強度インジケーター - 基本的にデバイスがどれだけ近いかを示し、数値が大きいほど近いことを意味します)、およびデバイスがブロードキャストしている生のアドバタイズメント データです。

    上記の簡単な例では、MAC アドレスと RSSI を記録しているだけですが、実際のアプリでは、おそらく UI を更新したり、デバイスをリストに追加したり、接続をトリガーしたりする必要があるでしょう。

    173 パラメータは理由を示します このコールバックがトリガーされました。 186 の可能性があります (デフォルト、「見つかったすべてのデバイスがここにあります」という意味)、199 (このデバイスを初めて見たとき)、または 203 (このデバイスをしばらく見ていないので、おそらく去ってしまったでしょう)。これらのタイプについては、上級セクションでさらに詳しく説明します。

    次に 217 です。 、誰もが決して呼び出されないことを望んでいますが、絶対に処理する必要があるメソッドです。これは、スキャンで致命的な問題が発生した場合に呼び出されます。スキャン中に Bluetooth アダプターがオフになったか、アプリに適切な権限がないか、あるいは Bluetooth コントローラーの調子が悪かっただけかもしれません。 226 何が問題だったのかについてのヒントが得られます。常にこれを記録し、ユーザーにメッセージを表示するか、遅延後にスキャンを再開するなどして、適切に処理する必要があります。

    スキャンを設定する

    次に、ScanSettings を作成します。ここで Android に、受動的でバッテリーを節約する忍者になりたいと伝えます。

    val scanSettings = ScanSettings.Builder()
     .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) // Let's be nice to the battery
     .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
     .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
     .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) // Report each ad once
     .setReportDelay(0L) // Report immediately
     // And here's the star of the show!
     .setScanType(ScanSettings.SCAN_TYPE_PASSIVE)
     .build()
    

    234 上で構築しているオブジェクトは、Bluetooth スキャナーの詳細な取扱説明書のようなものです。各メソッド呼び出しはスキャンの動作を正確に微調整します。これらの設定を正しく行うことが、バッテリーに優しいアプリと数時間以内にアンインストールされるアプリの違いとなります。

    それぞれの設定を見ていきましょう。まず、240 これは、連続的ではなく間隔を置いてスキャンすることを意味します。これは、即時の結果を必要とせず、バッテリー寿命を節約したいほとんどのユースケースに最適です。スキャナーは起動し、少しスキャンし、スリープし、これを繰り返します。これは Bluetooth に相当するもので、仮眠をとるのと同じです。

    次に、252 これは、スキャナーが一致するデバイスを見つけるたびに通知を受け取りたいことを意味します。これはデフォルトの動作であり、ほとんどの場合に使用されます。前に述べたように、262 を使用することもできます。 または 272 より高度な存在検出を実現します。

    282 この設定は、ハードウェアがどの程度積極的にデバイスをフィルターと照合しようとするかを制御します。 290 308 は、「100% 確実ではない場合でも、レポートはすぐに一致します」を意味します。一方、308 「本当に確信が持てるまで報告する前に待ってください」という意味です。アグレッシブ モードでは、より高速な結果が得られますが、場合によっては誤検知が発生する可能性があります。

    次に、310 があります。 これは、デバイスからの広告を 1 つだけ確認した後、デバイスを報告するようにスキャナーに指示します。代替案は 325 です。 、レポートする前に複数のアドバタイズメントを待機します。 1 つのアドバタイズメントを使用すると、発見が早くなり、いくつかのアドバタイズメントを待つことで、ちょうど通り過ぎるデバイスによる誤検知が減少します。

    338 設定が重要です。 348 の遅延 「結果をすぐに報告する」という意味です。これをたとえば 351 に設定すると、 ミリ秒ごとに、スキャナは結果をまとめて 5 秒ごとに配信します。バッチ処理は、バックグラウンド スキャン (高度なセクションで説明したように) には最適ですが、ユーザーがアクティブに待機しているフォアグラウンド スキャンの場合は、即時レポートが必要です。

    そして最後に、ショーの主役:362 。これは、スキャナをサイレント リスナーに変換する Android 16 QPR2 の新しい API です。受信するすべてのデバイスにスキャン リクエストを積極的に送信するのではなく、空中に漂う広告を受信するだけです。この 1 つの設定により、スキャン中のアプリのバッテリー消費を大幅に削減できます。これは私たちが待ち望んでいた機能であり、素晴らしいものです。

    スキャンの開始と停止

    最後に、スキャンを開始および停止する関数が必要です。必ずスキャンを停止してください。スキャンを忘れるとバッテリーを消耗してしまいます。

    private fun startBleScan() {
     // Don't forget to request permissions first!
     if (bleScanner != null) {
     // You can add ScanFilters here to search for specific devices
     val scanFilters: List<ScanFilter> = listOf() 
     bleScanner.startScan(scanFilters, scanSettings, scanCallback)
     Log.d("BleScanner", "Scan started.")
     } else {
     Log.e("BleScanner", "Bluetooth is not available.")
     }
    }
    private fun stopBleScan() {
     if (bleScanner != null) {
     bleScanner.stopScan(scanCallback)
     Log.d("BleScanner", "Scan stopped.")
     }
    }
    

    上記の 2 つの機能は Bluetooth スキャナのオン/オフ スイッチであり、重要な割に一見シンプルです。それぞれで何が起こっているのかを分析してみましょう。

    372 で 、まず 381 かどうかを確認します。 はヌルではありません。これは私たちのセーフティ ネットです。デバイスに Bluetooth ハードウェアが搭載されていない場合、または Bluetooth が無効になっている場合、スキャナーは null になり、null オブジェクトのメソッドを呼び出そうとすることでクラッシュすることは避けたいのです。スキャナーが存在する場合は、395 を呼び出します。 3 つのパラメータ:408 のリスト オブジェクト、私たちが慎重に作成した 417424 前に定義しました。

    430 この例では list は現在空です。これは、「すべての BLE デバイスを検索する」ことを意味します。実際のアプリでは、通常、ここにフィルタを追加して検索を絞り込みます。

    たとえば、心拍数モニターでのみ動作するアプリを構築している場合は、心拍数サービス UUID をアドバタイズするデバイスのみに一致するフィルターを作成します。これはパフォーマンスとバッテリー寿命の両方にとって非常に重要です。フィットネス トラッカーだけを気にしているのに、ランダムな Bluetooth 歯ブラシごとにアプリを起動する必要はありません。

    441 メソッドによりスキャン プロセスが開始されます。この時点から、Bluetooth 無線はアクティブに (またはこの場合は受動的に) アドバタイズメントをリッスンし、455 が受信されます。 結果の受信が開始されます。これは非同期操作です。つまり、コードはここで結果を待ってブロックされず、実行を継続し、結果が利用可能になるたびにコールバックを介して取得されます。

    次に、460 について話しましょう。 、これはあなたが作成する最も重要な関数かもしれません。 471 を呼び出すとき コールバックでは、Bluetooth 無線に「はい、これで作業は終わりました。また寝てください」と伝えていることになります。これにより、スキャン プロセスが直ちに停止され、リソースが解放されます。

    理解しておくべき重要な点は、これを呼び出さない場合、スキャンは無制限に実行され続け、食べ放題の血液銀行の吸血鬼のようにユーザーのバッテリーを消耗することです。これが、私たちがこれを強調する理由です。忘れられた 484 通話は、Bluetooth アプリのバッテリー消耗に関する苦情の最も一般的な原因の 1 つです。

    同じ 496 を渡していることに注意してください。 オブジェクトを 500 に設定します 512 で使用したもの 。これは、Android がどのスキャンを停止すべきかを知る方法です。理論的には、異なるコールバックで複数のスキャンを実行することができます (ただし、それが良い考えであることはほとんどありません)。同じコールバック参照を使用して、開始したのと同じスキャンを停止していることを常に確認してください。

    すべてをまとめる

    アクティビティにドロップできる完全な例を次に示します。実行時の権限を処理することを忘れないでください。

    // In your Activity class
    class MainActivity : AppCompatActivity() {
     // ... (lazy properties for adapter and scanner from above)
     override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
     // ... your UI setup ...
     // Example: Start scan on button click
     val startButton = findViewById<Button>(R.id.startButton)
     startButton.setOnClickListener {
     // You MUST request permissions before calling this!
     startBleScan()
     }
     // Example: Stop scan on another button click
     val stopButton = findViewById<Button>(R.id.stopButton)
     stopButton.setOnClickListener {
     stopBleScan()
     }
     }
     // ... (scanCallback, startBleScan, stopBleScan functions from above)
     override fun onPause() {
     super.onPause()
     // Always stop scanning when the activity is not visible.
     stopBleScan()
     }
    }
    

    上記の完全な例は、実際のアクティビティですべての部分がどのように組み合わされるかを示しています。これは、実際に実行できる、最小限だが機能的な Bluetooth スキャナーです。ここで使用している重要なパターンをいくつか取り上げてみましょう。

    まず、ボタンのクリックを通じてスキャンのライフサイクルをユーザーのアクションにどのように結び付けているかに注目してください。これは一般的なパターンです。ユーザーは明示的にスキャンを開始および停止し、アプリが Bluetooth を使用するタイミングを制御できるようにします。ユーザーが希望した場合にのみスキャンが実行されるため、これは UX が良く、バッテリー寿命にも優れています。

    しかし、ここが本当に重要な部分です:520 オーバーライドします。これは重要なセーフティネットです。アクティビティがバックグラウンドになると (ユーザーがホーム ボタンを押したか、別のアプリに切り替えた可能性があります)、537 が呼び出されると、すぐにスキャンが停止されます。ユーザーがアプリを表示できない場合、スキャン結果は必要なく、バッテリーを消耗する理由もないため、これは不可欠です。このパターンにより、ユーザーが「停止」ボタンを押し忘れた場合でも、スキャンがバックグラウンドで永久に実行されることはありません。

    543 はどうだろう」と疑問に思われるかもしれません。 ?ユーザーが戻ってきたらスキャンを再開すべきではないでしょうか?」それは設計上の決定です。一部のアプリでは、557 でスキャンを自動的に再開する必要がある場合があります。 。他の場合には、ユーザーに明示的に「開始」をもう一度押してもらいたい場合もあります。それはユースケースによって異なります。ユーザーが積極的に検索しているデバイス検索アプリの場合、自動再開は理にかなっています。バックグラウンドで実行される監視アプリの場合は、より明示的な制御が必要になる場合があります。

    この例で示していない重要なことの 1 つは、実行時の権限の処理です。マニフェストで宣言した権限を覚えていますか? Android 6.0 以降では、単に宣言するだけではなく、実行時にユーザーに実際にリクエストする必要があります。 567 を呼び出す前に 必要な権限があるかどうかを確認し、ない場合は 572 を使用してリクエストする必要があります。 。適切な権限を持たずにスキャンを開始しようとすると、静かに (Android のバージョンによっては大声で) 失敗し、なぜ何も機能しないのか疑問に思うことになります。

    そして、それができました!最初の AOSP 16 パッシブ Bluetooth スキャナーを構築しました。無駄がなく、意地悪で、信じられないほど電力効率が優れています。スキャナーは BLE アドバタイズメントをサイレントにリッスンし、コールバックを通じて報告し、不要な場合には適切に停止します。

    さて、次のトピック「問題が発生した場合の対処法」に移りましょう。解散について話す時が来ました... Bluetooth 接続の解散について話します。

    詳細 #2:Bluetooth ボンドが失われる理由

    ああ、Bluetooth 接続です。それは美しく神聖なものです。これは、友情のブレスレットを交換するのとデジタル版に相当します。携帯電話とヘッドフォンを結び付けると、長期にわたる信頼関係が築かれます。秘密キーを共有し、お互いを記憶し、自動的に接続することを約束するため、毎回ペアリングする手間が省けます。それは美しいロマンスです。

    そうならないまでは。

    ある日突然、彼らはお互いを忘れてしまいます。接続が切れてしまいました。信頼は壊れてしまったのです。そしてあなたのアプリは真ん中に残され、何が問題だったのか見当もつかず、セラピストを演じようとしています。あなたは幽霊になってしまったのです。そして今まで、Android は役に立ちませんでした。結合状態が BOND_NONE になったという通知が表示されますが、それだけです。説明はありません。閉鎖はありません。接続が失敗したときの冷たく硬い沈黙だけです。

    ついに決着がつきました!

    しかし、Android チームの友人たちは、AOSP 16 で私たちに終結という贈り物を与えてくれたので、明らかにいくつかのつらい別れを経験しました。 BluetoothDevice.EXTRA_BOND_LOSS_REASON の紹介。これは ACTION_BOND_STATE_CHANGED ブロードキャストに付属する新しい追加機能で、絆が失われた理由を説明します。それは、実際に何が起こったのかを説明する別れのメッセージを受け取るようなものです!

    結合が壊れたときに、特定の理由コードを取得できるようになりました。これらは典型的な別れの言い訳だと考えてください。ただし、Bluetooth の場合は次のとおりです。

    理由コード (例)

    実際の意味

    BOND_LOSS_REASON_BREDR_AUTH_FAILURE

    ボンディング損失の理由が BREDR 認証の失敗であることを示します。

    BOND_LOSS_REASON_BREDR_INCOMING_PAIRING

    ボンド損失の理由が BREDR ペアリングの失敗であることを示します。

    BOND_LOSS_REASON_LE_ENCRYPT_FAILURE

    Indicates that the reason for the bond loss is LE encryption failure.

    BOND_LOSS_REASON_LE_INCOMING_PAIRING

    Indicates that the reason for the bond loss is LE pairing failure.

    The Code:Playing Detective

    So, how do we get this juicy gossip? We need to set up a BroadcastReceiver to listen for bond state changes.

    // Create a BroadcastReceiver to listen for bond state changes
    private val bondStateReceiver = object : BroadcastReceiver() {
     override fun onReceive(context: Context, intent: Intent) {
     if (intent.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
     val device: BluetoothDevice? = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
     val bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR)
     val previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.ERROR)
     // Check if we went from bonded to not bonded
     if (bondState == BluetoothDevice.BOND_NONE && previousBondState == BluetoothDevice.BOND_BONDED) {
     Log.d("BondBreakup", "We got dumped by ${device?.address}!")
     // Now, let's find out why...
     val reason = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_LOSS_REASON, -1)
     when (reason) {
     // Note: The actual constant values are in the Android SDK
     BluetoothDevice.BOND_LOSS_REASON_REMOTE_DEVICE_REMOVED -> {
     Log.d("BondBreakup", "Reason: The remote device removed the bond.")
     // You could show a message to the user: "Your headphones seem to have forgotten you. Please try pairing again."
     }
     // ... handle other reasons ...
     else -> {
     Log.d("BondBreakup", "Reason: It's complicated (Unknown reason code: $reason)")
     }
     }
     }
     }
     }
    }
    // In your Activity or Service, register the receiver
    override fun onResume() {
     super.onResume()
     val filter = IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
     registerReceiver(bondStateReceiver, filter)
    }
    override fun onPause() {
     super.onPause()
     // Don't forget to unregister!
     unregisterReceiver(bondStateReceiver)
    }
    

    The code above implements a detective system for Bluetooth bond breakups, and it's more sophisticated than it might first appear. Let's walk through how this broadcast receiver pattern works and why it's so powerful.

    First, we're creating a 588 , which is Android's way of letting your app listen for system-wide events. Think of it as subscribing to a notification service, whenever something interesting happens in the Android system (like a bond state change), the system broadcasts an "intent" to all registered listeners. Our receiver is one of those listeners.

    In the 592 method, we first check if the incoming intent's action is 609 。 This is crucial because broadcast receivers can potentially receive many different types of intents, and we only care about bond state changes. Once we've confirmed this is the right type of event, we extract the relevant information from the intent using 610 and 625 .

    The 630 object tells us which Bluetooth device this event is about. After all, you might be bonded to multiple devices (your headphones, your smartwatch, your car), and we need to know which one just broke up with us. The 642 tells us the current state (are we bonded, bonding, or not bonded?), and 651 tells us what the state was before this change occurred.

    The key logic happens in our conditional check:664 。 This is checking for the specific transition from "bonded" to "not bonded," which is the digital equivalent of a breakup. We're not interested in the bonding process itself (going from none to bonding to bonded) – we only care about when an existing bond is lost.

    Once we've detected a breakup, we extract the new 674 from the intent. This is the star feature from AOSP 16 that finally gives us closure. The reason code tells us exactly why the bond was lost – was it the remote device that ended things? Did the user manually forget the device? Did authentication fail? Each reason code corresponds to a different scenario, and you can handle each one appropriately.

    In the example above, we're using a when expression to handle different reason codes. For BOND_LOSS_REASON_BREDR_INCOMING_PAIRING, we know the other device initiated the breakup, so we can show a helpful message like "Your headphones seem to have forgotten you. Please try pairing again." For other reasons, you'd add more branches to handle them specifically.

    Now, notice the lifecycle management at the bottom. We register our receiver in 688 and unregister it in 692 。 This is critical:if you forget to unregister a broadcast receiver, it will continue to receive broadcasts even after your Activity is destroyed, which can cause memory leaks and crashes. The pattern of registering in 707 and unregistering in 716 ensures that we only listen for bond changes when our Activity is visible and active.

    This is a huge step forward for debugging and for user experience. Instead of just telling the user "Connection failed," you can now give them actionable advice based on the specific reason the bond was lost. It's like being a helpful, informed relationship counselor instead of a confused bystander who can only shrug and say "I don't know what happened."

    Now that we've dealt with the emotional baggage of breakups, let's move on to something a little more lighthearted:speed dating for Bluetooth devices.

    Deep Dive #3:Service UUIDs from Advertisements

    Let's talk about finding a compatible partner... for your app. In the world of BLE, not all devices are created equal. A heart rate monitor is very different from a smart lightbulb. So how does your app know if it's talking to the right kind of device? The answer is the Service UUID.

    What in the World is a Service UUID?

    A Service UUID (Universally Unique Identifier) is like a device's job title. It's a unique, 128-bit number that says, "I am a device that provides a Heart Rate Service" or "I am a device that provides a Battery Service." It's the single most important piece of information for determining what a device can do.

    The Old Way:The Awkward First Date

    Traditionally, finding out a device's services was a whole ordeal. It was like going on a full, three-course dinner date just to find out the other person's job. The process went something like this:

    <オル>
  • Scan:Find the device.

  • Connect:Establish a connection (a slow and power-hungry process).

  • Discover Services:Ask the device, "So... what do you do for a living?" and wait for it to list all its services.

  • Evaluate:Check if the list of services contains the one you're interested in.

  • Disconnect (or stay connected):If it's not the right device, you have to break up (disconnect) and move on. What a waste of time and energy!

  • This is incredibly inefficient, especially if you're in a crowded room with dozens of BLE devices and you're only looking for one specific type.

    The New Way:The Glorious Name Tag

    Wouldn't it be great if everyone at a party just wore a name tag with their job title on it? That's exactly what AOSP 16 has given us with BluetoothDevice.EXTRA_UUID_LE. Many BLE devices are already polite enough to include their primary service UUID in their advertisement packets. It's their way of shouting, "I'M A HEART RATE MONITOR!" to the whole room.

    Before AOSP 16, getting this information out of the advertisement packet was a messy, manual process of parsing the raw byte array of the scan record. It was doable, but it was the kind of code that you'd write once, pray it worked, and never touch again.

    Now, Android does the dirty work for us! The system automatically parses the advertising data and, if it finds any service UUIDs, it conveniently hands them to you in the ScanResult.

    The Code:Reading the Name Tag

    This new feature makes our ScanCallback even more powerful. We can now check the device's job title the moment we discover it, without ever having to connect.

    private val scanCallback = object : ScanCallback() {
     override fun onScanResult(callbackType: Int, result: ScanResult) {
     Log.d("BleSpeedDating", "Found device: ${result.device.address}")
     // Let's check their name tag!
     val serviceUuids = result.scanRecord?.serviceUuids
     if (serviceUuids.isNullOrEmpty()) {
     Log.d("BleSpeedDating", "This one is mysterious. No service UUIDs in the ad.")
     return
     }
     // Define the UUID we're looking for (e.g., the standard Heart Rate Service UUID)
     val heartRateServiceUuid = ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
     if (serviceUuids.contains(heartRateServiceUuid)) {
     Log.d("BleSpeedDating", "It's a match! This is a heart rate monitor. Let's connect!")
     // Now you can proceed to connect to result.device, knowing it's the right one.
     stopBleScan() // We found what we were looking for
     // connectToDevice(result.device)
     } else {
     Log.d("BleSpeedDating", "Not a match. Moving on.")
     }
     }
     // ... onScanFailed ...
    }
    

    The code above demonstrates the power of reading service UUIDs directly from advertisement data, and it's a game-changer for device discovery. Let's break down exactly what's happening and why this is such a significant improvement.

    When we receive a scan result in our callback, the 725 object contains a 737 財産。 This scan record is essentially the raw advertisement packet that the BLE device broadcast into the air.

    Before AOSP 16, if you wanted to extract service UUIDs from this data, you'd have to manually parse the byte array, understand the BLE advertisement format, handle different data types, and pray you didn't make an off-by-one error. It was the kind of code that worked once and then you never touched it again out of fear.

    Now, with the improvements in AOSP 16, Android does all that messy parsing for us. We can simply call 740 and get back a nice, clean list of 753 オブジェクト。 The safe call operator (767 ) is important here because not all devices include a scan record in their results, and we need to handle that gracefully.

    After retrieving the service UUIDs, we check if the list is null or empty. Some devices don't include service UUIDs in their advertisements. They might be using a proprietary format, or they might just be poorly configured. If there are no UUIDs, we log a message and return early. There's no point in continuing if we can't identify what the device does.

    Next, we define the UUID we're looking for. In this example, we're searching for heart rate monitors, so we use the standard Heart Rate Service UUID:775 。 This is a UUID defined by the Bluetooth SIG (Special Interest Group), and any compliant heart rate monitor will advertise this UUID. You can find a complete list of standard service UUIDs in the Bluetooth specifications, or you can use custom UUIDs if you're building your own BLE peripherals.

    The magic happens in the 787 check. This is where we're doing our speed dating:we're checking the device's "name tag" to see if it matches what we're looking for.

    If it does, we've found our match! We can immediately stop scanning (because why keep looking when we've found what we need?) and proceed to connect to the device. We know, with certainty, that this device is a heart rate monitor, so we won't waste time and battery connecting to random devices only to discover they're not what we need.

    If the UUID doesn't match, we simply log "Not a match" and move on. The callback will be called again when the next device is found, and we'll repeat this process until we find our heart rate monitor or the user stops the scan.

    This is a massive performance improvement over the old approach. Previously, you'd have to connect to every device you found, perform service discovery (which involves multiple round-trip communications with the device), check if it has the services you need, and then disconnect if it doesn't. Each connection attempt takes time, uses battery, and creates unnecessary radio traffic.

    Now, you can filter and identify devices at lightning speed, all at the scanning stage. No more awkward first dates where you connect to a smart lightbulb thinking it might be a fitness tracker. Just efficient, targeted connections.

    This is particularly useful for apps that need to find a specific type of sensor or peripheral in a sea of irrelevant devices. Imagine you're in a hospital with hundreds of BLE-enabled medical devices, or in a smart home with dozens of sensors and actuators. Being able to instantly identify the right device from its advertisement is the difference between a responsive, professional app and one that feels sluggish and unreliable.

    We've now met all three of our Bluetooth musketeers:passive scanning for battery efficiency, bond loss reasons for better debugging, and service UUIDs from advertisements for faster device identification. But our journey isn't over. It's time to venture into the deep woods of advanced scanning techniques.

    Advanced Topics:Filtering, Batching, and Other Sorcery

    Alright, you've mastered the basics. You can scan passively, you can get closure on your connection breakups, and you can speed-date devices like a pro. You're no longer a Bluetooth padawan. It's time to become a Jedi Master.

    Let's dive into the advanced arts of filtering, batching, and other optimization sorcery that will make your app a true battery-saving champion.

    Hardware Filtering:Your Personal Assistant

    Imagine you're a celebrity, and you've hired a personal assistant. You don't want to be bothered by every single person who wants an autograph. So, you give your assistant a list:"Only let me know if you see my agent or my mom." Your assistant then stands at the door and only bothers you when someone on the list shows up.

    This is exactly what hardware filtering does. Instead of your app's code (the celebrity) being woken up for every single Bluetooth device the radio sees, you can offload the filtering logic to the Bluetooth controller itself (the personal assistant). This is a feature that's been around since Android 6.0, but it's more important than ever.

    Why is this so great? Because your app's code can stay asleep. The main processor (the AP) doesn't have to wake up every time a random Bluetooth toothbrush advertises itself. The Bluetooth controller, which is much more power-efficient, handles the filtering. The AP only wakes up when the controller finds a device that matches your criteria.

    The Code:Building Your VIP List

    You implement this using ScanFilter. You can filter by a device's name, its MAC address, or, most usefully, by the Service UUID it's advertising.

    // We only want to be bothered if we see a heart rate monitor.
    val heartRateServiceUuid = ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
    val filter = ScanFilter.Builder()
     .setServiceUuid(heartRateServiceUuid)
     .build()
    val scanFilters: List<ScanFilter> = listOf(filter)
    // Now, when you start your scan, pass in this list
    bleScanner.startScan(scanFilters, scanSettings, scanCallback)
    

    The code above shows how to create a hardware-level filter that dramatically improves both battery life and app performance. Let's dive deep into what's happening here and why this is such a powerful technique.

    We start by defining the service UUID we're interested in – in this case, the standard Heart Rate Service UUID. This is the same UUID we used in the previous example, but now we're using it in a fundamentally different way. Instead of checking the UUID in our app's code after receiving scan results, we're telling the Bluetooth hardware itself to only report devices that match this UUID.

    The 791 is our tool for constructing this filter. It's a builder pattern, which means we can chain multiple methods together to configure exactly what we're looking for. In this example, we're calling 800 , which tells the filter to only match devices that advertise this specific service.

    But the builder has many other options you can use:

    • 816 – Match devices with a specific name (like "My Heart Monitor")

    • 826 – Match a specific device by its MAC address (useful if you've already paired with a device and want to find it again)

    • 833 – Match devices based on manufacturer-specific data in their advertisements

    • 846 – Match based on service data included in the advertisement

    You can even combine multiple criteria in a single filter. For example, you could create a filter that matches devices with a specific service UUID and a specific manufacturer ID. The more specific your filter, the fewer false positives you'll get.

    After building our filter, we create a list containing it. Why a list? Because you can have multiple filters, and a device will match if it satisfies any of the filters in the list. For instance, you might create one filter for heart rate monitors and another for blood pressure monitors, and your scan will report devices that match either one. This is an OR operation:the device doesn't need to match all filters, just one of them.

    Finally, we pass this list of filters to 858 along with our scan settings and callback. This is where the magic happens. When you provide filters, Android doesn't just filter the results in your app's code. It pushes these filters down to the Bluetooth controller hardware itself. This means the filtering happens at the lowest level, before your app is even notified.

    Here's why this is so powerful:without filters, every time the Bluetooth radio hears an advertisement from any device (your neighbor's smart toaster, someone's fitness tracker walking by, the Bluetooth speaker three rooms away), it has to wake up your app's process, deliver the scan result, and let your code decide if it cares about this device. Each of these wake-ups costs battery and processing time.

    With hardware filters, the Bluetooth controller silently ignores all the devices that don't match your criteria. Your app stays asleep. The main processor stays asleep. Only when a heart rate monitor is detected does the hardware wake up your app and deliver the result. It's like having a bouncer at a club who only lets in people on the VIP list. Everyone else is turned away at the door, and you never even know they were there.

    By using a 869 , you're telling the hardware, "Don't wake me up unless you see a heart rate monitor." It's the ultimate power-saving move for background scanning. Combined with passive scanning and batch reporting, you can create a Bluetooth scanning system that runs for hours or even days with minimal battery impact. This is how professional-grade apps handle long-term device monitoring without destroying battery life.

    Batch Scanning:The Daily Report

    Let's go back to our celebrity analogy. Sometimes, you don't need to be interrupted the moment your mom shows up. You'd rather just get a report at the end of the day:"Today, your mom stopped by twice, and your agent called once." This is batch scanning.

    Instead of delivering scan results to your app in real-time, the Bluetooth controller can collect them and deliver them in a big batch. This is another incredible power-saving feature. Your app can sleep for long periods, then wake up, process a whole bunch of results at once, and go back to sleep.

    You enable this with the setReportDelay() method in your ScanSettings.

    val scanSettings = ScanSettings.Builder()
     // ... other settings ...
     // Deliver results every 5 seconds (5000 milliseconds)
     .setReportDelay(5000)
     .build()
    

    When you use a report delay, your onScanResult callback will be replaced by onBatchScanResults, which gives you a List.

    private val scanCallback = object : ScanCallback() {
     override fun onBatchScanResults(results: List<ScanResult>) {
     Log.d("BatchScanner", "Here's your daily report! Found ${results.size} devices.")
     for (result in results) {
     // Process each result
     }
     }
     // ... onScanFailed ...
    }
    

    The batch scanning mechanism shown above is one of the most underutilized power-saving features in Android Bluetooth, and understanding how it works can transform your app's battery profile. Let's break down exactly what's happening under the hood and when you should use this technique.

    When you set a report delay of 5000 milliseconds (5 seconds) in the code above, you're fundamentally changing how the scanning pipeline works. Instead of the Bluetooth controller immediately waking up your app every time it sees a device, it acts like a diligent assistant taking notes. For those 5 seconds, the controller silently collects every scan result it encounters, storing them in its own internal buffer. Your app remains completely asleep during this time – no CPU cycles wasted, no battery drained by context switches or process wake-ups.

    After the 5-second delay expires, the controller delivers all the accumulated results in one batch to your 877 callback. This is where the power savings come from:instead of waking up your app 50 times if 50 devices were detected, it wakes up once and hands you all 50 results at the same time. Your app can then efficiently process this batch – maybe updating a UI list, logging the data, or checking for specific devices – and then go back to sleep until the next batch arrives.

    The 883 parameter in 895 is a 905 , and each 915 in the list represents a single advertisement that was heard during the batching period. It's important to note that if the same device advertises multiple times during the delay period, you might receive multiple results for that device in the batch. The list isn't automatically deduplicated – that's your job if you need it.

    In the example above, we're simply logging the number of devices found and then iterating through each result. In a real application, you might want to do more sophisticated processing. For instance, you could build a map of devices keyed by MAC address to track how many times each device advertised, calculate average RSSI values to estimate distance, or filter the batch to only process devices that meet certain criteria.

    警告: Batch scanning is a powerful tool, but it's not for every situation. If you need to react to a device's presence immediately (for example, if you're building a "find my keys" app where the user is actively searching), a report delay is not your friend. The user doesn't want to wait 5 seconds to see results – they want instant feedback. In these cases, set 926 for immediate reporting.

    But for long-term monitoring or data collection scenarios, batch scanning is a battery's best friend. Consider these use cases:

    • Background presence monitoring :Your app checks every minute to see if the user's smartwatch is still in range, but doesn't need second-by-second updates.

    • Environmental sensing :You're collecting data from temperature sensors throughout a building and only need to update your dashboard every 30 seconds.

    • Beacon analytics :You're tracking how many people pass by a retail location based on their phone's BLE advertisements, and you aggregate the data every 10 seconds.

    The sweet spot for report delay depends on your use case. Too short (like 1 second), and you're not getting much benefit, you're still waking up frequently. Too long (like 60 seconds), and your app might feel unresponsive or miss time-sensitive events. For most background monitoring tasks, delays between 5 and 30 seconds work well.

    One more thing to be aware of:batch scanning has limits. The Bluetooth controller has a finite buffer for storing scan results. If you set a very long delay and you're in an environment with hundreds of BLE devices, the buffer might fill up before the delay expires. When this happens, the oldest results get dropped. Android doesn't give you a warning when this occurs, so if you're missing data, consider reducing your report delay or using more aggressive filters to reduce the number of results being collected.

    OnFound/OnLost:The Drama of Presence

    Since Android 8.0, scanning has gotten even more dramatic. You can now ask the hardware to not only tell you when it finds a device, but also when it loses one. This is done using the CALLBACK_TYPE_FIRST_MATCH and CALLBACK_TYPE_MATCH_LOST flags in your ScanSettings.

    val scanSettings = ScanSettings.Builder()
     .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH or ScanSettings.CALLBACK_TYPE_MATCH_LOST)
     .build()
    

    Now, in your ScanCallback, the callbackType parameter in onScanResult will tell you what happened.

    override fun onScanResult(callbackType: Int, result: ScanResult) {
     when (callbackType) {
     ScanSettings.CALLBACK_TYPE_FIRST_MATCH -> {
     Log.d("PresenceDetector", "Found them! ${result.device.address} has entered the building.")
     }
     ScanSettings.CALLBACK_TYPE_MATCH_LOST -> {
     Log.d("PresenceDetector", "They're gone! ${result.device.address} has left the building.")
     }
     }
    }
    

    The presence detection mechanism shown above represents a fundamental shift in how we think about Bluetooth scanning. Instead of treating scanning as a continuous stream of "here's what I see right now," we're now working with events:"this device appeared" and "this device disappeared." Let's dive deep into how this works and why it's so powerful.

    When you set the callback type using the bitwise OR operator (930 in Kotlin, 941 in Java), you're telling the Bluetooth hardware to track the presence state of devices over time. The code 953 combines both flags, meaning you want to be notified both when a device first appears and when it disappears. You can use these flags individually if you only care about one type of event, but using both together gives you complete presence awareness.

    Let's understand what "first match" and "match lost" actually mean. When the Bluetooth controller hears an advertisement from a device that matches your filters for the first time, it triggers a 969 イベント。 This is different from 971 (the default), which would trigger every single time the device advertises. A device might advertise multiple times per second, so the difference is significant. With 981 , you get one notification when the device enters your scanning range, not a flood of notifications as it continues to advertise.

    The 994 event is even more interesting. The Bluetooth controller keeps track of when it last heard from each device. If a device stops advertising (because it moved out of range, was turned off, or its battery died), the controller notices the absence and triggers a 1005 イベント。 This happens automatically:you don't have to manually track timestamps or implement timeout logic in your app. The hardware does it for you.

    But how does the hardware know when a device is "lost"? It uses an internal timeout. If the controller hasn't heard from a device for a certain period (typically a few seconds, though the exact duration is implementation-dependent and not exposed to apps), it considers the device lost. This means there's a slight delay between when a device actually leaves range and when you get the 1015 callback, but this delay is usually acceptable for presence detection use cases.

    In the code example above, we're using a 1024 expression to handle the different callback types. When we receive a 1039 , we know the device has just entered our scanning range, so we log "Found them!" This is perfect for triggering actions like unlocking a door when your phone comes near, or starting to sync data when your fitness tracker is detected.

    When we receive a 1041 , we know the device has left our scanning range or stopped advertising, so we log "They're gone!" This is ideal for triggering cleanup actions like locking the door when your phone leaves, or stopping a data sync when your tracker disconnects.

    This is incredibly useful for presence detection scenarios. Is your smart lock in range? Is your fitness tracker still connected? Is the user's phone nearby? Now you can know, with hardware-level certainty, and you can react to changes in presence without constantly polling or maintaining complex state machines in your app code.

    Here's a practical example of how you might use this in a smart home app:

    private val presenceCallback = object : ScanCallback() {
     override fun onScanResult(callbackType: Int, result: ScanResult) {
     when (callbackType) {
     ScanSettings.CALLBACK_TYPE_FIRST_MATCH -> {
     // User's phone detected - they're home!
     Log.d("SmartHome", "Welcome home! Unlocking door and turning on lights.")
     unlockFrontDoor()
     turnOnLights()
     adjustThermostat(COMFORTABLE_TEMP)
     }
     ScanSettings.CALLBACK_TYPE_MATCH_LOST -> {
     // User's phone is gone - they left!
     Log.d("SmartHome", "Goodbye! Locking door and entering away mode.")
     lockFrontDoor()
     turnOffLights()
     adjustThermostat(ENERGY_SAVING_TEMP)
     armSecuritySystem()
     }
     }
     }
     override fun onScanFailed(errorCode: Int) {
     Log.e("SmartHome", "Presence detection failed: $errorCode")
     }
    }
    

    One important consideration:1050 and 1067 are mutually exclusive with 1073 。 If you combine them with 1084 , the behavior becomes undefined and varies by device. Stick to either 1098 for continuous reporting, or 1103 /1114 for presence detection – don't try to use both at once.

    Also, be aware that presence detection works best when combined with hardware filtering. If you're scanning for all devices without filters, the controller has to track the presence state of every single BLE device in range, which can overwhelm its internal tracking tables. Always use 1127 to narrow down which devices you care about when using presence detection.

    By combining these advanced techniques – hardware filtering, batch scanning, and presence detection – you can build incredibly sophisticated and power-efficient Bluetooth applications. You're not just a developer anymore. You're a Bluetooth wizard, wielding the power to create apps that are aware of their surroundings, responsive to changes, and respectful of battery life.

    Now, let's see where we can apply these magical powers in the real world.

    Real-World Use Cases:Where the Bluetooth Hits the Road

    Okay, we've learned a ton of cool new tricks. We're basically Bluetooth black belts at this point. But what's the use of all this power if we don't use it for good (or at least for a cool app)? Let's explore some real-world scenarios where the new features in AOSP 16 can turn a good app into a great one.

    1. The "Find My Everything" App

    We've all been there. You're late for work, and your keys have decided to play a game of hide-and-seek in another dimension. This is the classic use case for a BLE tracker.

    • The Old Way: Your app would be constantly doing active scans, draining your battery while you frantically search. It would connect to every tracker in your house just to see if it's the right one.

    • The AOSP 16 Way: Your app runs a passive scan in the background with a hardware filter for your tracker's specific Service UUID. The battery impact is minimal. When you open the app to find your keys, it already knows they're in the house because it's been listening silently. You hit the "Find" button, the app connects, and your keys start screaming from inside the couch cushions. And if the connection fails? Bond loss reason tells you if the tracker's battery died, so you're not looking for a dead device.

    2. The Smart Supermarket

    Imagine an app that gives you coupons for products as you walk past them in the store. This is the dream of proximity marketing, a dream that has been historically thwarted by, you guessed it, battery drain.

    • The Old Way: The app would need to constantly scan for beacons, turning the user's phone into a hot potato and a dead battery by the time they reach the checkout line.

    • The AOSP 16 Way: The supermarket places BLE beacons in each aisle. Your app uses a passive, batched scan. It wakes up every minute or so, gets a list of all the beacons it has seen, and then goes back to sleep. When it sees you've been loitering in the cookie aisle for five minutes (it knows, it always knows), it uses the Service UUID from the advertisement to identify the "Cookie Aisle Beacon" and sends you a coupon for Oreos. It's targeted, it's efficient, and it doesn't kill your battery before you can pay.

    3. The Overly-Attached Smart Home

    Your smart home should be, well, smart. It should know when you're home and when you've left. It should lock the door behind you and turn on the lights when you arrive.

    • The Old Way: You'd have to rely on GPS (a notorious battery hog) or Wi-Fi connections, which can be unreliable. BLE was an option, but constant scanning was a problem.

    • The AOSP 16 Way: Your phone is the key. Your smart hub (acting as a central device) runs a continuous, low-power passive scan. When it sees your phone's BLE advertisement, it knows you're home. But what if you just walk by the house? This is where the OnFound/OnLost feature comes in. The hub can be configured to only trigger the "Welcome Home" sequence after it has seen your device consistently for a minute (OnFound), and to trigger the "Goodbye" sequence only after it hasn't seen you for five minutes (OnLost). It's a smarter, more reliable presence detection system that finally makes the smart home feel... smart.

    4. The Corporate Asset Tracker

    In a large hospital or warehouse, keeping track of expensive, mobile equipment (like IV pumps or forklifts) is a huge challenge. BLE tags are the solution.

    • The Old Way: Employees would have to walk around with a tablet, doing active scans to take inventory. It's slow, manual, and inefficient.

    • The AOSP 16 Way: A network of fixed BLE gateways is installed throughout the building. Each gateway is a simple device (like a Raspberry Pi) running a continuous passive scan. They collect all the advertisement data from the asset tags and send it to a central server. The server can now see, in real-time, that IV Pump #34 is in Room 201, and Forklift #3 is currently in the loading bay. No manual scanning required. It's a low-cost, low-power, real-time location system, all thanks to the efficiency of passive scanning.

    These are just a few examples. From fitness trackers to industrial sensors, the new Bluetooth features in AOSP 16 open up a world of possibilities for building apps that are not only powerful but also polite to your user's battery. Now, let's talk about how to make sure our shiny new app works on all devices, not just the new ones.

    API Version Checking:How to Not Crash Your App

    So, you've built a beautiful, battery-sipping app using all the new hotness from AOSP 16's Q4 release. You're ready to ship it, become a millionaire, and retire to a private island. But then, a bug report comes in. Your app is crashing on a brand new Android 16 device. What gives?!

    Welcome, my friend, to the wonderful world of API version checking. With Android's new release schedule, this has become more important (and slightly more complicated) than ever.

    The Problem:A Tale of Two Android 16s

    As we discussed, 2025 gave us two Android 16 releases:

    • The Q2 Release: The main "Baklava" release. Let's call this API level 36.0.

    • The Q4 Release: The minor, feature-drop release. This is where our new Bluetooth toys live. Let's call this API level 36.1.

    Our new passive scanning API, setScanType(), only exists on 36.1 and later. If you try to call it on a device that's running the initial Q2 release (36.0), your app will crash with a NoSuchMethodError. It's the digital equivalent of asking for a menu item that was only added last night. The chef (your app) just gets confused and has a meltdown.

    The Old Guard:SDK_INT

    For years, our trusty friend for checking API levels has been Build.VERSION.SDK_INT. It's simple and effective.

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
     // Use an API from Android 12 (S) or higher
    }
    

    But SDK_INT only knows about major releases. For both Android 16 Q2 and Q4, SDK_INT will just report 36. It has no idea about the minor version. It's like asking someone their age, and they just say "thirties." Not very specific.

    The New Hotness:SDK_INT_FULL

    To solve this, the Android team has given us a new, more precise tool:1136 。 This constant knows about both the major and minor version numbers. And to go with it, we have a new set of version codes:1143 .

    So, to safely call our new passive scanning API, we need to do a more specific check:

    // Let's build our ScanSettings
    val scanSettingsBuilder = ScanSettings.Builder()
     .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
    // Now, let's check if we can go passive
    if (Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.BAKLAVA_1) {
     Log.d("ApiCheck", "This device is cool. Going passive.")
     // This is the new API from the Q4 release (36.1)
     scanSettingsBuilder.setScanType(ScanSettings.SCAN_TYPE_PASSIVE)
    } else {
     Log.d("ApiCheck", "This device is old school. Sticking to active scanning.")
     // Fallback for devices that don't have the new API
     // We don't need to do anything here, as active is the default
    }
    val scanSettings = scanSettingsBuilder.build()
    

    Graceful Degradation:The Art of Falling with Style

    This brings us to a crucial concept:graceful degradation. It means your app should still work on older devices, even if it can't use the latest and greatest features. It should fall back gracefully.

    In our example above, if the setScanType method isn't available, we just... don't call it. The app will default to a normal, active scan. It won't be as battery-efficient, but it will still work. The user on the older device gets a functional app, and the user on the newer device gets a more optimized experience. Everybody wins.

    Here's a table to help you remember when to use which check:

    If you're using an API from...

    Use this check...

    A major Android release (for example, Android 16 Q2)

    if (SDK_INT>=VERSION_CODES.BAKLAVA)

    A minor, feature-drop release (for example, Android 16 Q4)

    if (SDK_INT_FULL>=VERSION_CODES_FULL.BAKLAVA_1)

    Mastering this new API checking is non-negotiable. It's the key to writing modern Android apps that are both innovative and stable. Now that we know how to build a robust app, let's talk about how to fix it when it inevitably breaks.

    Testing and Debugging:The Fun Part (Said No One Ever)

    There are two universal truths in software development:

    • It works on my machine, and

    • It will break in the most spectacular way possible during a live demo.

    Bluetooth development, in particular, seems to delight in this second truth. It's a fickle, invisible force that seems to have a personal vendetta against developers.

    So, how do we fight back? With a solid testing and debugging strategy. It's not glamorous, but it's the only way to stay sane.

    The Emulator:A Land of Make-Believe

    Android Studio's emulator is a fantastic tool. It's fast, it's convenient, and it can simulate all sorts of devices. And for Bluetooth? It can... sort of help. The emulator does have virtual Bluetooth support. You can enable it, and your app will think it has a Bluetooth adapter. It's great for testing your UI and making sure your app doesn't crash when it tries to get the BluetoothLeScanner.

    But here's the catch:it's not real. The emulator can't actually interact with the radio waves in your room. You can't use it to find your real-life BLE headphones. For that, you need to venture into the real world.

    The Real World:Where the Bugs Live

    There is no substitute for testing on real, physical devices. Every phone manufacturer has its own special flavor of Bluetooth stack, its own quirky antenna design, and its own unique way of making your life difficult. A scan that works perfectly on a Google Pixel might fail miserably on another brand. The only way to know is to test.

    Your testing arsenal should include:

    • A variety of phones: Different brands, different Android versions. The more, the better.

    • A variety of BLE peripherals: Don't just test with one type of device. Get a few different beacons, sensors, or wearables. You'll be amazed at how differently they behave.

    Common Errors:The Usual Suspects

    When your scan inevitably fails, it will give you an error code. Here are a few of the most common culprits:

    Error Code

    The Problem

    How to Fix It

    SCAN_FAILED_ALREADY_STARTED

    You tried to start a scan that was already running.

    You got too excited. Make sure you're not calling startScan() multiple times without calling stopScan() in between.

    SCAN_FAILED_APPLICATION_REGISTRATION_FAILED

    Something is fundamentally wrong with your app's setup.

    This is a vague and unhelpful error. It usually means you have a problem with your permissions or the system is just having a bad day. Try restarting Bluetooth.

    SCAN_FAILED_INTERNAL_ERROR

    The Bluetooth stack had a panic attack.

    This is the classic "it's not you, it's me" error. It's an internal issue with the device's Bluetooth controller. There's not much you can do except try again later.

    SCAN_FAILED_FEATURE_UNSUPPORTED

    You tried to use a feature the hardware doesn't support.

    You might be trying to use batch scanning on a device that doesn't support it. Use your API version checks!

    Debugging Tools:Your Ghost-Hunting Kit

    When things go wrong, you need the right tools to see what's happening in the invisible world of Bluetooth.

    • logcat: This is your best friend. Be generous with your log statements. Log when you start a scan, when you stop a scan, when you find a device, and when a scan fails. Create a filter for your app's tag so you can see the signal through the noise.

    • Android's Bluetooth HCI Snoop Log: This is the holy grail of Bluetooth debugging. It's a developer option that records every single Bluetooth packet that goes in or out of your device. It's incredibly detailed and can be overwhelming, but it's the ultimate source of truth. You can open the generated log file in a tool like Wireshark to see the raw, unfiltered conversation between your phone and the BLE device. It's like having a wiretap on the radio waves.

    • nRF Connect for Mobile: This is a free app from Nordic Semiconductor, and it's an essential tool for any BLE developer. It lets you scan for devices, see their advertising data, connect to them, and explore their GATT services. If your app can't find a device, the first thing you should do is see if nRF Connect can. If it can't, the problem is likely with the peripheral, not your app.

    Testing and debugging Bluetooth is a marathon, not a sprint. It requires patience, a methodical approach, and a healthy dose of self-deprecating humor. But with the right tools and techniques, you can tame the beast.

    Now, let's talk about how to make sure our well-behaved app is also a good citizen when it comes to performance.

    Performance and Best Practices:How to Be a Good Bluetooth Citizen

    Writing code that works is one thing. Writing code that works well, is efficient, and doesn't make your users want to throw their phone against a wall is another thing entirely. When it comes to Bluetooth, being a good citizen is all about one thing:battery, battery, battery.

    The Bluetooth radio is a powerful piece of hardware, but it's also a thirsty one. Every moment it's active, it's sipping power. Your job is to make sure it's only sipping when absolutely necessary. Here are the golden rules of being a good Bluetooth citizen.

    1. Don't Scan If You Don't Have To

    This sounds obvious, but it's the most common mistake. Before you even think about starting a scan, ask yourself:"Do I really need to do this right now?" If the user is not on the screen that needs scan results, don't scan. If the app is in the background, be extra critical. Background scanning is a huge drain on battery and is heavily restricted by Android for that very reason.

    2. Stop Your Scan!

    I'm going to say it again because it's that important:always stop your scan when you're done. A scan that's left running is like a leaky faucet for your battery. It will drain and drain until there's nothing left. The best practice is to tie your scan lifecycle to your UI lifecycle.

    override fun onPause() {
     super.onPause()
     // The user can't see the screen, so they don't need the results.
     stopBleScan()
    }
    override fun onResume() {
     super.onResume()
     // The user is back on the screen, let's start scanning again.
     startBleScan()
    }
    

    If you find the device you're looking for, stop the scan immediately. There's no need to keep looking.

    3. Choose the Right Scan Mode

    ScanSettings gives you a few different modes. Choose wisely.

    • SCAN_MODE_LOW_POWER: This is your default, everyday mode. It scans in intervals, balancing discovery speed and battery life. Use this for most foreground scanning.

    • SCAN_MODE_BALANCED: A middle ground. It scans more frequently than low power mode.

    • SCAN_MODE_LOW_LATENCY: This is the "I need to find it NOW" mode. It scans continuously. This will find devices the fastest, but it will also drain your battery the fastest. Only use this for short, critical operations.

    • SCAN_MODE_OPPORTUNISTIC: This is the ultimate passive mode. Your app doesn't trigger a scan at all. It just gets results if another app happens to be scanning. It uses zero extra battery, but you have no guarantee of getting results. Use this for non-critical background updates.

    And of course, if you're on AOSP 16 QPR2 or later, use setScanType(SCAN_TYPE_PASSIVE) whenever you don't need the scan response data. It's the new king of power efficiency.

    4. Use Hardware Filtering and Batching

    We covered this in the advanced section, but it's a best practice that's worth repeating. If you're looking for a specific device, use a ScanFilter. If you're doing a long-running scan, use setReportDelay() to batch your results. These two techniques offload the work to the power-efficient Bluetooth controller and let your app's code sleep, which is the number one way to save battery.

    5. Be Mindful of Memory

    Every ScanResult object that your app receives takes up memory. If you're in a crowded area with hundreds of BLE devices, and you're not using filters, your app can quickly get overwhelmed and run out of memory. This is another reason why filtering is so important. Only get the results you actually care about.

    By following these rules, you can build a Bluetooth app that is not only powerful and feature-rich but also respectful of your user's device. You'll be a true Bluetooth sensei. Now, let's wrap things up and look to the future.

    Conclusion:The Future is Passive (and That's Okay)

    We've been on quite a journey, haven't we? We've traveled back in time to the dark ages of Classic Bluetooth, witnessed the renaissance of BLE, and emerged into the brave new world of AOSP 16. We've learned to be silent ninjas with passive scanning, played detective with bond loss reasons, and mastered the art of speed dating with service UUIDs from advertisements.

    If there's one big takeaway from all of this, it's that the future of Bluetooth on Android is smarter, more efficient, and a whole lot less frustrating. The Android team is clearly listening to the pain points of developers and giving us the tools we need to build better, more battery-friendly apps. The introduction of passive scanning isn't just a new feature – it's a change in philosophy. It's an acknowledgment that sometimes, the best way to communicate is to just listen.

    As developers, these new tools empower us to move beyond the simple "connect and stream" use cases. We can now build sophisticated, context-aware applications that are constantly aware of their surroundings without turning our users' phones into expensive paperweights. The dream of a truly smart, seamlessly connected world is a little bit closer, and it's going to be built on the back of these power-efficient technologies.

    So, what's next? The world of Bluetooth is always evolving. We have Bluetooth 5.4 with Auracast, mesh networking, and even more precise location-finding on the horizon. The one thing we can be sure of is that the tools will continue to get better, and the challenges will continue to get more interesting.

    For now, take a moment to appreciate the progress we've made. The next time you start a Bluetooth scan and it just works, take a moment to thank the hardworking engineers who made it possible. And the next time your app's battery graph is a beautiful, flat line instead of a terrifying ski slope, give a little nod to the power of passive scanning.

    The Bluetooth beast may never be fully tamed, but with AOSP 16, we've been given a much stronger leash. Now go forth and build amazing things. And for the love of all that is holy, remember to stop your scan.

    無料でコーディングを学びましょう。 freeCodeCamp のオープンソース カリキュラムは、40,000 人以上の人々が開発者としての職に就くのに役立ちました。始めましょう


    1. Android デバイスに LineageOS をインストールする方法:完全なステップバイステップ ガイド

      LineageOS は、Android デバイスで最も人気のある ROM です。ブロートウェアのないほぼ標準の Android エクスペリエンスと、クリーンで整然としたインターフェイスにより、大きな成功を収めています。定期的なセキュリティ更新と一部のストック ROM に欠けているバグ修正が提供されます。さらに、標準の Android ファームウェアよりも高いレベルのカスタマイズが可能です。 さらに良いことに、この ROM は、公式アップデートを受け取らなくなった古いデバイスを含む、広範なデバイスのリストをサポートしています。ここでは、Android スマートフォンに LineageOS をイ

    2. Androidで新しいiOS9.1絵文字を表示および送信する方法

      Appleは10月末にiOS9.1を一般公開しましたが、多くのユーザーにとって最大の視覚的変化の1つは、中指、タコス、ブリトー、ユニコーンヘッドなどの新しい絵文字の追加でした(重要なものはすべて言語の境界を超えて) これは、AppleがUnicodeコンソーシアムによって作成された2つの最新の標準であるUnicode7.0と8.0を迅速に採用したおかげです。ただし、Androidは、最新バージョンの6.0 Marshmallowであっても、最新のUnicode標準をサポートしていません。また、Androidの更新が消費者に届くまでの時間がどれほど遅いかを考えると、Androidデバイスで最新