Open13
Rustでアンドロイドアプリ開発をしたい
現状雛形ができただけの状態だがとりあえずやったことを
- サンプルそのままだと落ちるので修正
- AndroidライフサイクルでActivityが表示されていないとNativeWindowが死ぬため
-
Event::Resumed
が来る度にwgpu::Surface
を作り直す運用
- あと
Event::RedrawRequested
が自動で呼ばれないのでEvent::MainEventsCleared
のタイミングでWindow::request_redraw
を呼ぶ - とりあえずこれで起動して表示まで行けた
ここからRust ネイティブオンリーで開発するなら多分これだけでOK
以下はJVMなコードを含める場合
- cargo-apkはネイティブオンリーでの開発を想定されているのでJVM言語をビルドする仕組みにはなっていない
- Java関数を呼び出すだけならJNIでできる
- 普通にAndroid Studioでプロジェクト作ってネイティブライブラリをビルドする仕組みにするのが一般的だと思う
- でもせっかくだから全部Rustのエコシステムに乗せたい
- cargo-mobileというものがあるらしい
- ところで私はWindowsユーザーである
-
cargo install --git https://github.com/BrainiumLLC/cargo-mobile
を実行する - Host platform not yet supported by cargo-mobile! We'd love if you made a PR to add support for this platform ❤️
- 😿
- ……作るか
- 作った
- cargo-mobileはAndroidプロジェクトを自動生成してくれる
- 思想としては全部cargo-mobileが面倒見るから自動生成に任せるべき的な感じらしく生成したAndroidプロジェクトはまるごと.gitignoreに入れられている
- が、そこまでうまく行ってない感じなので結局Gradle スクリプト色々弄る
- まあまだcrates.ioにもリリースされとらんしこれからの発展に期待しよう
- そもそもなぜマネージドコードが必要なのかと言うとナビゲーションバーを消したい。これ↓
- 消す方法は二つある
-
activity.getWindow().getDecorView().setSystemUiVisibility(int)
でView.SYSTEM_UI_FLAG_HIDE_NAVIGATION
をセットする- Activityが非表示になるたびにクリアされるので
Event::Resumed
の度に呼びなおす - 実のところこれだけならJNIで必要なクラスと関数検索して呼んでやるだけでもいい
- ただしこの方法はDeprecated(API Level 30~)
- Activityが非表示になるたびにクリアされるので
- 新しい方法は
activity.getWindow().getDecorView().getInsetsController.hide(int)
を呼んでやること- こちらは呼び出し一回すればずっと有効
- ただし
View
が作られたスレッドで実行しないといけない -
Activity.runOnUiThread
を呼ぶのにRunnable
が必要 - JNIではインターフェイスを実装できない
- とりあえず
AndroidManifest.xml
をいじってandroid:hasCode="true"
にする - Kotlinを使ってみようと思ったので
cargo android open
でAndroid Studioを立ち上げ(別にこの方法じゃなくても良いんだけど)、メニューのTools -> Kotlin -> Configure Kotlin in ProjectでKotlinの設定を追加- したらビルドスクリプトがbuild.gradle.ktsなのにgroovy用のコードが追加されてエラー出まくったので修正
- appの
build.gradle.kts
にを追加。これでこの下の*.ktファイルがコンパイルされるはずandroid { sourceSets.getByName("main") { // ... kotlin.srcDir("src/main/kotlin") } }
- 適当に
com.example.my_app.Util
的なクラスを追加してstaticメソッドを使えばいいかな?と思ってやってみる - なんかJNIからクラス探索が失敗する
- わからん
- わからんので方法を変えた。調べてみたところ
NativeActivity
はサブクラス化できるのでサブクラス化してそいつにメソッドを生やす。Activityはネイティブコード側から必ず取れる(取れなかったらウィンドウの初期化も何もできない)のでそこに生えたメソッドなら確実に呼べる -
com.example.my_app.MyActivity
的なクラスを生やす -
AndroidManifest.xml
のactivity
要素をandroid:name="com.example.my_app.MyActivity"
にする - あとついでに
android:theme="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"
にもする - これでフルスクリーンかつナビゲーションバーのないアプリになった
- これだけなら正直言うとRustオンリーでも良かった気もする。Deprecatedとはいえ
setSystemUiVisibility
で用は足りる訳だし - とはいえ、今はまだ単純に画面表示しているだけだけど、そのうちまたマネージドコードが必要になることも無いとは言えないし、その時になってビルド方法を大きく変えるのは面倒そう
- なのでこれで良しとしておこう
- ところで改めて
setSystemUiVikibility
の方で試してみたところ例外が発生して落ちた - どうもJNIを使っていた時にうまくいっていたように見えたのは例外を握りつぶしていたかららしい
- どんな例外が発生しているかと調べたところ "android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views."
- どうやら初回の
Event::Resume
と二回目以降(Activityを移って戻ってきた時など)のEvent::Resume
で呼び出しスレッドが違う(そんなことあるのか……) - 結局
runOnUiThread
からは逃れられない
- もうすこし調査したところRustのネイティブスレッドは常に同じだったがJVMスレッドが毎回違う(理由はよく分からない。AttachCurrentThreadの度に異なるJVMスレッドと認識される?)
- JVMスレッドが異なるならメインスレッドでもUIスレッドでもないので
setSystemUiVisibility
は常に失敗しそうなものだが成功する理由もよく分からない - まあ結局のところ常に
runOnUiThread
を使っておけば良さそう。
- JVMスレッドの問題はやはり
AttachCurrentThread
(実際にはjni::JavaVM::attach_current_thread
)を使っていたことが原因だったようで、代わりにGetEnv
を使う仕組みに切り替えたところ問題は解消 - はじめにクラスが見つからなかったのもここが原因の可能性が高いと思う
- Activityをサブクラス化する今のやり方のほうがスマートに思えるのでもとに戻す気はない