Running Go from Android/iOS — Tutorial

Xyz Zyx
7 min readSep 30, 2018

--

super app

Step 1:

Getting the minimum necessary know-how

Let’s start with creating a simple package in go

You notice that you can’t go crazy with functions, because gobind (the tool we’re going to use only supports.

Quickly read the info below so you get a feeling what is going on there.

Command gobind

Gobind generates language bindings that make it possible to call Go functions from Java and Objective-C.

Binding Go

Gobind generates target language (Java or Objective-C) bindings for each exported symbol in a Go package. The Go package you choose to bind defines a cross-language interface.

Bindings require additional Go code be generated, so using gobind manually requires calling it twice, first with -lang=<target>, where target is either java or objc, and again with -lang=go. The generated package can then be _ imported into a Go program, typically built with -buildmode=c-archive for iOS or -buildmode=c-shared for Android. These details are handled by the `gomobile bind` command.

Passing Go objects to target languages

Consider a type for counting:

package mypkgtype Counter struct {
Value int
}
func (c *Counter) Inc() { c.Value++ }func NewCounter() *Counter { return &Counter{ 5 } }

In Java, the generated bindings are,

public abstract class Mypkg {
public static native Counter newCounter();
}

and

public final class Counter {
public Counter() { ... }
public final long getValue();
public final void setValue(long v);
public void inc();
}

The package-level function newCounter can be called like so:

Counter c = Mypkg.newCounter()

For convenience, functions on the form NewT(…) *T are converted to constructors for T:

Counter c = new Counter()

Both forms returns a Java Counter, which is a proxy for a Go *Counter. Calling the inc, getValue and setValue methods will call the Go implementations of these methods.

Similarly, the same Go package will generate the Objective-C interface

@class GoMypkgCounter;@interface GoMypkgCounter : NSObject {
}
@property(strong, readonly) id ref;
- (void)inc;
- (int64_t)value;
- (void)setValue:(int64_t)v;
@end
FOUNDATION_EXPORT GoMypkgCounter* GoMypkgNewCounter(void);

The equivalent of calling newCounter in Go is GoMypkgNewCounter in Objective-C. The returned GoMypkgCounter* holds a reference to an underlying Go *Counter.

Passing target language objects to Go

For a Go interface:

package myfmttype Printer interface {
Print(s string)
}
func PrintHello(p Printer) {
p.Print("Hello, World!")
}

gobind generates a Java interface that can be used to implement a Printer:

public abstract class Myfmt {
public static void printHello(Printer p0);
}

and

public interface Printer {
public void print(String s);
}

You can implement Printer, and pass it to Go using the printHello package function:

public class SysPrint implements Printer {
public void print(String s) {
System.out.println(s);
}
}

The Java implementation can be used like so:

Printer printer = new SysPrint();
Myfmt.printHello(printer);

For Objective-C binding, gobind generates a protocol that declares methods corresponding to Go interface’s methods.

@protocol GoMyfmtPrinter
- (void)Print:(NSString*)s;
@end
FOUNDATION_EXPORT void GoMyfmtPrintHello(id<GoMyfmtPrinter> p0);

Any Objective-C classes conforming to the GoMyfmtPrinter protocol can be passed to Go using the GoMyfmtPrintHello function:

@interface SysPrint : NSObject<GoMyfmtPrinter> {
}
@end
@implementation SysPrint {
}
- (void)Print:(NSString*)s {
NSLog("%@", s);
}
@end

The Objective-C implementation can be used like so:

SysPrint* printer = [[SysPrint alloc] init];
GoMyfmtPrintHello(printer);

Type restrictions

At present, only a subset of Go types are supported.

All exported symbols in the package must have types that are supported. Supported types include:

- Signed integer and floating point types.- String and boolean types.- Byte slice types. Note that byte slices are passed by reference,
and support mutation.
- Any function type all of whose parameters and results have
supported types. Functions must return either no results,
one result, or two results where the type of the second is
the built-in 'error' type.
- Any interface type, all of whose exported methods have
supported function types.
- Any struct type, all of whose exported methods have
supported function types and all of whose exported fields
have supported types.

Unexported symbols have no effect on the cross-language interface, and as such are not restricted.

The set of supported types will eventually be expanded to cover more Go types, but this is a work in progress.

Exceptions and panics are not yet supported. If either pass a language boundary, the program will exit.

Reverse bindings

Gobind also supports accessing API from Java or Objective C from Go. Similar to how Cgo supports the magic “C” import, gobind recognizes import statements that start with “Java/” or “ObjC/”. For example, to import java.lang.System and call the static method currentTimeMillis:

import "Java/java/lang/System"t := System.CurrentTimeMillis()

Similarly, to import NSDate and call the static method [NSDate date]:

import "ObjC/Foundation/NSDate"d := NSDate.Date()

Gobind also supports specifying particular classes, interfaces or protocols a particular Go struct should extend or implement. For example, to create an Android Activity subclass MainActivity:

import "Java/android/app/Activity"type MainActivity struct {
app.Activity
}

Gobind also recognizes Java interfaces as well as Objective C classes and protocols the same way.

For more details on binding the the native API, see the design proposals, https://golang.org/issues/16876 (Java) and https://golang.org/issues/17102 (Objective C).

Avoid reference cycles

The language bindings maintain a reference to each object that has been proxied. When a proxy object becomes unreachable, its finalizer reports this fact to the object’s native side, so that the reference can be removed, potentially allowing the object to be reclaimed by its native garbage collector. The mechanism is symmetric.

However, it is possible to create a reference cycle between Go and objects in target languages, via proxies, meaning objects cannot be collected. This causes a memory leak.

For example, in Java: if a Go object G holds a reference to the Go proxy of a Java object J, and J holds a reference to the Java proxy of G, then the language bindings on each side must keep G and J live even if they are otherwise unreachable.

We recommend that implementations of foreign interfaces do not hold references to proxies of objects. That is: if you implement a Go interface in Java, do not store an instance of Seq.Object inside it.

Further reading

Examples can be found in http://golang.org/x/mobile/example.

===========

Step 2:

Generate the bindings

$ go get golang.org/x/mobile/cmd/gomobile
$ gomobile init # it might take a few minutes
or gomobile init -ndk=PATH_TO_ANDROID_NDK

Generate the bindings

$gomobile bind -target=android

(replace with ios if needed, and read this https://github.com/golang/go/wiki/Mobile#tools on how to import them into Xcode)

it will generate 2 files. gotomobile.aar and gotomobile-sources.jar

Step 3:

Run them from android

Copy the aar file and the gotomobile-sources.jar into the libs folder of you android application.

Modify the build.gradle file of the app so it includes aar files.

implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')

for example mine looks like this:

apply plugin: 'com.android.application'

android {
compileSdkVersion 28
defaultConfig {
applicationId "com.mex.integrationapp"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'


implementation 'com.squareup.okhttp:okhttp:2.7.2'

// Testing

testImplementation 'junit:junit:4.12'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test:rules:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2', {
exclude group: 'com.android.support', module: 'support-annotations'
}
androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.2'
}

notice the okhttp dependency, we’ll use it later.

Now open the class Gotomobile and you can use the generated class wherever you want

public abstract class Gotomobile {
static {
Seq.touch(); // for loading the native library
_init();
}

private Gotomobile() {} // uninstantiable

// touch is called from other bound packages to initialize this package
public static void touch() {}

private static native void _init();



public static native String returns2Arguments(String firstName, String LastName) throws Exception;
public static native void sayHello();
public static native void sayHelloWithName(String name);
public static native void thisReturnsAnError(String name) throws Exception;
}

You should play alittle with it before moving to Chapter 2 of this tutorial

Chapter 2

more advanced shit

What if we import some more complex packages ? will this work too.

Mostly yes! Let’s import gin and create a server. I guess this can be counted as pretty complex right.

package gotomobile

import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)

func RunServer() {
router := gin.Default()

router.GET("/welcome", func(c *gin.Context) {
firstname := c.DefaultQuery("firstname", "Guest")
lastname := c.Query("lastname")

c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})
router.Run(":8080")
}

func SayHello() {
fmt.Println("Hello!")
}

func SayHelloWithName(name string) {
fmt.Println("Hello! " + name)
}

// this returns an error if name is not x
func ThisReturnsAnError(name string) error {
if name != "x" {
return fmt.Errorf("name is not x")
}
return nil
}

func Returns2Arguments(firstName, LastName string) (string, error) {
if LastName != "x" {
return "", fmt.Errorf("eerror")
}
return LastName + LastName, nil
}

// This will not work. Second result value must be of type error
//func Returns2Arguments(firstName, LastName string) (string, string) {
// return LastName, firstName
//}

Generate the bindings:

$gomobile bind -target=android

copy paste the files into your project. (if you plan to do this often maybe you can right a small script to do this automatically)

So we have a server, this will block when it will run, so we should move it to some background thread. I’ll use asynctask for now, but you should investigate other options

static class RunServerTask extends android.os.AsyncTask {
@Override
protected Object doInBackground(Object[] objects) {
Gotomobile.runServer();
return null;
}
}

and in your Activity -> new RunServerTask().execute();

now you can call your server. I’ll use okhttp, here’s an example:

Congratulations! You did it!
If you liked what you learned reading this article, please give it a clap.

--

--

Xyz Zyx
Xyz Zyx

Responses (1)