- 投稿日:2019-10-26T23:00:06+09:00
ARCoreアプリ開発環境構築&Pixel4で動作
はじめに
先日待望のPixel4が届きました。
以前使ってた端末はAndroid 8.0なのにARCoreが使えず、AR開発のツールが制限されていました。
これでようやくARCoreを使って色々開発できそうです。
まずは環境構築を一から行ったので、いつも通り備忘録として残しておきます。使用環境
- 開発用PC
- Windows 10 ver.1903
- Android Studio 3.5.1
- JRE: 1.8.0_202-release-1483-b03 amd64
- JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
- 検証端末
- Pixel4
- Android 10
環境構築
- Android StudioのHPの「DOWNLOAD ANDROID STUDIO」をクリックしてダウンロード
- 保存したインストーラを起動して、デフォルトの設定でAndroid Studioをインストール
- GoogleのARCore githubよりARCoreのサンプルプロジェクトをダウンロード。今回はsceneform-android-sdk-1.12.0.zip を利用
- ダウンロードしたファイルを任意のディレクトリで解凍。今回は「C:\Users\myaccount\AndroidStudioProjects」に解凍。
Android Studioを立ち上げ、「Open an Existing Android Studio Project」で任意のプロジェクトを選択。今回は「Argumented Image」を選択
GradleのBuildingなど様々な処理が完了した後、メニューの[Tools]→[SDK Manager]を選択
左メニューの[Appearance & Behavior]→[System Settings]→[Android SDK]を選択
[SDK Tools]タブの[Android SDK Tools]と[Android SDK Platform-Tools]がインストールされていなければ選択(おそらくデフォルトでインストール済み)
[OK]をクリックした後、[Accept]をクリック、インストールが開始される
SDKなどがインストールされた後、メニューの[Tools]→[SDK Manager]を選択
左メニューの[Plugins]を選択
[Marketplace]タブを選択し、検索窓で[sceneform]で検索
[Google Sceneform Tools (Beta)]が表示されるのでインストール(画像はインストール後のもの)
Android Studio IDEを再起動
サンプルアプリ起動
先ほど入手したARCoreのサンプルプロジェクトを実機にインストールする。
Pixel4の操作
- Pixel4にてGoogle Playを起動
- Google Play 開発者サービス(AR)がインストールされていることを確認
- 開発者向けオプションを有効化([設定]→[デバイス情報]へ進み、[ビルド番号]を7回タップ)
- [設定]→[システム]→[開発者向けオプション]に進み、デバッグ項目内の[USBデバッグ]を有効化
- Pixel4をPCに接続
開発用PCの操作
起動確認(Pixel4)
さいごに
これで環境構築は完了です。
環境構築の途中で導入したGoogle Sceneform Tools (Beta)は、Google Polyなどで入手した3Dモデルを導入する際に利用します(この記事のような事態を避けるため)。
また、Argumented Imageを使ってアプリ開発を進めていくので、開発が進んだら別記事で掲載します。参考サイト
- 投稿日:2019-10-26T19:20:10+09:00
Jetpack ComposeでHello WorldのLayoutNodeが追加されるまで
モチベーション
Jetpack ComposeのHello Worldは以下のような形になっています。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { Greeting("Android") } } } } @Composable fun Greeting(name: String) { Text(text = "Hello $name!") }この
@Composeで何が起こるのか。。? 実際には馴染みのAndroidのViewがあるはず。。?GreetingやTextはUnitしかこれ返してないけど、どうやって追加してるの。。?![]()
0.1.0-dev02で確認していきます
デバッグのミスなどで間違っている可能性などはかなりあるので、何かあればご指摘ください
![]()
コードを見ていく
LayoutNodeとは?
実際には馴染みのAndroidのViewがあるはず。。?
setContentの中は以下のようになっており、ComposeはAndroidComposeViewというViewに描画するようです。fun Activity.setContent( content: @Composable() () -> Unit ): CompositionContext? { val composeView = window.decorView .findViewById<ViewGroup>(android.R.id.content) .getChildAt(0) as? AndroidComposeView ?: AndroidComposeView(this).also { setContentView(it) } // **← ここでnewする!!**AndroidComposeViewは以下のように
rootのレイアウトを持っています。class AndroidComposeView constructor(context: Context) : ViewGroup(context), Owner, SemanticsTreeProvider, DensityScope { override var density: Density = Density(context) private set val root = LayoutNode().also { // **← rootのLayoutNode!!** it.measureBlocks = RootMeasureBlocks }このrootのNodeにTextが追加されるようです。
ちなみにこのLayoutNodeクラスはComponentNodeのサブクラスになっており、ComponentNodeは以下のように子codeを持ちます。sealed class ComponentNode : Emittable { internal val children = mutableListOf<ComponentNode>() // **← childrenを保持する!!**TextのLayoutNodeを追加しているのは誰か?
このchildrenの追加のタイミングにデバッガーを仕掛けてみます。
このようにTextが追加されているのがわかります。このTextKt$Text$3$2$1$clidren...、生成されている感があるので、気になります
このinstanceの値の出どころを探りましょう!どうやらこのup()メソッドから渡されてくるようです。そして、_currentの値が渡されてくるようなので、
down()の呼び元を調べます。
down()の呼び元としてemitNode()があるのですが、、emitNode()を呼び出しているところがスタックトレースでは追えない部分が出てきます。コンパイルされたコードの中から来るLayoutNode
コードを見ていくと実際にはViewComposer内の以下のような実装が適応されているようです。
inline fun <T : View> emit( key: Any, /*crossinline*/ ctor: (context: Context) -> T, update: ViewUpdater<T>.() -> Unit ) = with(composer) { startNode(key) // **ここにemitNodeがある** val node = if (inserting) ctor(context).also { emitNode(it) } else useNode() as T ViewUpdater<T>(this, node).update() endNode() } ... @Suppress("PLUGIN_WARNING") inline fun call( key: Any, /*crossinline*/ invalid: ViewValidator.() -> Boolean, block: @Composable() () -> Unit ) = with(composer) { startGroup(key) if (ViewValidator(composer).invalid() || inserting) { startGroup(invocation) block() endGroup() } else { skipCurrentGroup() } endGroup() }そこでコンパイラのコードをちょっと見ると以下のようなコメントを発見できます。
https://android.googlesource.com/platform/frameworks/support/+/a5396b43ef905756b18805fe0ae31a99e96e6df6/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposableCallTransformer.kt#239つまり
Foo(text="foo")をcomposer.call()に変換しています/* Foo(text="foo") // transforms into val attr_text = "foo" composer.call( key = 123, invalid = { changed(attr_text) }, block = { Foo(attr_text) } ) */つまり最初のMainActivityのコードは @Composeをなくして以下のように書くことができます。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { Greeting("Android") } } } } //@Composable //fun Greeting(name: String) { // Text(text = "Hello $name!") //} fun Greeting(name: String) { composer.call( key = 0x9a8fdf7d, invalid = { changed(name) }, block = { Text(text = "Hello $name!") } ) }
GreetingやTextはUnitしかこれ返してないけど、どうやって追加してるの。。?これと同じことがText()の中でも行われ、そこでcomposable.emit()に変換され、
emitNode()を呼んでいるようです。デバッグ方法のメモ
公開されているコードなのですが、かなりinline化されていてデバッガーで追いにくいです。またAndroid StudioについているKotlinのJavaへのデコンパイラではラムダを埋め込んでくれないので実質追えません。
brew cask install jadなどでjadをインストールして
./gradlew compileDebugKotlin
などをした後にcd app/build/tmp/kotlin-classes/debug/[パッケージ] jad *.classすることでラムダを埋め込んだコードを出してくれました。
まとめ
多分破壊的変更が何度も入っていくだろうとは思うので一瞬でこの知識は使えなくなるとは思いますが、ソースコードを追えるようになってきました。
- 投稿日:2019-10-26T19:20:10+09:00
Jetpack ComposeでHello WorldのLayoutNodeが追加されるまでを読んでみるメモ
普通に以下のセッションがめちゃくちゃわかりやすいのでそれを見ることをおすすめします
Understanding Compose (Android Dev Summit '19)
https://www.youtube.com/watch?v=Q9MtlmmN4Q0Understanding composeメモ
https://qiita.com/takahirom/items/e8fb7933fa44a546915fモチベーション
Jetpack ComposeのHello Worldは以下のような形になっています。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { Greeting("Android") } } } } @Composable fun Greeting(name: String) { Text(text = "Hello $name!") }この
@Composeで何が起こるのか。。? 実際には馴染みのAndroidのViewがあるはず。。?GreetingやTextはUnitしかこれ返してないけど、どうやって追加してるの。。?![]()
0.1.0-dev02で確認していきます
デバッグのミスなどで間違っている可能性などはかなりあるので、何かあればご指摘ください
![]()
コードを見ていく
LayoutNodeとは?
実際には馴染みのAndroidのViewがあるはず。。?
setContentの中は以下のようになっており、ComposeはAndroidComposeViewというViewに描画するようです。fun Activity.setContent( content: @Composable() () -> Unit ): CompositionContext? { val composeView = window.decorView .findViewById<ViewGroup>(android.R.id.content) .getChildAt(0) as? AndroidComposeView ?: AndroidComposeView(this).also { setContentView(it) } // **← ここでnewする!!**AndroidComposeViewは以下のように
rootのレイアウトを持っています。class AndroidComposeView constructor(context: Context) : ViewGroup(context), Owner, SemanticsTreeProvider, DensityScope { override var density: Density = Density(context) private set val root = LayoutNode().also { // **← rootのLayoutNode!!** it.measureBlocks = RootMeasureBlocks }このrootのNodeにTextが追加されるようです。
ちなみにこのLayoutNodeクラスはComponentNodeのサブクラスになっており、ComponentNodeは以下のように子codeを持ちます。sealed class ComponentNode : Emittable { internal val children = mutableListOf<ComponentNode>() // **← childrenを保持する!!**TextのLayoutNodeを追加しているのは誰か?
このchildrenの追加のタイミングにデバッガーを仕掛けてみます。
このようにTextが追加されているのがわかります。このTextKt$Text$3$2$1$clidren...、生成されている感があるので、気になります
このinstanceの値の出どころを探りましょう!どうやらこのup()メソッドから渡されてくるようです。そして、_currentの値が渡されてくるようなので、
down()の呼び元を調べます。
down()の呼び元としてemitNode()があるのですが、、emitNode()を呼び出しているところがスタックトレースでは追えない部分が出てきます。コンパイルされたコードの中から来るLayoutNode
コードを見ていくと実際にはViewComposer内の以下のような実装が適応されているようです。
inline fun <T : View> emit( key: Any, /*crossinline*/ ctor: (context: Context) -> T, update: ViewUpdater<T>.() -> Unit ) = with(composer) { startNode(key) // **ここにemitNodeがある** val node = if (inserting) ctor(context).also { emitNode(it) } else useNode() as T ViewUpdater<T>(this, node).update() endNode() } ... @Suppress("PLUGIN_WARNING") inline fun call( key: Any, /*crossinline*/ invalid: ViewValidator.() -> Boolean, block: @Composable() () -> Unit ) = with(composer) { startGroup(key) if (ViewValidator(composer).invalid() || inserting) { startGroup(invocation) block() endGroup() } else { skipCurrentGroup() } endGroup() }そこでコンパイラのコードをちょっと見ると以下のようなコメントを発見できます。
https://android.googlesource.com/platform/frameworks/support/+/a5396b43ef905756b18805fe0ae31a99e96e6df6/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/compiler/lower/ComposableCallTransformer.kt#239つまり
Foo(text="foo")をcomposer.call()に変換しています/* Foo(text="foo") // transforms into val attr_text = "foo" composer.call( key = 123, invalid = { changed(attr_text) }, block = { Foo(attr_text) } ) */つまり最初のMainActivityのコードは @Composeをなくして以下のように書くことができます。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { Greeting("Android") } } } } //@Composable //fun Greeting(name: String) { // Text(text = "Hello $name!") //} fun Greeting(name: String) { composer.call( key = 0x9a8fdf7d, invalid = { changed(name) }, block = { Text(text = "Hello $name!") } ) }
GreetingやTextはUnitしかこれ返してないけど、どうやって追加してるの。。?これと同じことがText()の中でも行われ、そこでcomposable.emit()に変換され、
emitNode()を呼んでいるようです。デバッグ方法のメモ
公開されているコードなのですが、かなりinline化されていてデバッガーで追いにくいです。またAndroid StudioについているKotlinのJavaへのデコンパイラではラムダを埋め込んでくれないので実質追えません。
brew cask install jadなどでjadをインストールして
./gradlew compileDebugKotlin
などをした後にcd app/build/tmp/kotlin-classes/debug/[パッケージ] jad *.classすることでラムダを埋め込んだコードを出してくれました。
まとめ
多分変更が何度も入っていくだろうとは思うので一瞬でこの知識は使えなくなるとは思いますが、ソースコードを追えるようになってきました。









