英文:
Building a server that can run every where, desktop and mobile
问题
我可以使用相同的代码库在移动端的webview
上运行我的应用程序吗,而不使用gomobile或其他包,这样我就可以将代码作为通用应用程序使用吗?
英文:
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?
答案1
得分: 0
答案是“是的”,但需要对文件本身进行一些轻微的修改。
- 从
func main() {}
中删除所有内容,因为我们将构建最终结果作为共享库,而不是可执行二进制文件。 - 在
//export
函数中运行服务器。 - 从
匿名goroutine
中作为go func() {}()
运行服务器,以便不阻塞移动应用程序的主线程。 - 为了保持服务器goroutine的运行,我们需要使用通道
<-c
来防止goroutine退出。 - 通过添加
import "C"
来使用cgo
,使主文件变为以下形式:
package main
import "C"
// 其他导入应与特殊的Cgo导入分开
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() {}
- 确保已安装Android
NDK
,并知道其路径。 - 使用输出名称为
libxxx
构建c-shared
输出,要构建为Android
,请使用以下命令:
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.go
等待由于Android具有多个架构,我们需要为每个架构单独编译,因此可以在Makefile
中自动化所有过程,如下所示,在从项目模板中选择Native C++
之后创建Android应用程序,下面的输出库名称为libfoo
,每个文件夹中将生成2个文件libfoo.so
和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
- 进入
android_app/app/src/main/cpp
并执行以下操作:
8.1. 将文件CMakeLists.txt
更改为:
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. 将文件native-lib.cpp
更改为:
#include <jni.h>
#include <string>
#include "libfoo.h" // 我们的库头文件
extern "C" {
void
Java_tk_android_MainActivity_serverJNI() {
// 运行服务器
server();
}
}
- 将webview添加到
layout/activity_main
中,如下所示:
<?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>
- 更新
MainActivity
如下所示:
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")
}
}
}
- 更新
AndroidManifest
如下所示:
<?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 crashing at rotation
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
奖励
使用Go的embed
,所有静态文件都可以嵌入到同一个库中,包括css
、javascript
和templates
,因此可以构建API或带有GUI的完整应用程序。
英文:
The answer is "Yes", but some slight modifications 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
//export
function. - Run the server from an
anonymous goroutine
asgo func() {}()
so it is not blocking the main thread of the mobile app. - To keep the server goroutine running, we need to use a channel as
<-c
to prevent the goroutine from exit. - Use
cgo
by addingimport "C"
, so the main file become like this:
package main
import "C"
// other imports should be seperated 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 Android
NDK
installed, and you know its bath - Build the
c-shared
output with an output name aslibxxx
, to build forAndroid
use:
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.go
Wait As android has multiple architectures, we need to compile for each one individually, so we can have all the processes 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/cpp
and 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
MainActivity
as 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
AndroidManifest
to 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 crashing at rotation
<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 build either API, or full app with GUI
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论