Just port a Golang game to Android 
Tags: gomobile, game, korok, go
Published: 2018-08-18 07:11:21

---
title: Just port a Golang game to Android 
published: true
description: Everything you should know to port Golang game to Android
tags: gomobile,game,korok,go
---

Months ago, @hajimehoshi wrote a post [Go Packages we developed for our games](https://dev.to/hajimehoshi/go-packages-we-developed-for-our-games--4cl9), he use `gomobile bind` to port Go games to Android in his engine - ebiten. But here, I'll use `gomobile build`, `gomobile build` can pack `.apk` directly and need no other dependencies.

I have written a game engine to implement some basic game logic. But I won't talk about it (for anyone interested: [https://korok.io](https://korok.io)) today, I'll talk about the problems we meet when using 'gomobile build' command.

Here is 2048 game I made: 
![2048](https://thepracticaldev.s3.amazonaws.com/i/c4v1s8kz6fs5l879wbyf.png)

You can get it on itch.io: [The 2048 Game](https://ntop.itch.io/the-2048-game)

## How to make a fullscreen/NoTitleBar screen?

The first problem is to make a full screen, `gomobile` does't provide any configuration to do this.  After reading the [gomobile wiki](https://github.com/golang/go/wiki/Mobile), I know that I can create an `AndroidManifest.xml` file at the root directory in my project. `gomobile` will include it in android APK file.

This is the manifest file I used in my project:

```
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          android:versionCode="1" android:versionName="1.0" package="io.korok.ad2048">
    <application android:label="2048 Game">
        <activity android:label="2048 Game"
                  android:name="org.golang.app.GoNativeActivity"
                  android:configChanges="keyboardHidden|orientation"
                  android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
                  android:screenOrientation="portrait">
            <meta-data android:name="android.app.lib_name" android:value="2048" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
```

In the file, `android:theme="@android:style/Theme.NoTitleBar.Fullscreen"` will make a full screen.

## Where to store my game data ?

In my 2048 game, I need to store the score value in local storage. As an Android developer once, I known there are three ways to store data in Android:

1. File 
2. SharedPreferences
3. SQLite

There is no wrapper method in Go. But, if I can find a writable file directory, I can use File system. After reading the source code of `android.go`, I see that `gomobile` has a environment value -- 'TMPDIR', which represent a temporary file directory(only works on Android):

```
var filepath = os.Getenv("TMPDIR")
```

Then, I can create file and store data.

## How to get the system language?

If you installed my 2048 games, You'll find that it supports multi-language(Chinese and English), I implemented this by getting the system language. Getting system language is harder than getting a writable file path, it needs writing glue method: Go -> Cgo -> Java. I'll also lean JNI to call java method from C, fortunately, JNI is like java reflection, very easy！

Here is the code to get system language:
```
/*
#cgo LDFLAGS: -landroid

#include <jni.h>
#include <stdlib.h>
#include <string.h>

// Equivalent to:
// String lan = Locale.getDefault().getLanguage();
char* kk_getLanguage(uintptr_t java_vm, uintptr_t jni_env, jobject ctx) {
	JavaVM* vm = (JavaVM*)java_vm;
	JNIEnv* env = (JNIEnv*)jni_env;

	jclass locale_clazz = (*env)->FindClass(env, "java/util/Locale");
	jmethodID getdft_id = (*env)->GetStaticMethodID(env, locale_clazz, "getDefault", "()Ljava/util/Locale;");
	jobject locale = (*env)->CallStaticObjectMethod(env, locale_clazz, getdft_id);

	jmethodID getlang_id = (*env)->GetMethodID(env, locale_clazz, "getLanguage", "()Ljava/lang/String;");
	jobject lang = (*env)->CallObjectMethod(env, locale, getlang_id);
	const char* str = (*env)->GetStringUTFChars(env, (jstring) lang, NULL);
	char * retString = strdup(str);
	(*env)->ReleaseStringUTFChars(env, (jstring)lang, str);
	return retString;
}
 */
import "C"
import (
	"golang.org/x/mobile/app"
	"unsafe"

)

func Language() string {
	return deviceAttr.Lang(func() string {
		var ret string
		app.RunOnJVM(func(vm, jniEnv, ctx uintptr) error {
			cstring := C.kk_getLanguage(C.uintptr_t(vm), C.uintptr_t(jniEnv), C.jobject(ctx))
			ret = C.GoString(cstring)
			C.free(unsafe.Pointer(cstring))
			return nil
		})
		return ret
	})
}
```

Note: `app.RunOnJVM` method is a useful method to access JNI, the latest version of gomobile has this method.

## The Back Key!

When I installed my game on an Android device, I found that it'll just exit if I press the Back key. It's not a normal behavior when I'm playing and touch the Back accidently. Most Android games will have a 'Double Click to Exit' mechanism to ensure you really want to exit the game.

The Good news is you can gets the key event from gomobile, the Bad news is gomobile just ignores the Back key. There is a function called `convAndroidKeyCode` in `android.go` which maps the Back key as Unknown. To solve the problem, I have to changes the code in gomobile, I did this in a new branch(thanks git!!). 

```
// event/key/key.go
// generate different Codes (but the same Rune).
 	Code Code
 
+	// Origin is the original value of the key code.
+	Origin int
+
 	// Modifiers is a bitmask representing a set of modifier keys: ModShift,
 	// ModAlt, etc.
 	Modifiers Modifiers

// app/android.go
k := key.Event{
 		Rune: rune(C.getKeyRune(env, e)),
 		Code: convAndroidKeyCode(int32(C.AKeyEvent_getKeyCode(e))),
+		Origin: int(C.AKeyEvent_getKeyCode(e)),
 	}
 	switch C.AKeyEvent_getAction(e) {
 	case C.AKEY_STATE_DOWN:
```

Adding a new filed 'Origin', I can use it to get the original key code value. 
```
case key.Event:
    if e.Origin == 4 {
           // do something
    }
```
Note: The Back key's value is 4 on Android. You need also rebuild the `gomobile` tools to make it works.

I can get the Back key event, but the game still exit. To change the default Back key behavior on Android, need to override the `OnBackPressed` method in `Acitivty` class.

There is a `java` file in gomobile package called `GoNativeActivity.java`, this is where the Activity declared. Add the following method:
```
    @Override
    public void onBackPressed() {
        Log.e("Go", "Override Back key");
    }
```

Note: You need also regenerate the `dex.go` file with `go run gendex.go`, then rebuild the `gomobile` again. 

## How to Exit the app, manually?

Now, we override the default Back key event, and can get Back key event, what should we do next? A simple way to do this is call `panic` method, just kill the process with an exception. Or we can call the `Activity.finish()` method with glue method, I have written some JNI method, it not that hard.

```
/*
#cgo LDFLAGS: -landroid

#include <jni.h>
#include <stdlib.h>
#include <string.h>

// Equivalent to:
// Activity.Finish()
void kk_finish(uintptr_t java_vm, uintptr_t jni_env, jobject ctx) {
	JavaVM* vm = (JavaVM*)java_vm;
	JNIEnv* env = (JNIEnv*)jni_env;
	jclass clazz = (*env)->GetObjectClass(env, ctx);
	jmethodID finish_id = (*env)->GetMethodID(env, clazz, "finish", "()V");
	(*env)->CallVoidMethod(env, ctx, finish_id);
}
 */
import "C"
import (
	"golang.org/x/mobile/app"
	"unsafe"

)


func Quit() {
	app.RunOnJVM(func(vm, jniEnv, ctx uintptr) error {
		C.kk_finish(C.uintptr_t(vm), C.uintptr_t(jniEnv), C.jobject(ctx))
		return nil
	})
}
```

I also make a 'Double Click Exit':
![Click Back Again to Quit!](https://thepracticaldev.s3.amazonaws.com/i/clrptnqaj2avl0nv82i5.png)

## Preserve eglContext!!

If I pressed the Home button on an Android device, the system'll bring my game to back. Then click the game icon again, the system'll bring my game to front, but, it's a white screen!!! Nothing is showing!!! I have searched a lot about this bugly behavior, it seems that Android apps will lost eglContext when paused. Ebiten has some code to restore the eglContext, reload all shaders/texutres... But I think it's too complicated to implement this, SDL2 use setPreserveEGLContextOnPause method, I can implement the same logic on gomobile, too.

In `android.c` file, add a new global variable `EGLContext context = NULL;`, then reuses it if it's valid or creates it if it's invalid.

```
// app/android.c
 EGLDisplay display = NULL;
 EGLSurface surface = NULL;
+EGLContext context = NULL;

-	const EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
-	context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
+    if (context == NULL) {
+        const EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+        context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
+    }
```  
These changes works very well. Now, my game can correctly paused and resumed.

Note: You need to rebuild the `gomobile` tools again!

## How to set 'targetSdkVersion '?

I thought I have fixed all the bug and successfully build an APK file. But when I upload the APK file to GooglePlay, it always fails with an error message -"you need to target Android version 26"!!

As an experienced Android developer, I know how to fix it, just add 'targetSdkVersion=26' in 'AndroidManifest.xml' will works. But, unfortunately, it failed, gomoile complains that:

```
manual declaration of uses-sdk in AndroidManifest.xml not supported
``` 

Gomobile must did some weird logic when building APK file. After digging into the `build_androidapp.go` and `binres.go` file, I found that gombile checks the 'uses-sdk' element and fails the build process:

```
case "uses-sdk":
	return nil, fmt.Errorf("manual declaration of uses-sdk in AndroidManifest.xml not supported")
case "manifest":
```
I also found where 'gomobile' set the 'minSdkVersion', but, it never set the `targetSdkVersion`, so I add some code:

```
if !skipSynthesize {
	s := xml.StartElement{
		Name: xml.Name{
			Space: "",
			Local: "uses-sdk",
		},
		Attr: []xml.Attr{
			xml.Attr{
				Name: xml.Name{
					Space: androidSchema,
					Local: "minSdkVersion",
				},
				Value: fmt.Sprintf("%v", MinSDK),
			},
			xml.Attr{
				Name: xml.Name{
					Space: androidSchema,
					Local: "targetSdkVersion",
				},
				Value: fmt.Sprintf("%v", 26),
			},
		},
	}
```
Finally, I have to say, it's a long way, I fixed all the problem, and upload it to GooglePlay. It's welcomed to download my 2048 games, it's 99% Golang、no ads、clean design、smooth animation... Yeah, just install, I'm really happy to hear that. 

Here is the link to itch.io: [The 2048 Game](https://ntop.itch.io/the-2048-game)

I also create serval issues in Github, but with no respond now:

1. [x/mobile: manual declaration of uses-sdk in AndroidManifest.xml not supported](https://github.com/golang/go/issues/27023)
2. [x/mobile: Can't get the Back key event on Android ](https://github.com/golang/go/issues/26985)
3. [x/mobile: preserve eglContext when activity paused on Android](https://github.com/golang/go/issues/26962)
4. [x/mobile: lifecycle.StageDead never reach on Android ](https://github.com/golang/go/issues/26959)