# 集成之路: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
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
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渲染平台”演进奠定了基础。