GoでAndroidアプリを作るためには
まずcgoの話からはじめて行きたいと思います。

環境

  • OS : Mac OS X 10.9.3
  • Go : go1.3 darwin/amd64 (brew install)
  • Android SDK : android-20
  • Android NDK : android-ndk-r10

執筆時点:2014/8/15

cgo

Cgo enables the creation of Go packages that call C code

http://golang.org/cmd/cgo/

GoからC/C++にアクセスするために用いるパッケージのこと。
つまり、Cで書かれたライブラリが、Goでも再利用できる。

基本的な使い方

 
まず、Cの関数や型にアクセスするために、cgoのimportを行う

import "C"  

import文の上にinclude <ほげほげ.h> と書けば
コンパイル時に自動で読み込まれるので、必要なヘッダを書く

// #include <stdio.h>
// #include <stdlib.h>
import "C"  

これで、C.int, *C.char、C.rand とかで、型や関数にアクセスできる

GoからCを呼ぶ

Sample1

sample1.go

package main  
/*
 #include <stdio.h>

 int total(int a, int b) {
  return a + b;
 }
*/
import "C"  
import "fmt"

func main() {  
  total := C.total(10, 20)
  fmt.Println(total)
}

Output

# go run sample2.go
30  

Sample2

コメントアウトにC言語のソースを書くとEditorとかで認識されないので、C言語は別のファイルにしても問題なく読み込まれます。

  • C言語のメソッドヘッダを作成

sample2.h

#include <stdio.h>

int total(int a, int b) {  
    return a + b;
}


sample2.go

package main  
// #include "sample2c.h"
import "C"  
import "fmt"

func main() {  
  total := C.total(10, 20)
  fmt.Println(total)
}

GoからObjective-Cを呼ぶ

Sample3

sample3.go

package main  
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation

#import <Foundation/Foundation.h>

void hello() {  
    NSLog(@"Hello objective cgo world!");
}
*/
import "C"

func main() {  
    C.hello()
}

Output

# go run sample3.go
2014-08-14 18:06:26.903 sample3objc[45354:303] Hello objective cgo world!  

参考:
http://unknownplace.org/archives/cgo-and-eventloop.html

golangでAndroidアプリを作る

 
英語:The State of Go Language for Android Native Development
日本語:Androidネイティブ開発に関するGo言語の状況  

Target Version
  • Android SDK : target=android-20
  • Android NDK : android-ndk-r10

toolchainの作成

Android Toolchain Standalone Compilerとは...
Android向けのライブラリ(.so)を作るときに使うgccとかです
 

# $ANDROID_NDK/build/tools/make-standalone-toolchain.sh \
# --toolchain=arm-linux-androideabi-4.9 --platform=android-L --install-dir=ndk-toolchain

goandroid

Goandroidとは...
go1.2にpatchをあてて、ARMv7+のAndroidでgoを動かせるようにしたもの
 

まずは、go1.2.2をhg clone

# hg clone -u go1.2.2 https://code.google.com/p/go

goandroidをgit clone

# git clone https://github.com/eliasnaur/goandroid.git
# cp -r patches go/.hg

Mercurialの設定ファイルに追記

go/.hg/hgrc

[extensions]  
mq =  
codereview = !

[ui]  
username = me<me@mail.com>  

goにpatchをあてる

# cd go/.hg
# hg qpush -a
applying shared-lib-runtime  
applying android-tls  
applying android-build-hacks  
now at: android-build-hacks  

golang make

# cd src
# CGO_ENABLED=0 GOOS=linux GOARCH=arm ./make.bash \
# CC="/Users/wasabeef/go/ndk-toolchain/bin/arm-linux-androideabi-gcc" \
# GOOS=linux GOARCH=arm GOARM=7 \
# CGO_ENABLED=1 ../bin/go install -tags android -a -v std  


(省略)

---
Installed Go for linux/arm in /Users/wasabeef/go/go1.2.2  
Installed commands in /Users/wasabeef/go/go1.2.2/bin  

version表示で動作確認

# ./bin/go version
go version go1.2.2 darwin/amd64  

Android NativeAcivity

ANativeActivity_onCreate関数で、golangの関数を登録

main.c

extern void onNativeWindowCreated(ANativeActivity *activity, ANativeWindow* window);  
extern void onNativeWindowDestroyed(ANativeActivity *activity, ANativeWindow* window);  
extern void onNativeWindowResized(ANativeActivity *activity, ANativeWindow* window);  
extern void onInputQueueCreated(ANativeActivity *activity, AInputQueue* queue);  
extern void onInputQueueDestroyed(ANativeActivity *activity, AInputQueue* queue);  
extern void onCreate(ANativeActivity *activity, void* savedState, size_t savedStateSize);  
extern void onDestroy(ANativeActivity *activity);  
extern void onResume(ANativeActivity *activity);  
extern void onPause(ANativeActivity *activity);  
extern void onConfigurationChanged(ANativeActivity *activity);  
extern void onWindowFocusChanged(ANativeActivity *activity, int focused);

void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_t savedStateSize) {  
    activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
    activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
    activity->callbacks->onInputQueueCreated = onInputQueueCreated;
    activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
    activity->callbacks->onDestroy = onDestroy;
    activity->callbacks->onResume = onResume;
    activity->callbacks->onPause = onPause;
    activity->callbacks->onConfigurationChanged = onConfigurationChanged;
    activity->callbacks->onNativeWindowResized = onNativeWindowResized;
    activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;

    onCreate(activity, savedState, savedStateSize);
}

登録したコールバックを実装していく

main.go

package main

// #include <stdlib.h>
// #include <jni.h>
// #include <android/native_activity.h>
// #include <android/input.h>
// #include <EGL/egl.h>
// #include "main.h"
//
// #cgo LDFLAGS: -landroid -lEGL
import "C"

import (  
    "fmt"
    "log"
    "runtime"
    "runtime/debug"
    "unsafe"
)

// (省略)

build.shの調整

主に、pathやarchの設定を環境によって調整

#!/bin/bash

set -e

mkdir -p android/libs/armeabi-v7a  
mkdir -p android/obj/local/armeabi-v7a  
CC="/Users/wasabeef/go/ndk-toolchain/bin/arm-linux-androideabi-gcc"

if [ -z $GOANDROID ]  
then  
  GOANDROID="../go"
fi

CC=$CC GOPATH="`pwd`:$GOPATH" GOROOT="" d=linux GOARCH=arm GOARM=7 CGO_ENABLED=1 /Users/a12622/go/go1.2.2/bin/go install $GOFLAGS -v -ldflags="-android -shared -extld $CC -extldflags '-march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16'" -tags android nativeactivity  
cp bin/linux_arm/nativeactivity android/libs/armeabi-v7a/libnativeactivity.so  
cp bin/linux_arm/nativeactivity android/obj/local/armeabi-v7a/libnativeactivity.so  

ant build

# ant -f android/build.xml clean debug install

Buildfile: /Users/wasabeef/go/goandroid/native-activity/android/build.xml

-check-env:
 [checkenv] Android SDK Tools Revision 23.0.2
 [checkenv] Installed at /Applications/android-sdk-macosx

-setup:
     [echo] Project Name: nativeactivity
  [gettype] Project Type: Application

-pre-clean:

clean:  
   [delete] Deleting directory /Users/wasabeef/go/goandroid/native-activity/android/bin
   [delete] Deleting directory /Users/wasabeef/go/goandroid/native-activity/android/gen
[getlibpath] Library dependencies:
[getlibpath] No Libraries
   [subant] No sub-builds to iterate on

-set-mode-check:

-set-debug-files:

-check-env:
 [checkenv] Android SDK Tools Revision 23.0.2
 [checkenv] Installed at /Applications/android-sdk-macosx

-setup:
     [echo] Project Name: nativeactivity
  [gettype] Project Type: Application

-set-debug-mode:

-debug-obfuscation-check:

-pre-build:

-build-setup:
[getbuildtools] Using latest Build Tools: 20.0.0
     [echo] Resolving Build Target for nativeactivity...
[gettarget] Project Target:   Android 4.4W
[gettarget] API level:        20
     [echo] ----------
     [echo] Creating output directories if needed...
    [mkdir] Created dir: /Users/wasabeef/go/goandroid/native-activity/android/bin
    [mkdir] Created dir: /Users/wasabeef/go/goandroid/native-activity/android/bin/res
    [mkdir] Created dir: /Users/wasabeef/go/goandroid/native-activity/android/bin/rsObj
    [mkdir] Created dir: /Users/wasabeef/go/goandroid/native-activity/android/bin/rsLibs
    [mkdir] Created dir: /Users/wasabeef/go/goandroid/native-activity/android/gen
    [mkdir] Created dir: /Users/wasabeef/go/goandroid/native-activity/android/bin/classes
    [mkdir] Created dir: /Users/wasabeef/go/goandroid/native-activity/android/bin/dexedLibs
     [echo] ----------
     [echo] Resolving Dependencies for nativeactivity...
[dependency] Ordered libraries:
[dependency]
[dependency] ------------------
     [echo] ----------
     [echo] Building Libraries with 'debug'...
   [subant] No sub-builds to iterate on

-code-gen:
[mergemanifest] Merging AndroidManifest files into one.
[mergemanifest] Manifest merger disabled. Using project manifest only.
     [echo] Handling aidl files...
     [aidl] No AIDL files to compile.
     [echo] ----------
     [echo] Handling RenderScript files...
     [echo] ----------
     [echo] Handling Resources...
     [aapt] Generating resource IDs...
     [echo] ----------
     [echo] Handling BuildConfig class...
[buildconfig] Generating BuildConfig class.

-pre-compile:

-compile:
    [javac] Compiling 2 source files to /Users/wasabeef/go/goandroid/native-activity/android/bin/classes

-post-compile:

-obfuscate:

-dex:
      [dex] input: /Users/wasabeef/go/goandroid/native-activity/android/bin/classes
      [dex] Converting compiled files and external libraries into /Users/wasabeef/go/goandroid/native-activity/android/bin/classes.dex...

-crunch:
   [crunch] Crunching PNG Files in source dir: /Users/wasabeef/go/goandroid/native-activity/android/res
   [crunch] To destination dir: /Users/wasabeef/go/goandroid/native-activity/android/bin/res
   [crunch] Crunched 0 PNG files to update cache

-package-resources:
     [aapt] Creating full resource package...

-package:
[apkbuilder] Current build type is different than previous build: forced apkbuilder run.
[apkbuilder] Creating nativeactivity-debug-unaligned.apk and signing it with a debug key...

-post-package:

-do-debug:
 [zipalign] Running zip align on final apk...
     [echo] Debug Package: /Users/wasabeef/go/goandroid/native-activity/android/bin/nativeactivity-debug.apk
[propertyfile] Creating new property file: /Users/wasabeef/go/goandroid/native-activity/android/bin/build.prop
[propertyfile] Updating property file: /Users/wasabeef/go/goandroid/native-activity/android/bin/build.prop
[propertyfile] Updating property file: /Users/wasabeef/go/goandroid/native-activity/android/bin/build.prop
[propertyfile] Updating property file: /Users/wasabeef/go/goandroid/native-activity/android/bin/build.prop

-post-build:

debug:

install:  
     [echo] Installing /Users/wasabeef/go/goandroid/native-activity/android/bin/nativeactivity-debug.apk onto default emulator or device...
     [exec] 6040 KB/s (809846 bytes in 0.130s)
     [exec]   pkg: /data/local/tmp/nativeactivity-debug.apk


     [exec] Success

BUILD SUCCESSFUL  
Total time: 1 minute 2 seconds  

動かしてみた

雑感

ArtVMを積んでるAndroid Lでは上手くうごかなかった。

Genymotionなどのエミュレータではx86で動いてるけど
gccのoptionで-marhc=x86とかにしても"Fatal Signal 11"系で動かなかった。
goandroidがarm用だったからかな・・

結論、Go+Androidは正式Supportされるまで、待つ他ないと思います。

(2014/8/15)