hiro99ma blog

何か技術的なこと

android: 非同期処理

2024/10/17

もともと BLE の Central 側を作りたくて Android の勉強をし始めたのだった。
そろそろ何か動かせるんじゃないか、と思ったが非同期処理をやっていなかった。
BLE を操作する API 自体が非同期になっているならわざわざ自分で非同期処理を書く箇所はないかもしれない。
例えばデバイスのスキャンであれば、スキャンを依頼して結果だけコールバックされるようになっているとか。
そうなっていれば、ButtononClick に処理を書くのと同じようにできるはず。

が、基本はやっておこう。

非同期処理はコルーチンで良いのか?

Android の非同期処理はいろいろ書き方があったと思うけど今はコルーチンだけ覚えておけば何とかなるのだろうか?という確認をする回です。
「コルーチンではダメなんじゃないか?」という疑問ではないです。

昔話java.util.concurrent か Kotlin concurrency utilities

例えば、以前は AsyncTask というクラスがあった。
これは API 30 から deprecated になっている。 まだ使えるけどなくなっても知らんよ、というところか。

Use the standard java.util.concurrent or Kotlin concurrency utilities instead.

と案内してあるので、Kotlin を使うならコルーチンでいいんじゃなかろうか。

コルーチン?

特に詳しいわけでも無いが、golang の go routine と同じようなものだと思う。
並列処理をするのに、最初はプロセス、その次にスレッドが出てきたけど、さらに小さい単位としてコルーチンが出てきたという印象を持っている。

プロセスとスレッドは OS の機能に頼り切るが、コルーチンはプログラムの中にそういう機能を持っているからディスパッチが軽いし自由度が高い(と思っている)。
プロセスだとメモリ空間が切り離されるけど、スレッドは切り離されない。 コルーチンはどちらかといえばスレッド寄りの考え方になるだろう。

Kotlin のコルーチン

主な機能が列挙してあるので書いておこう。

基本

こちらを参考にを作った。
launch{} で囲まれた部分は 1 つのコルーチン。
それを 2つ用意したらそれぞれ並列に動作するというサンプルだ。

package kotlinx.coroutines.guide.exampleBasic02

import kotlinx.coroutines.*

fun main() = runBlocking { // this: CoroutineScope
    launch { doWorld() }
    launch { doSome() }
    println("Hello")
}

suspend fun doWorld() {
    delay(1000L)
    println("World!")
}

// 200 msec ごとに 10 回ログ出力
// doWorld()が 1秒待って "World" を出力するので 5 回目付近でそのログが出る
suspend fun doSome() {
    for (i in 0..9) {
        println("some... $i")
        delay(200L)
    }
}

実行するとこういう結果になる。

Hello
some... 0
some... 1
some... 2
some... 3
some... 4
World!
some... 5
some... 6
some... 7
some... 8
some... 9

ちょろっと Activity にコルーチンを入れて試してみよう、というのが面倒そうだ。
ただ Composable 関数からコルーチンを立ち上げることはできるようだった。

rememberCoroutineScope は引数に直接 Dispatchers.IO などは与えられないが、launch() の引数に与えられるのでそれでよかろう。
通常はバックグラウンドで時間がかかることをするのはデータにアクセスするような処理で、それはどこかで描画に使われるか、使われなくても他のデータアクセスに使われるかだろう。
そうだったら素直に ViewModel の派生クラスを用意して State Holder を作った方がフレームワークの恩恵を受けやすいだろう。

簡単じゃないねー。

おまけ

依存関係の追加は必要か?

Compose を使う前は build.gradle だったが、今やこのファイルも Kotlin 形式になったようだ。
依存関係情報 を見て追加しようとしたが、Android Studio で作ったプロジェクトはこういう書き方になっていた。

dependencies {
  implementation(libs.androidx.core.ktx)
  ...

たぶん gradle/libs.versions.toml が変数を定義しているファイルだ。

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
...

どうやって追加したら良いかと考えていたが、Jetpack がコルーチン向けになっているのであればわざわざ追加しなくても使えるはず。

suspend, launch runBlocking を書いてみると import kotlinx.coroutines.launch などは使えた。
しばらく様子を見よう。
GitHub kotlin の方にはもう少し詳しく書かれていたので、必要とあらばそちらを見るのもよさそう。

TAG という名前

Log.d() の第1引数はタグだ。

    public static int d(@Nullable String tag, @NonNull String msg) {
        return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
    }

class 内ではだいたい固定値として使うので Java のときは全部大文字の TAG で使っていた。
Kotlin でもそうしているのだが、Android Studio が全部大文字の単語を使っていることに対して警告を出す。

image

Java がどういうルールだったか覚えていないが、基本的に小文字始まりのキャメルだったような気がする。
ただ C/C++ でマクロが大文字だったことと、固定値は const 変数よりもマクロで書くことが多かったことから、Java でも固定値は全部大文字でいいか、と思っている。
私が思ってもどうしようもないのだが。

const で固定値にするときは小文字k始まりにすることが多かった。
どこからきたルールなのか知らないけど、使っていたフレームワークがそうだったので私もそういうものかと受け入れている。

まあ、それはよいとしてタグだ。
tag としても問題はないのだけど、全部小文字で短いのも classスコープの変数としてどうなのかという気もする。
かといって logTag みたいにするのもなんだかなあ、という気がしている。 使う箇所が Log.X() でしかないので Log.i(logTag, ~) と “log” が 2回出てくるのもくどい気がしてしまう。

それになにより「Log には TAG」という慣れがある。
見慣れているので違っていれば気付きやすいし、他で使うことにちゅうちょするという抑止効果もある(たぶん)。

なんで、TAG でもういいんじゃないの、と私は思っている。

ちなみに私は class 変数として TAG を持たせてる。
companion object にしてシングルトンにしたほうが無駄が少ないのかもしれんが、val だしコンパイル時にたぶん解決してくれるし、もういいんじゃないか。
他の class に持っていくときもコピペで済むし。

private val TAG = javaClass.simpleName

製品アプリの場合、ログをファイルに保存してバグが起きたときに送ってもらうという使い方をしたいかもしれない。
log4j は今も使われているのだろうか?
検索すると Timber というライブラリが出てきたのでそのうち使ってみよう。

val は const じゃないんだ

非同期処理を調べていたが、こうすればよいというのがわからないままだった。
もうちょっと Codelabs で勉強しようと思い、順番を飛ばして興味があるところだけでもやっておこうと眺めていた。

コンパイル時の定数としてマークするには、変数を宣言するときに const を使用します。コンパイル時の定数は、コンパイル時に判明する値です。

private const val TAG = "MainActivity"

これは classcompanion object ではなく地の文(?)で定義されていた。
Java は何でもかんでも class の中にあったと思うが Kotlin はそうではないのだな。
(main() がそうじゃない時点でそうなのだと気付くべきだな。。。)

val は定数宣言だと思っていたけど const もあったんだ!
koglinlang.orgの検索だと見つけられないのだ。

class のメンバ変数を const にはできないそうだ。

image

companion object はシングルトンとして使えるという説明をよく見るが、それだったら static を付ければ良いのでは?
そう思ったが static は Android Studio で色も変わらないところを見ると存在しないようだ。

まあ、class を定義したときにしか書かないので大した作業ではないから毎回書いてもいいかな(敗北)。
それに Android Studio の Live Templates には「logt」というスニペットがある。
これを使うと class の数行上くらいに logt(TABキー) とするだけで TAG の定義を作ってくれるので、これでいいか。

image

でも Log.X(TAG, xxx)と毎回書くのも面倒だし、ログを出力したい場合を考えるとやっぱりライブラリを使う方向が良い気がする。

< Top page