Android で BLE
AndroidKotlin
2022-7-17 17:54 JST

Android の BLE アプリを Kotlin でつくってみました。Java の時代とは違って Kotlin での開発はちょっとコツが必要。

まずは Android Studio から

Eclipse から Android Studio になって久しい。やっぱり Eclipse はIDE として概念が複雑過ぎた気がします。とくにプラグインの概念。Android Studio もメニュー数が多くて全体を把握できません。Arudino くらいがちょうどよいのに、、、結局、最後は vi に回帰するでしょう。

Android Studio 便利なところもあって Refactoring とか便利。あと、エラーを起こしたときのお勧め修正機能があって、"何も考えずに"修正してもらうということができちゃう。これで表面上はエラーのない状態になりますが、実行してみるとまったく動かないという、、、まぁちゃんと理解した上でプログラムを書きましょう。

なんだ Gradle のラッパーか

Gradle という Build Tool があってこれで Kotlin の Android 環境ではない質素な環境を構築することが可能です。GradleでKotlinプロジェクトを一撃生成するという記事がわかりやすい説明でした。

gradle init --type=kotlin-application

Gradle の設定ファイルを Groovy あるいは Kotlin に選択可能で、私は Kotlinにしてみました。Kotlin の情報少ないけどね。これで、めでたく Hello World を実行できました。後に、okhttp3 とかを単独でテストする環境も Gradle でつくりました。Android Studio でテストするより見通しがいいからね。

Android Studio でテスト的にアプリ

とりあえずつくってみる。最初は Fragment が2つあるサンプル。

BLE との接続

久々に Android のアプリの開発をするのですっかり AndroidManifest.xml の存在を忘れていました。BLUETOOTH_ADVERTISE は使わなかったので必要ないかも。

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

SDK の対象バージョンを 31 にしたので(OS でいうと 11 以降)次の設定は追加しませんでした。

    <uses-permission android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30" />

ACCESS_FINE_LOCATION が必要

Android の公式ドキュメントにも書いてあったのですが、 ACCESS_FINE_LOCATION のパーミッションが必要です。ACCESS_COARSE_LOCATION 必要ないようなのですが(実際に試してみた)Android Studio でワーニングが出るので追加しました。

パーミッションを聞く

Kotlin 側の記述。知らない間に Fragment なるものが導入されています。達成したいことは BLUETOOTH_SCAN して BLUETOOTH_CONNECT すること。

次のコードをは BLUETOOTH_SCAN のパーミッションがなければ、ユーザにその権利をくださいと OS 経由で聞くというコード。onCreate で呼ぶようにしました。この registerForActivityResult はview ができる前に呼ばないといけないみたいで、ボタンでアクションを起こして呼ぶと Exception を発生させアプリを異常終了させてしまいます。

        if (checkSelfPermission( this.requireContext(), Manifest.permission.BLUE
TOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
            val requestPermission = registerForActivityResult(ActivityResultCont
racts.RequestPermission()) { result ->
                if (result) {
                    Toast.makeText(
                        this.requireContext(),
                        "Permission Accepted.",
                        Toast.LENGTH_SHORT
                    ).show()
                    //bluetoothLeScanner?.startScan(scanCallback)
                } else {
                    Toast.makeText(this.requireContext(), "Permission Denied.",
Toast.LENGTH_SHORT).show()
                }
            }

            requestPermission.launch(Manifest.permission.BLUETOOTH_SCAN)
        }

問題は BLUETOOTH_SCAN だけでなく BLUETOOTH_CONNECT も同様のコードを書かないといけない点です。この requestPermission.launch している間に view は出来てしまうので、BLUETOOTH_SCAN のお伺いを立てた後に BLUETOOTH_CONNECT するということが出来ないような気がします。registerForActivityResult がview のない状態を要求するため。

ということで、現在は Fragment に1つ聞きに行くという作戦をとっています。

BLUETOOTH_CONNECT の Permission 貰えれば BLUETOOTH_SCAN は自動的に、、、とは行かないようです。ユーザに聞きに行くのは1度なのですが、OS には各Permissionごとに聞きに行く必要があるみたいです。

AlertDialog

AlertDialog を出す方法で聞く方法もあるようです。次のコードは LOCATION_FINE_PERM の許可をもらうためのコードです。これだと view が出来た後でも呼べるみたい。このコードelse でも makeLocationRequest を呼んでいるけど、、、これまちがいじゃね?

アプリの権限をリクエストするにかかれていますが、shouldShowRequestPermissionRationale を理解してない、、、

パーミッションダイアログで「今後表示しない」を選択されたかどうかを判定するに丁寧に書いてあった。ありがとう。

   private fun requestLocationPermission() {
        if (shouldShowRequestPermissionRationale(LOCATION_FINE_PERM)) {
            val alertDialogBuilder = AlertDialog.Builder(requireContext())
            with(alertDialogBuilder) {
                setTitle(getString(R.string.loc_request_title))
                setMessage(getString(R.string.loc_request_msg))
                setPositiveButton(getString(R.string.okay)) { _, _ -> makeLocationRequest() }
            }
            alertDialogBuilder.create().show()
        } else {
            makeLocationRequest()
        }
    }

private fun makeLocationRequest() = requestPermissions(
            arrayOf(LOCATION_FINE_PERM),
            PERMISSION_REQUEST_LOCATION
    )

requestPermissions これ、もしかして arrayOf ってことは複数設定できる?が、Android Studio では requestPermissions は傍線が惹かれるのでどうやら Deprecated ぽいんだけど。startActivityForResult / requestPermissions が deprecated になる話あ〜やっぱり deprecated です。じゃどうせよと?

結局 registerForActivityResult で launch 可能なオブジェクトを onCreate 時に作るみたいね。複数作っておいて、launch 自体は後でできるみたい。

あと、参考までに書くと SCAN するだけなら、Gadd いや Gatt のサービスは必要ありませんでした(確認済み)。

リンク集