探秘 UIKit-cross-platform 的 Android 构建机制:CMake、Ninja、NDK 与 JNI 如何让 Swift 跑起来

发布时间:2026/8/21 13:40:37
探秘 UIKit-cross-platform 的 Android 构建机制:CMake、Ninja、NDK 与 JNI 如何让 Swift 跑起来 探秘 UIKit-cross-platform 的 Android 构建机制CMake、Ninja、NDK 与 JNI 如何让 Swift 跑起来【免费下载链接】UIKit-cross-platformCross-platform Swift implementation of UIKit, mostly for Android项目地址: https://gitcode.com/gh_mirrors/ui/UIKit-cross-platformUIKit-cross-platform 是一个用 Swift 完整重写 Apple UIKit 的开源项目目标是让 iOS 开发者用同一套 Swift 代码直接编译到 Android 上运行。而这一切的核心就是一套层层嵌套的UIKit-cross-platform 构建机制Swift Package Manager 负责编译源码CMake 负责组织目标Ninja 负责并行加速NDK 负责交叉链接JNI 负责让 Swift 与 Java/Kotlin 双向通话。本文将沿着这条链路逐层拆解带你看懂 Swift 究竟是如何在 Android 上真正跑起来的。一、UIKit-cross-platform 是什么简单来说它把 iOS 开发者熟悉的UIView、UILabel、UIButton、UINavigationController、UIScrollView、UIAlertController等组件全部用 Swift 重新实现了一遍并借助 SDL 生态完成渲染 图形渲染SDL_gpu️ 设备能力与窗口SDL2 字体渲染SDL_ttf在 Mac 上它直接用 Xcode 构建在 Android 上则改用 Swift Package Manager 加一套定制的工具链完成编译。项目架构总览可以参考 docs/ARCHITECTURE.md注文档以文字与图片形式记录了完整的渲染与构建思路。二、为什么 Android 需要一套独立的构建机制iOS 上 Swift 代码天然运行在 Apple 的运行时里一切顺理成章但 Android 完全没有 Cocoa Touch 运行时Swift 代码要落地必须解决三件事交叉编译Swift 编译器要能产出 Android 可执行的机器码这依赖 NDK 提供的 clang 工具链。语言桥接Android 应用层是 Java/KotlinSwift 必须通过 JNIJava Native Interface与 Java 虚拟机对话。运行时分发Swift 自身的运行时如 libdispatch需要作为动态库一并打进 APK。所以项目专门设计了一条“SwiftPM → CMake → Ninja → NDK → JNI”的完整流水线。三、整条构建链路一览先看一张总览表建立整体印象阶段工具作用关键产物Swift 源码编译SwiftPM Swift Android 工具链把.swift编译为目标文件编译产物库组织与链接CMake Ninja NDK按平台挑选源码、链接成动态库libUIKit.so打包分发GradleAGP把.so放进jniLibs并打进 APKlib/arm64-v8a/libUIKit.so运行时桥接JNI SDLActivity启动 Swift 入口、双向调用nativeInit等原生方法下面我们按顺序逐层深入。四、第一步SwiftPM 与 Swift Android 工具链项目根目录的 Package.swift 定义了 Swift Package依赖swift-jniJNI 的 Swift 封装与本地SDL包通过UIKit_C_API目标暴露 C 接口仅在 Android 平台启用 JNI 依赖并开启interoperabilityMode(.Cxx)支持 C 互操作。而在 Gradle 侧示例工程 samples/getting-started/android/app/build.gradle 定义了一个buildSwiftArm64任务它在preBuild阶段调用swift-android-toolchain/swiftpm.sh传入ANDROID_ABIarm64-v8a并把编译结果输出到src/main/jniLibs/arm64-v8a目录这样 AGP 就能自动把 Swift 动态库打包进 APK。五、第二步CMake 如何按平台挑选源码根目录的 CMakeLists.txt 是整个构建的“总调度”。它用 CMake 的生成器表达式按平台条件加载源码例如add_library(UIKit SHARED Sources/$$PLATFORM_ID:Android:androidNativeInit.swift Sources/$$PLATFORM_ID:Android:AVPlayerAndroid.swift Sources/$$PLATFORM_ID:Darwin:UIApplicationMainMac.swift ... )也就是说Android 专属源码Sources/androidNativeInit.swift启动入口、Sources/AVPlayerAndroid.swift、Sources/AVPlayerLayerAndroid.swift、Sources/AVURLAssetAndroid.swiftDarwin 专属源码Sources/UIApplicationMainMac.swift、Sources/AVPlayerItemMac.swift等平台无关源码UIView、UILabel、CALayer、动画系统等约百个 Swift 文件。随后 CMake 还会add_subdirectory(swift-jni)和add_subdirectory(SDL)并把JNI、SDL链接进UIKit库。示例工程的 samples/getting-started/CMakeLists.txt 则通过add_subdirectory(../../ UIKit)引入整个 UIKit 项目再把自己的ViewController.swift、AppDelegate.swift链接成DemoApp动态库。六、第三步NDK 与 Ninja 如何炼出 libUIKit.so当 Android Studio 触发 CMake 构建时NDK 提供交叉编译所需的 clang、sysroot 与标准库Ninja 则作为默认构建后端并行编译、链接。最终产物是一个libUIKit.so共享库它被 Gradle 自动收集进jniLibs/arm64-v8a运行时由 Java 层的System.loadLibrary加载由于只声明了arm64-v8a一种 ABI见 build.gradle 的splits配置体积和构建时间都能得到控制。七、第四步JNI 双向桥——Swift 与 Kotlin 的对话有了.so还不够Swift 与 Java/Kotlin 之间必须有一座“桥”这座桥就是 JNI。7.1 Java/Kotlin 如何进入 Swift入口在 Sources/androidNativeInit.swift。它用_cdecl把 Swift 函数导出为符合 JNI 命名规范的原生方法_cdecl(Java_org_libsdl_app_SDLActivity_nativeInit) public func nativeInit(env: UnsafeMutablePointerJNIEnv, view: JavaObject) - JavaInt { SDL_Android_Init(env, view) SDL_SetMainReady() return JavaInt(UIApplicationMain(...)) }Kotlin 侧src/main/java/com.flowkey.uikit/UIKitActivity.kt 与UIKitView.kt继承自 SDL 的SDLActivityActivity 启动后JVM 就会调用nativeInit从而把控制权交给 Swift 的UIApplicationMain——iOS 上熟悉的启动流程就这样在 Android 上复现了。7.2 Swift 如何调用 Java反向的调用通过swift-jni完成。以视频播放为例Sources/AVPlayerAndroid.swift 里的AVPlayer直接继承JNIObject可以像调用本地方法一样调用 Java 侧实现基于 ExoPlayer 的org.uikit.AVPlayerpublic func play() { try! call(play) } public func pause() { try! call(pause) } public func seek(to timeInMilliseconds: Int64) { ... }7.3 Java 如何回调 Swift为了让 Java 侧把事件播放结束、出错等传回 Swift项目使用了一个巧妙的技巧Sources/JNIObjectswiftInstancePtr.swift 把 Swift 对象自身包装成不透明的指针传给 JavaJava 持有这个指针后就能在回调时把它还原成 Swift 实例。此外Sources/SDLJNIExtensions.swift 负责把SDLActivity的 Java 对象包装成可在 Swift 中安全使用的类型。至此Swift ↔ Java 的双向通信彻底打通。八、一个 iOS 工程如何一键变身 Android 工程项目还提供了 create-android-project 脚本让 iOS 工程快速“长出”Android 壳从*.xcodeproj/project.pbxproj中解析出 Bundle Identifier以 samples/getting-started 为模板生成android/工程目录与CMakeLists.txt生成对应的MainActivity.kt与androidMain.swift内含AppDelegate声明批量替换包名、工程名后即可交给 Android Studio 构建。这意味着你只需要维护一份 iOS 代码库就能同时产出 iOS 与 Android 两个平台的 App。九、新手容易踩的几个坑ABI 限制目前默认只构建arm64-v8a模拟器x86_64设备需要自行调整splits配置。NDK/CMake 版本构建依赖较新的 NDK 与 CMake最低 3.16版本过低会直接报错。Swift 运行时符号libdispatch.so需要保留调试符号否则可能出现运行时崩溃示例工程已在packagingOptions.jniLibs.keepDebugSymbols中做了处理。包名一致性JNI 函数名中的类路径如org_libsdl_app_SDLActivity必须与 Kotlin 类完全对应改包名时要同步修改。十、总结回过头看UIKit-cross-platform 的Android 构建机制其实是一套非常清晰的接力赛SwiftPM 编译 Swift 源码 → CMake 按平台组织目标 → Ninja NDK 交叉链接出libUIKit.so→ Gradle 打进 APK → JNI 让 Kotlin 与 Swift 双向通话 → SDL 完成渲染。正是这条精心设计的链路让“一份 Swift 代码iOS 与 Android 双端运行”从理想变成了现实。无论你是想把它集成进自己的项目还是单纯想学习 Swift 跨平台工程化这套 CMake、Ninja、NDK 与 JNI 的组合拳都值得反复研读。【免费下载链接】UIKit-cross-platformCross-platform Swift implementation of UIKit, mostly for Android项目地址: https://gitcode.com/gh_mirrors/ui/UIKit-cross-platform创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考