新しく入ったReverse Binding機能を使ってPure GoでAndroidアプリを作ってみる #golang #gomobile

December 1, 2016

この記事はQiitaの記事をエクスポートしたものです。内容が古くなっている可能性があります。

はじめに

Go MobileというGoをモバイルアプリ開発に用いるというGoのサブリポジトリであるgolang.org/x以下で進められているプロジェクトがあります。

Go Mobile には、NativeアプリとSDKアプリという2種類の開発方法があり、NativeアプリはすべてをGoで書き、SDKアプリはJavaやObjective-CからGoの関数などを呼び出すというものでした。

これらの違いや詳しいやり方については、DroidKaigi 2016の資料やGopherCon 2016の資料ビデオ)などを確認していただければと思います。

Go MobileのNativeアプリでは、Goですべてを書けるものの、UIをOpenGLの関数を使って書く必要があり、せっかくAndroid APIがJava向けに提供されているにもかかわらず、Go Mobileからは直接は利用ができませんでした。

また、SDKアプリではJavaからGoを呼び出せるものの、GoからJavaのAPIを呼び出す方法がなく、NativeアプリもSDKアプリもこれじゃない感が否めませんでした。

ここでは、最近入ったJavaのAPIのバインディングを自動で作成する、Reverse Bindingの機能を使って、Javaで提供されているAndroid APIをGoから呼び出すサンプルを動かしてみます。

なお、Android SDKが必要となりますので、最新版のAndroid Studioをインストールするなりしておいて下さい。

Go Mobileのインストール

gomobileコマンドをgo getしてきます。

$ go get -u golang.org/x/mobile/cmd/gomobile

変更したものをgo installでビルドして反映させます。

$ go install golang.org/x/mobile/cmd/gomobile

もし、$GOPATH/bin以下にPATHを通してない場合は事前にパスを通して下さい。

$ export PATH=$PATH:$GOPATH/bin

gomobile initを呼び出して、初期化を行います。

$ gomobile init -v

もし、すでに過去にGo Mobileをインストールしたことがある方は、gomobile initの前に以下のようにキレイにしておくことをオススメします。

$ gomobile clean

サンプルを動かしてみる

今回使用するサンプルは、Go Mobileのリポジトリのexample/reverseGithubのリンク)に入っています。

Android Studioでexample/reverse/androidを開いてビルドする方法もありますが、ここではターミナルで直接gradleを使ってビルドする方法を説明します。 ちなみに、Android Studioでは開いてビルドボタンを押すだけで大丈夫です。

まず、gradleが入ってない場合はインストールします。Macの場合はbrew installで入ります。

$ brew install gradle

続いてディレクトリを移動します。

$ cd $GOPATH/src/golang.org/x/mobile/example/reverse/android

Android SDKがインストールされている場所をANDROID_HOMEに設定します。 毎回実行するのがめんどくさい場合は、.bashrc.zshrcに設定しておくと良いでしょう。

$ export ANDROID_HOME=$HOME/Library/Android/sdk

gradlewを生成します。

$ gradle wrapper

ビルドには上記で生成したgradlew(Windowsの場合はgradlew.bat)を使います。

$ ./gradlew build

うまくいくとbuild/outputs/apkapkファイルができているはずです。

$ ls build/outputs/apk
android-debug.apk            android-release-unsigned.apk

デバッグビルドがandroid-debug.apkでリリースビルドがandroid-release-unsigned.apkです。

Android機に入れて実行してみましょう。

$ adb install -r build/outputs/apk/android-debug.apk

実行してみると以下のような画面が出て来るはずです。

ぱっと見はAndroidアプリっぽい感じになってますが、Goのコードはどうなっているんでしょうか?

ソースコードを読んでみる

Goのコードは、example/reverse/reverse以下に入っています。

$ cd $GOPATH/src/golang.org/x/mobile/example/reverse/reverse
$ ls
reverse.go

reverse.goの中身を見てましょう。

package reverse

import (
        "Java/android/databinding/DataBindingUtil"
        "Java/android/os"
        "Java/android/support/v7/app"
        rlayout "Java/go/reverse/R/layout"
        "Java/go/reverse/databinding/ActivityMainBinding"
)

type MainActivity struct {
        app.AppCompatActivity
}

func (a *MainActivity) OnCreate1(this app.AppCompatActivity, b os.Bundle) {
        this.Super().OnCreate1(b)
        db := DataBindingUtil.SetContentView2(this, rlayout.Activity_main)
        mainBind := ActivityMainBinding.Cast(db)
        mainBind.SetAct(this)
}

func (a *MainActivity) GetLabel() string {
        return "Hello from Go!"
}

Java/で始まるパッケージをインポートしていることが分かります。 これらのパッケージはどこからインポートされるのでしょうか?

実はGOPATHの中を探してもこれらのパッケージはありませんし、どこかにこれらのパッケージが公開されていてgo getするわけでもありません。 また、よく見るとJava/の後ろはJavaのパッケージ名またはパッケージ名+クラス名になっていることが分かります。 Reverse Bindingという機能を使って、このインポート文から自動でバインディングが生成されます。 Java/android/databinding/DataBindingUtilのメソッドを呼び出すと、JNI経由でandroid.databinding.DataBindingUtilが呼び出されるようなバインディングがビルド時に自動的に生成されます。

Java/go/reverse/R/layoutについては、example/reverse/android/src/main/res/layout/以下で定義されているレイアウトに対応します。ActivityMainBindingについては、Androidバインディングの機構で生成されるクラスのようです。

このサンプルを見ると、MainActivityapp.AppCompatActivityを埋め込んでいます。 これは、Javaの継承に対応するコードで、onCreateをオーバーライドしています。 onCreateonCreate1となっているのは、Javaにはオーバーロードの機能があり、Goには無いため、メソッド名が被る場合は引数の個数を後ろにつけるルールでバインディングが生成されるからです。 この場合、引数が2つに見えますが、実際にはJava側のOnCreate(bundle Bundle)に対応しているので、1になっています。

第1引数のthisについては、他のJavaのメソッドの引数に渡すためのレシーバに対応するオブジェクトであり、この場合はDataBindingUtil.SetContentView2mainBind.SetActに渡すために使用されます。 また、Superを呼び出すとJava上のsuperに当たるオブジェクトを取得することができ、スーパークラスのメソッドなどを呼び出すことができます。

詳しい説明については、Reverse Bindingのプロポーザルを読むとよいでしょう。

まとめ

さて、いかがだったでしょうか? GoでAndroidアプリを書くという話がだんだんと現実的なところまできてるな感じる機能です。 もちろん、まだまだクリアしないといけない課題もあり、Experimentalなプロジェクトであることは変わりませんが、今後が楽しみです。 今回説明できなかった、生成されたバインディングやどうやってバインディングを作るのかといった話やiOSについてはまた別の記事にまとめようと思います。