Problem
I've the below simple go server that is running at my laptop (Mac/Windows/Linux):
package main
import (
"fmt"
"log"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi there %s!", r.URL.Path[1:])
}
func main() {
http.HandleFunc("/", handler)
log.Println(http.ListenAndServe("localhost:6060", nil))
}Can I use the same codebase to run my app at mobile webview, without using gomobile or other packages, so I've my code as universal app?
Solution
The answer is "Yes", but some slight modification to the file itself is required.
- Remove everything from the
func main() {}as we'll build the final result as a shared library, not as an executable binary. - Run the server in an
//exportfunction. - Run the server from an
anonymous goroutineasgo func() {}()so it is not blocking the main thread of the mobile app. - To keep the server gorotine running, we need to use a chanel as
<-cto prevent the gorotine from exit. - Use
cgoby addingimport "C", so the main file become like this:
package main
import "C"
// other imports should be seperate from the special Cgo import
import (
"fmt"
"log"
"net/http"
)
//export server
func server() {
c := make(chan bool)
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
<-c
}()
http.HandleFunc("/", handler)
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi there %s!", r.URL.Path[1:])
}
func main() {}- Ensure to have Androd
NDKinstalled, and you know its bath - Build the
c-sharedoutput with an output name aslibxxx, to build forAndroiduse:
CGO_ENABLED=1 \
GOOS=android \
GOARCH=arm \
GOARM=7 \
CC=$(NDK_BIN)/armv7a-linux-androideabi21-clang \
go build -buildmode=c-shared -o libfoo.so http.goWait As android has multiple architectures, we need to compile for each one individually, so we can have all the process automated in a Makefile as below after creating the android app by selecting Native C++ from the project templates, below the output library name is libfoo and 2 files will be generated in each folder libfoo.so and libfoo.h:
#Filename: Makefile
# To compile run:
# make android
IOS_OUT=lib/ios
ANDROID_OUT=../android_app/app/src/main/jniLibs
ANDROID_SDK=$(HOME)/Library/Android/sdk
NDK_BIN=$(ANDROID_SDK)/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin
android-armv7a:
CGO_ENABLED=1 \
GOOS=android \
GOARCH=arm \
GOARM=7 \
CC=$(NDK_BIN)/armv7a-linux-androideabi21-clang \
go build -buildmode=c-shared -o $(ANDROID_OUT)/armeabi-v7a/libfoo.so ./cmd/libfoo
android-arm64:
CGO_ENABLED=1 \
GOOS=android \
GOARCH=arm64 \
CC=$(NDK_BIN)/aarch64-linux-android21-clang \
go build -buildmode=c-shared -o $(ANDROID_OUT)/arm64-v8a/libfoo.so ./cmd/libfoo
android-x86:
CGO_ENABLED=1 \
GOOS=android \
GOARCH=386 \
CC=$(NDK_BIN)/i686-linux-android21-clang \
go build -buildmode=c-shared -o $(ANDROID_OUT)/x86/libfoo.so ./cmd/libfoo
android-x86_64:
CGO_ENABLED=1 \
GOOS=android \
GOARCH=amd64 \
CC=$(NDK_BIN)/x86_64-linux-android21-clang \
go build -buildmode=c-shared -o $(ANDROID_OUT)/x86_64/libfoo.so ./cmd/libfoo
android: android-armv7a android-arm64 android-x86 android-x86_64- Go to
android_app/app/src/main/cppand do the following: 8.1. FileCMakeLists.txt, make it as:
cmake_minimum_required(VERSION 3.10.2)
project("android")
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp )
add_library(lib_foo SHARED IMPORTED)
set_property(TARGET lib_foo PROPERTY IMPORTED_NO_SONAME 1)
set_target_properties(lib_foo PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libfoo.so)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}/)
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
target_link_libraries( # Specifies the target library.
native-lib
lib_foo
# Links the target library to the log library
# included in the NDK.
${log-lib} )8.2. File native-lib.cpp make it as:
#include <jni.h>
#include <string>
#include "libfoo.h" // our library header
extern "C" {
void
Java_tk_android_MainActivity_serverJNI() {
// Running the server
server();
}
}- Add webview to the
layout/activity_main, as:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:id="@+id/wv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:isScrollContainer="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>- Update the
MainActivityas below:
package tk.android
import android.os.Bundle
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var wv = findViewById<WebView>(R.id.web_view)
serverJNI()
wv.loadUrl("http://127.0.0.1:6060/")
wv.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(viewx: WebView, urlx: String): Boolean {
viewx.loadUrl(urlx)
return false
}
}
}
private external fun serverJNI(): Void
companion object {
// Used to load the 'native-lib' library on application startup.
init {
System.loadLibrary("native-lib")
}
}
}- Update
AndroidManifestto be:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="tk.android">
<!-- Mandatory:
android:usesCleartextTraffic="true"
Optional:
android:hardwareAccelerated="true"
Depending on the action bar required:
android:theme="@style/Theme.AppCompat.NoActionBar"
-->
<application
android:hardwareAccelerated="true" // <- Optional
android:usesCleartextTraffic="true" // <- A must to be added
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.NoActionBar"> // <- If do not want action bar
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize"> // <- A Must to avoid crash at rotating
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>Bonus
With Go embed all static files can be embed in the same library, including css, javascript, templates so you can buid either API, or full app with GUI
I uploaded the main file here if any one interested about the topic.
Credit goes to Roger Chapman

