构建一个可以 运行 在任何地方、桌面和移动设备的服务器

Building a server that can run every where, desktop and mobile

我的笔记本电脑 (Mac/Windows/Linux) 上 运行ning 有以下简单的 go 服务器:

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))
}

我可以在移动 webview 上使用与 运行 我的应用程序相同的代码库,而不使用 gomobile 或其他软件包,所以我的代码是通用应用程序吗?

答案是“是”,但需要对文件本身进行一些细微的修改。

  1. 删除 func main() {} 中的所有内容,因为我们会将最终结果构建为共享库,而不是可执行二进制文件。
  2. 运行 //export 函数中的服务器。
  3. 运行 来自 anonymous goroutine 的服务器为 go func() {}(),因此它不会阻塞移动应用程序的主线程。
  4. 为了保持服务器 goroutine 运行,我们需要使用一个通道作为 <-c 来防止 goroutine 退出。
  5. 使用cgo加上import "C",所以主文件变成这样:
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() {}
  1. 确保安装Android NDK,你知道它的浴室
  2. 构建 c-shared 输出,输出名称为 libxxx,为 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.solibfoo.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
  1. 转到 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" // our library header

extern "C" {
    void
    Java_tk_android_MainActivity_serverJNI() {
        // Running the server
        server();
    }
}
  1. 将 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>
  1. 更新 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")
        }
    }
}
  1. 更新 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,所有静态文件都可以嵌入到同一个库中,包括 cssjavascripttemplates,因此您可以构建 API 或完整的应用程序带 GUI