集成之路:Sceneform-EQR合并Filament JNI源码的关键记录

# 集成之路:Sceneform-EQR合并Filament JNI源码的关键记录


在Sceneform-EQR项目中,原有对Filament的使用方式主要依赖官方预编译的Android产物。这种方式接入简单,但在工程演进中逐渐暴露出Native层不可控、SO体积冗余、深度定制受限等问题。为提升工程可维护性并优化产物体积,我启动了一项底层重构:将Filament的JNI源码直接引入工程,合并多个模块并统一JNI注册逻辑。本文将记录这一过程中的关键点与操作步骤。


## 优化的背景与目标


### 原有方案的痛点


Sceneform-EQR本质上是一个基于Filament的Android 3D/AR渲染框架。在早期工程中,Java层直接依赖Filament官方提供的Java API,Native层则使用官方预编译的SO产物。Filament在Android侧被拆分为`filament-android`、`filament-utils-android`、`gltfio-android`三个模块,每个模块对应独立的SO文件。


这种多SO结构带来几个实际问题:多个SO之间存在符号冗余,导致APK/AAR体积膨胀;Native层完全封闭,无法修改JNI注册逻辑;排查Native Crash时如同面对黑盒;后续计划中的自定义渲染管线、深度接入glTF/IBL等功能受到限制。


### 优化目标的设定


基于上述痛点,明确了本次优化的核心目标:引入Filament JNI源码,将三个模块合并为单一SO,统一JNI_OnLoad入口,同时保持Java API层不变,最终实现SO体积降低和工程可维护性提升。


## 源码引入与合并过程


### 准备阶段


首先从Filament官方仓库获取源码,推荐直接下载官方Release中的`android-native`产物,其结构包含必要的头文件和静态库(.a文件),无需自行编译整套工程,可大幅降低接入门槛。


### Java层代码合并


Sceneform-EQR目前对Filament Java API未做修改,因此可直接覆盖官方API包(`com.google.android.filament.*`)。同时将三个Android模块的Java源码(位于各模块的`src/main/java`目录)合并至工程对应位置。


### JNI源码合并


核心步骤是拷贝各模块的JNI实现:


- `filament-android/src/main/cpp` → `Sceneform-eqr/cpp/filament-android`

- 同样处理`gltfio`和`filament-utils`的cpp目录

- 拷贝`common`层工具类代码

- 按需引入必要的第三方依赖库,避免全量拷贝

<"v9.j9k5.org.cn"><"s2.j9k5.org.cn"><"f5.j9k5.org.cn">

## 核心难题:JNI_OnLoad冲突的解决


### 问题本质


官方Filament的三个模块各自拥有独立的JNI_OnLoad函数,分别注册各自的原生方法。合并为单一SO后,只能存在一个JNI_OnLoad入口,原有的多注册逻辑必须重构。


### 重构思路


解决方案的核心是:将各模块的JNI注册逻辑从JNI_OnLoad中剥离,改为提供独立的注册函数,由统一的JNI_OnLoad依次调用。


**修改各模块,移除JNI_OnLoad**:


```cpp

// Filament模块的注册函数

extern "C" JNIEXPORT jint registerFilament(JavaVM* vm, void* reserved) {

    JNIEnv* env;

    if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) {

        return -1;

    }

    ::filament::VirtualMachineEnv::JNI_OnLoad(vm);

    return JNI_VERSION_1_6;

}


// Utils模块的注册函数

extern "C" JNIEXPORT jint registerUtils(JavaVM* vm, void*) {

    JNIEnv* env;

    if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) {

        return -1;

    }

    // 注册Utils的原生方法

    env->RegisterNatives(...);

    return JNI_VERSION_1_6;

}

```


**编写统一的JNI_OnLoad**:


```cpp

extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {

    registerFilament(vm, reserved);

    registerUtils(vm, reserved);

    // 可继续添加gltfio等模块的注册

    return JNI_VERSION_1_6;

}

```


这种设计既保持了各模块注册逻辑的内聚性,又实现了统一入口的目标。


## Native编译配置


### CMakeLists的关键设置


在CMake配置中,有几个关键点需要注意:


**静态库引入**:使用`STATIC IMPORTED`方式引入各模块的静态库,显式指定.a文件路径。


**链接顺序**:Filament的静态库之间存在依赖关系,需严格控制`target_link_libraries`中的顺序。


**whole-archive选项**:部分JNI符号可能因未被直接引用而被链接器裁剪,需使用`-Wl,--whole-archive`强制包含:


```cmake

target_link_libraries(eqr-core

    -Wl,--whole-archive

    filament-jni

    filament-utils-jni

    gltfio-jni

    -Wl,--no-whole-archive

)

<"z1.j9k5.org.cn"><"j8.j9k5.org.cn"><"n0.j9k5.org.cn">

```


### 头文件补全


Filament 1.67.1版本的android-native产物中,`backend/private`等目录未被包含,需从源码工程中手动补拷这些头文件。


## 优化收益与后续价值


### 体积优化成果


完成合并编译后,SO体积从原多SO方案的4.3MB降至2.8MB,缩减约35%。这一提升源于消除了模块间的符号冗余,以及更精确的链接控制。


### 工程可控性提升


Native层完全开放后,为后续功能扩展铺平了道路。在此基础之上,Sceneform-EQR后续成功实现了对PLY点云格式的支持,并开始探索3D Gaussian Splatting的渲染实现。可控的Native层意味着可以自由扩展数据通道、自定义渲染管线,而不受预编译产物的限制。


## 小结


将Filament JNI源码引入Sceneform-EQR并合并多模块的过程,是一次典型的底层重构实践。核心挑战在于JNI_OnLoad的统一与链接符号的完整保留,而收益则是显著的体积优化与工程自由度提升。这一改造为Sceneform-EQR从“基于Filament的封装框架”向“可深度定制的3D渲染平台”演进奠定了基础。


请使用浏览器的分享功能分享到微信等