将 Outline SDK 添加到移动应用

本文档概述了如何将 Outline SDK 集成到移动应用中,并重点讲解如何使用 MobileProxy 库简化本地代理管理。

MobileProxy 是一个基于 Go 的库,旨在简化将代理服务器功能集成到移动应用中的流程。该库利用 Go Mobile 生成移动端库,让您能够配置应用的网络库,将流量通过本地代理进行传输。

不使用 MobileProxy 的应用

不使用 MobileProxy 的内容应用

使用 MobileProxy 的应用

使用 MobileProxy 的内容应用

第 1 步:构建 MobileProxy 移动端库

使用 gomobile 将 Go 代码编译为 Android 和 iOS 库。

  1. 克隆 Outline SDK 仓库:

    git clone https://github.com/Jigsaw-Code/outline-sdk.git
    cd outline-sdk/x
    
  2. 使用 go build 构建 Go Mobile 二进制文件:

    go build -o "$(pwd)/out/" golang.org/x/mobile/cmd/gomobile golang.org/x/mobile/cmd/gobind
    

    添加 Psiphon 支持

    要想让应用支持使用 Psiphon 网络,请按以下额外步骤操作:

    • 与 Psiphon 团队联系,获取用于访问其网络的配置,可能需要签订合同。
    • 将获得的 Psiphon 配置添加到 SmartDialer 配置的 fallback 部分。
    • 使用 -tags psiphon 标记构建 MobileProxy:

      go build -tags psiphon -o "$(pwd)/out/" golang.org/x/mobile/cmd/gomobile golang.org/x/mobile/cmd/gobind
      

    由于 Psiphon 代码库基于 GPL 协议授予许可,可能会对您的代码施加许可限制,因此需要使用 -tags psiphon 标记。您可能需要考虑申请特殊许可。

  3. 生成移动端库并将其添加到项目中:

    Android

    PATH="$(pwd)/out:$PATH" gomobile bind -ldflags='-s -w' -target=android -androidapi=21 -o "$(pwd)/out/mobileproxy.aar" github.com/Jigsaw-Code/outline-sdk/x/mobileproxy
    

    在 Android Studio 中,依次选择文件 > 导入项目…,导入生成的 out/mobileproxy.aar 软件包。如需获取更多帮助,请参阅 Go Mobile 的构建并部署到 Android

    iOS

    PATH="$(pwd)/out:$PATH" gomobile bind -ldflags='-s -w' -target=ios -iosversion=11.0 -o "out/mobileproxy.xcframework" github.com/Jigsaw-Code/outline-sdk/x/mobileproxy
    

    out/mobileproxy.xcframework 软件包拖放到 Xcode 项目中。如需获取更多帮助,请参阅 Go Mobile 的构建并部署到 iOS

第 2 步:运行 MobileProxy

在应用的运行时内初始化并启动 MobileProxy 本地代理服务器。您可以使用静态传输配置,或通过 Smart Proxy 实现动态策略选择。

  • 静态传输配置:将 RunProxy 函数与本地地址和传输配置结合使用。

    Android

    import mobileproxy.*
    
    val dialer = StreamDialer("split:3")
    
    // Use port zero to let the system pick an open port for you.
    val proxy = Mobileproxy.runProxy("localhost:0", dialer)
    // Configure your networking library using proxy.host() and proxy.port() or proxy.address().
    // ...
    // Stop running the proxy.
    proxy.stop()
    

    iOS

    import Mobileproxy
    
    let dialer = MobileproxyStreamDialer("split:3")
    
    // Use port zero to let the system pick an open port for you.
    let proxy = MobileproxyRunProxy("localhost:0", dialer)
    // Configure your networking library using proxy.host() and proxy.port() or proxy.address().
    // ...
    // Stop running the proxy.
    proxy.stop()
    
  • Smart Proxy:Smart Proxy 可根据指定的测试网域动态选择 DNS 和 TLS 策略。您需要以 YAML 格式指定配置策略(示例)。

    Android

    val testDomains = Mobileproxy.newListFromLines("www.youtube.com\ni.ytimg.com")
    val strategiesConfig = "..."  // Config YAML.
    val dialer = Mobileproxy.newSmartStreamDialer(testDomains, strategiesConfig, Mobileproxy.newStderrLogWriter())
    
    // Use port zero to let the system pick an open port for you.
    val proxy = Mobileproxy.runProxy("localhost:0", dialer)
    // Configure your networking library using proxy.host() and proxy.port() or proxy.address().
    // ...
    // Stop running the proxy.
    proxy.stop()
    

    iOS

    import Mobileproxy
    
    var dialerError: NSError?
    let testDomains = MobileproxyNewListFromLines("www.youtube.com\ni.ytimg.com")
    let strategiesConfig = "..."  // Config YAML.
    let dialer = MobileproxyNewSmartStreamDialer(
        testDomains,
        strategiesConfig,
        MobileproxyNewStderrLogWriter(),
        &dialerError
    )
    
    var proxyError: NSError?
    // Use port zero to let the system pick an open port for you.
    MobileproxyRunProxy("localhost:0", dialer, &proxyError)
    // Configure your networking library using proxy.host() and proxy.port() or proxy.address().
    // ...
    // Stop running the proxy.
    proxy.stop()
    

第 3 步:配置 HTTP 客户端和网络库

配置网络库以使用本地代理服务器地址和端口。

Dart/Flutter HttpClient

通过 HttpClient.findProxy 设置代理服务器。

HttpClient client = HttpClient();
client.findProxy = (Uri uri) {
  return "PROXY " + proxy.address();
};

OkHttp (Android)

通过 OkHttpClient.Builder.proxy 设置代理服务器。

val proxyConfig = Proxy(Proxy.Type.HTTP, InetSocketAddress(proxy.host(), proxy.port()))
val client = OkHttpClient.Builder().proxy(proxyConfig).build()

JVM(Java、Kotlin)

使用系统属性配置要使用的代理服务器:

System.setProperty("http.proxyHost", proxy.host())
System.setProperty("http.proxyPort", String.valueOf(proxy.port()))
System.setProperty("https.proxyHost", proxy.host())
System.setProperty("https.proxyPort", String.valueOf(proxy.port()))

Android WebView

通过 androidx.webview 库将代理服务器配置应用于应用中的所有 WebView:

ProxyController.getInstance()
    .setProxyOverride(
        ProxyConfig.Builder()
            .addProxyRule(this.proxy!!.address())
            .build(),
        {}, // execution context for the following callback - do anything needed here once the proxy is applied, like refreshing web views
        {} // callback to be called once the ProxyConfig is applied
    )

iOS WebView

从 iOS 17 开始,您可以使用 WKWebsiteDataStore 属性将代理服务器配置添加到 WKWebView

let configuration = WKWebViewConfiguration()
let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(proxyHost), port: NWEndpoint.Port(proxyPort)!)
let proxyConfig = ProxyConfiguration.init(httpCONNECTProxy: endpoint)
let websiteDataStore = WKWebsiteDataStore.default()
websiteDataStore.proxyConfigurations = [proxyConfig]
let webview = WKWebView(configuration: configuration)

高级:生成自定义移动端库

对于高级应用场景,您可以生成自己的移动端库:

  1. 创建 Go 库:开发一个封装了所需 SDK 功能的 Go 库。
  2. 生成移动端库:使用 gomobile bind 生成 Android Archive (AAR) 和 Apple 框架。示例:
  3. 集成到应用中:将生成的库添加到移动应用中。