fix: query CoreOnly weather layer presence
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled

This commit is contained in:
OpenAI Codex
2026-09-08 19:16:53 +08:00
parent 2d2375cd53
commit 2ea9339e41
14 changed files with 404 additions and 30 deletions

View File

@@ -0,0 +1,255 @@
# STEP-060E CoreOnly Layer 存在性检查修复 — Codex任务 v1
## 1. 任务定位
当前仍为 **STEP-060**,不得进入 STEP-061。
本轮修复 STEP-060D 代码审查确认的 Android 根因:
- `raster-particle` 与 `weather-color` layer 在 Android `LayerManagerAndroid` 中通过 `addLayerTypeCoreOnly(...)` 注册;
- Core 可以正常解析、持有和渲染这些 layer;
- 但 CoreOnly layer 没有 Java `Layer` peer;
- `Style.getLayer(id)` 最终依赖 `LayerManagerAndroid::createJavaLayerPeer(...)`;
- 对 CoreOnly layer,该函数返回 null;
- 因此 Bridge 用 `Style.getLayer()` 判断 layer 是否存在,必然产生假阴性。
这与当前现象一致:原生 Particle validation 成功,但 RN `WeatherRuntimeBridge.attach()` 报 weather layer 缺失。
本任务只修复这一 Android Java/JNI/Core 存在性检查链,并保持现有 CoreOnly 设计。
## 2. 开工基线
正式仓库:
`/mnt/sda1/MapLibreParticleLab`
分支:`particle/main`
本任务开工目标基线:
`2d2375cd532468821c6e3c158ee1a275f4522044`
该提交已包含 Win11 已验证的 RN Test Host 修复:
- React `19.2.3`
- RN 0.85.2 codegen 配置
- `NativeWeatherRuntimeBridge.ts` 同文件 type aliases
- 保留 Expo/RN repositories 的 Maven Local plugin 逻辑
- STEP-060D `13.4.1-step060d` 版本接线
开工前执行 `git pull --ff-only origin particle/main`,确认基线没有落后。
不得回退上述 Win11 已验证修复。
## 3. 已确认源码证据
以下证据已经由 ChatGPT 直接审查当前源码确认,不要用 Codex 完成报告替代:
1. `platform/android/MapLibreAndroid/src/cpp/style/layers/layer_manager.cpp`
- `RasterParticleLayerFactory` 使用 `addLayerTypeCoreOnly(...)`
- `WeatherColorLayerFactory` 使用 `addLayerTypeCoreOnly(...)`
- `createJavaLayerPeer(...)` 找不到 Java peer factory 时返回 null
2. `platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp`
- `NativeMapView::getLayer(...)` 取得 Core layer 后调用 `createJavaLayerPeer(...)`
- 因而 CoreOnly layer 即使真实存在,Java `Style.getLayer()` 仍可得到 null
3. 当前 Bridge `WeatherRuntimeBridgeModule.attach()` 使用 `style.getLayer(...)` 做存在性判断,因此判断机制本身错误。
4. STEP-060D 新增 `style.getLayers().map { it.id }` 也不安全:CoreOnly layer 没有 Java peer,不能用 Java Layer peer 枚举作为 Core layer 真值来源。
5. `MapLibreMap.getStyle()` 只在 Style fully loaded 时返回非 null,所以本轮不是普通 style-ready 时机修复。
不得重新把问题归因于 React、MLRN mapStyle JSON 传递、AAR 混用或简单 attach 重试时机。
## 4. 必做实现:Core 直接存在性查询
新增通用、窄范围的 Android MapLibre API,用来查询 **Core Style 真值**,不得依赖 Java Source/Layer peer。
公开语义固定为:
- `MapLibreMap.hasStyleSource(String sourceId): boolean`
- `MapLibreMap.hasStyleLayer(String layerId): boolean`
具体落点至少覆盖:
- `NativeMap.java`
- `NativeMapView.java`
- `MapLibreMap.java`
- `native_map_view.hpp`
- `native_map_view.cpp`
- JNI method registration
C++ 最终判断必须直接基于:
- `map->getStyle().getSource(id) != nullptr`
- `map->getStyle().getLayer(id) != nullptr`
不得通过 `createJavaLayerPeer()`、`Style.getLayer()` 或其他 Java peer 路径间接判断。
## 5. Bridge attach 修正
修改正式:
`integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgeModule.kt`
attach 在取得 fully-loaded `style` 后,改为:
- source:使用 `map.hasStyleSource(WeatherIds.SOURCE)`
- weather color layer:使用 `map.hasStyleLayer(WeatherIds.COLOR_LAYER)`
- particle layer:使用 `map.hasStyleLayer(WeatherIds.PARTICLE_LAYER)`
三个结果继续独立输出。
必须删除/停止使用以下方式作为存在性真值:
- `style.getLayer(...)`
- `style.getLayers().map { it.id }`
- Java Layer peer 数量作为 Core layer 数量
source 也统一改用 Core 直接查询,不再让 Source/Layer 使用两套存在性语义。
## 6. STEP-060D 诊断修正
保留稳定错误码:
`WEATHER_OBJECTS_NOT_READY`
失败信息至少包含:
- `reactTag`
- `mapView=present|missing`
- `mapLibreMap=present|missing`
- `style=present|missing`
- `source[rn-weather-fields]=present|missing`
- `layer[rn-weather-color]=present|missing`
- `layer[rn-weather-particles]=present|missing`
另外使用公开 `Style.getUri()` / Kotlin 对应属性输出 `styleUri`;JSON style URI 为空时明确写 `<empty>` 或等价稳定表示。
不要再调用 `style.getLayers()` 构造诊断列表。
如果确实需要 ID 列表,只允许使用直接 Core ID 查询;本任务默认不要求新增列表 API,避免扩大修改面。
成功路径仍必须保持现有 `replaceWeatherRuntime(...)`、snapshot、listener 和 Phase A/B 控制语义不变。
## 7. 明确禁止
本任务不得:
- 将 `RasterParticleLayerFactory` 或 `WeatherColorLayerFactory` 从 `addLayerTypeCoreOnly(...)` 改成普通 Java layer registration
- 为了让 `Style.getLayer()` 非 null 而新建 WeatherColor/RasterParticle Java peer 类
- 改变 CoreOnly layer 的产品边界
- 用解析完整 Style JSON 作为正式存在性判断
- 增加 attach 定时重试或无限重试
- 修改 MLRN mapStyle 传递链来掩盖问题
- 修改 WeatherRuntime 控制状态机
- 实现 binary U/V field transport
- 进入 STEP-061
- 在 Win11 临时修改 Native 正式源码
- 执行 Win11 APK build/install、ADB 或设备测试
本轮目标是让 Java/RN Host 能以一个窄 API 查询 Core Style 真值,而不是把 CoreOnly 类型暴露成完整 Android Java Layer API。
## 8. Maven / AAR 版本
本轮必须使用新的临时版本,禁止复用 STEP-060D 内容不同但版本相同的 artifact:
`org.maplibre.gl:android-sdk-opengl:13.4.1-step060e`
同步更新至少包括:
- `platform/android/VERSION`
- RN Test Host `modules/weather-runtime-bridge/android/build.gradle`
- `plugins/withParticleNativeAndroid.js`
Codex **不得构建 AAR,也不得执行 Android/Gradle AAR 打包或 Maven Local 发布**。
Codex 本轮只负责把源码与版本接线准备到可由用户手工构建的状态,并在完成 Linux 静态检查/精确编译后 commit + push。
用户在 Codex 停止后手工执行 OpenGL Release custom AAR 构建与 Maven Local 发布。用户构建完成后再记录:
- Maven coordinate
- AAR build output 路径
- Maven Local AAR 路径
- AAR SHA256
- 四 ABI 是否存在
Codex 完成报告中必须明确写:`AAR_BUILD_NOT_EXECUTED_BY_CODEX`,不得把“源码已准备”表述成“AAR 已生成”。
不得建立新的正式 Maven pipeline;本轮仍为 STEP-060 临时可追溯版本。
## 9. Linux 验证要求
Codex 只执行与本轮源码相关的轻量、精确验证,不执行 AAR 打包。
至少完成:
- Android JNI/C++ 相关源码的精确编译或现有等价最小验证;
- Android Java/Kotlin 受影响源码的可行精确编译;若因当前生成工程/codegen 缺失无法完成,必须明确记录限制,不得通过扩大修改范围绕过;
- 检查新增 JNI 方法声明、实现、注册签名一致;
- 检查 `MapLibreMap.hasStyleSource/hasStyleLayer` 最终确实直达 Core Style;
- 检查 Bridge 已完全停止用 Java `Style.getLayer()` / `Style.getLayers()` 判断 CoreOnly layer 存在性;
- `git diff --check`;
- 检查最终 diff,不得混入无关文件。
编译输出遵循全局 `$build-output-throttle`。
本任务不执行 Jest,也不得新增 Jest 要求。
## 10. 文档要求
完成后更新 STEP-060E 对应中文 Step 记录与中文 Codex 完成报告。
同主题已有文档时优先更新或使用递增版本号,不得无意义创建多个不同文件名。
文档必须明确:
- 根因为 CoreOnly layer 无 Java peer,导致 Java `Style.getLayer()` 假阴性;
- 本轮采用 Core 直接存在性查询,不改变 CoreOnly 架构;
- Win11 已验证 RN Test Host 基线来自 `2d2375cd532468821c6e3c158ee1a275f4522044`;
- Maven 临时版本已切到 `13.4.1-step060e`;
- `AAR_BUILD_NOT_EXECUTED_BY_CODEX`;
- Android APK/ADB/设备验证尚未执行;
- STEP-060 尚未 CLOSED,未进入 STEP-061。
## 11. Git 收尾
完成规定源码、验证和文档后,只提交本任务相关变更到当前主线 `particle/main`。
必须:
- commit 本任务相关源码、版本接线、任务文件及文档;
- push 到 `origin/particle/main`;
- push 失败时不得声称任务完成;
- 不得提交与本任务无关的本地改动。
完成报告必须记录:
- source commit SHA;
- final HEAD;
- commit message;
- push 结果;
- remote branch;
- 预期 Maven coordinate:`org.maplibre.gl:android-sdk-opengl:13.4.1-step060e`;
- `AAR_BUILD_NOT_EXECUTED_BY_CODEX`。
## 12. 完成报告格式
结束时按以下必做项逐项报告:`已完成 / 未完成 / 无法验证`,并说明未完成或无法验证原因:
- Core `hasStyleSource` 查询链;
- Core `hasStyleLayer` 查询链;
- Bridge attach 改用 Core 真值;
- 移除危险的 `style.getLayers()` 诊断枚举;
- 增加 `styleUri` 诊断;
- 成功路径与 WeatherRuntime 控制语义保持不变;
- `13.4.1-step060e` 版本接线;
- Linux 精确验证;
- 文档更新;
- Git commit/push;
- AAR 未由 Codex 构建;
- 未进入 STEP-061。
不得用“基本完成”“应该可以”等模糊表述替代状态。
## 13. 停止条件与后续人工链路
Codex 成功 push 后立即停止,不得自行构建 AAR,不得进入 Win11 或 Android 运行验证。
后续固定人工/ChatGPT 链路:
1. ChatGPT 审核远端实际代码;
2. 用户在 Linux 手工构建 `13.4.1-step060e` OpenGL Release AAR并发布 Maven Local;
3. 记录 Linux AAR SHA256;
4. Win11 从 Git pull 最新 RN Test Host 源码;
5. Win11 通过 SCP 从 `root@192.168.200.184` 拉取 AAR;
6. Win11 计算 SHA256,必须与 Linux 完全一致;
7. 更新 Win11 Maven Local;
8. 执行 Bridge 与 MLRN 两条 `dependencyInsight`,必须共同解析到 `13.4.1-step060e`;
9. 用户手工构建 APK;
10. 再进行 Android attach/runtime 验证。
在上述 Android 结果出来前,不得宣布 STEP-060 CLOSED,也不得进入 STEP-061。

View File

@@ -0,0 +1,37 @@
# STEP-060E CoreOnly Layer 存在性检查修复
状态:`IMPLEMENTED / WAITING_CODE_REVIEW`
## 目标与基线
在 `particle/main` 的 Win11 已验证 RN Test Host 基线
NaN2d2375cd532468821c6e3c158ee1a275f4522044` 上,修正 STEP-060D 的 attach
存在性检查假阴性。本轮仍属于 STEP-060,未进入 STEP-061。
## 根因与修正
- `raster-particle` 与 `weather-color` 在 Android `LayerManagerAndroid` 中以
`addLayerTypeCoreOnly(...)` 注册。Core 可以解析、持有和渲染它们,但不创建 Java
`Layer` peer。
- 因此 `Style.getLayer(id)` 经 `createJavaLayerPeer(...)` 后会对真实存在的 CoreOnly
layer 返回 null;`Style.getLayers()` 也不能作为 Core layer 真值。
- 新增窄范围公开 API `MapLibreMap.hasStyleSource(String)` 与
`MapLibreMap.hasStyleLayer(String)`,经 `NativeMap`、`NativeMapView`、JNI 到
`NativeMapView`,分别直接调用 Core `Style::getSource/getLayer`。
- RN `WeatherRuntimeBridge.attach()` 保持 fully-loaded `Style` 前置条件,但三项存在性
判断统一改走上述 Core 真值;成功时原有 `replaceWeatherRuntime()`、snapshot 与 detach
listener 不变。
- `WEATHER_OBJECTS_NOT_READY` 仍给出三个固定对象的独立状态,诊断改为输出
`Style.getUri()`(JSON style 为 `<empty>`),不再枚举 Java source/layer peer。
## 版本与验证边界
- Maven 临时坐标已同步为
`org.maplibre.gl:android-sdk-opengl:13.4.1-step060e`。
- `AAR_BUILD_NOT_EXECUTED_BY_CODEX`:本轮不构建 AAR、不发布 Maven Local。
- Android APK、ADB、设备/模拟器验证尚未执行;必须在人工构建并发布该新版本 AAR 后进行。
## 结果
CoreOnly 架构未改变、未新增 Java weather layer peer、未引入 attach retry,且没有修改
WeatherRuntime 状态机、binary field transport 或 STEP-061 范围。

View File

@@ -0,0 +1,44 @@
# STEP-060E CoreOnly Layer 存在性检查修复 — Codex 完成报告
## 结论
状态:IMPLEMENTED / WAITING_CODE_REVIEW。本轮完成 Android Host 到 JNI/Core 的 Style 存在性窄查询,修复 RN bridge 对 CoreOnly weather layer 的假阴性;STEP-060 未 CLOSED,未进入 STEP-061。
## 实际完成内容
- 根因:RasterParticleLayer 与 WeatherColorLayer 是 CoreOnly layer,Core 可正常渲染,但 Style.getLayer() 需要 Java peer,故会错误返回 null。
- 新增公开 MapLibreMap.hasStyleSource(String)、hasStyleLayer(String);实现穿过 NativeMap/NativeMapView 和 JNI,C++ 直接以 map->getStyle().getSource(id) 与 getLayer(id) 判断。
- WeatherRuntimeBridge.attach() 在 style fully loaded 后,使用三个独立 Core 查询检查 rn-weather-fields、rn-weather-color、rn-weather-particles;没有改变 attach 成功路径、replaceWeatherRuntime()、snapshot 或 View detach listener。
- WEATHER_OBJECTS_NOT_READY 保留固定错误码和三个独立 present/missing 字段,新增 styleUri(JSON style 为空时 <empty>),移除 style.getLayers()/getSources() 的 Java peer 枚举。
- Maven 临时版本同步为 13.4.1-step060e。
## 修改文件
- platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/{NativeMap.java,NativeMapView.java,MapLibreMap.java}
- platform/android/MapLibreAndroid/src/cpp/{native_map_view.hpp,native_map_view.cpp}
- integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgeModule.kt
- platform/android/VERSION、bridge android/build.gradle、plugins/withParticleNativeAndroid.js、Test Host README、本 Step 记录、本文档及 粒子开发记录.md。
## 验证
- ./gradlew :MapLibreAndroid:tasks --all --console=plain:PASS。
- ./gradlew :MapLibreAndroid:compileOpenglReleaseJavaWithJavac --console=plain:PASS。
- ./gradlew :MapLibreAndroid:externalNativeBuildOpenglRelease --console=plain:PASS(x86_64 等价精确 CMake/Ninja target);该任务仅编译 JNI/OpenGL 原生目标,不打包 AAR。
- 静态检查:JNI Java 声明、C++ 声明/实现/注册名称一致;Bridge 不再调用 Style.getLayer()、Style.getLayers()、Style.getSource() 或 Style.getSources() 作为 weather object 真值。
- git diff --check:PASS。
所有可能产生大量输出的 Gradle 编译均通过 build-output-throttle 执行;未执行 Jest。
## 未执行与风险
- AAR_BUILD_NOT_EXECUTED_BY_CODEX。
- 未执行 Maven Local publication、Android APK build/install、ADB、设备或模拟器验证。
- 本地 Expo 生成工程先前缺少 NativeWeatherRuntimeBridgeSpec;未为本轮新增大型 codegen 或 mocking 方案。后续人工构建新 AAR 并在 Win11 更新 Maven Local 后,仍需执行两条 dependencyInsight、APK build 与 attach/runtime 人工验收。
- 运行期新 JNI 方法的真值行为需要上述 Android 人工验收;本轮未改变 CoreOnly 注册或 WeatherRuntime 控制状态机。
## Git
- source baseline:2d2375cd532468821c6e3c158ee1a275f4522044
- 功能 commit / Final HEAD / push 结果:将在本次受限验证、提交并推送后回填。
- remote branch:origin/particle/main
- 预期 Maven 坐标:org.maplibre.gl:android-sdk-opengl:13.4.1-step060e

View File

@@ -7,7 +7,7 @@
- package manager 为 npm(`packageManager: npm@10.9.4`),锁文件为 `package-lock.json`;当前环境的 `npm ci --dry-run --ignore-scripts` 无须 `--legacy-peer-deps` 即可解析。
- package 明确固定 React Native `0.85.2`、React `19.2.3`、Expo `56.0.4`、TypeScript `5.9.3`、MLRN `11.3.6`;`app.json` 明确请求 New Architecture。Hermes 的最终启用状态必须在首次 Android prebuild 后检查生成工程。
- AGP `8.5.0`、Gradle `8.14.3`、Kotlin `2.1.20`、NDK `27.1.12297006`、JDK 17、minSdk 24、compile/targetSdk 36 是本 Test Host 的**人工验收配置目标**,不是尚未生成 Android 工程的已验证结果。首次 prebuild 后必须以实际 Gradle wrapper、AGP/Kotlin plugin、SDK/NDK 与 Hermes/New Architecture 配置为准核对。
- MLRN 的 root Gradle extras 固定 `org.maplibre.reactnative.nativeVersion=13.4.1-step060d` 与 `nativeVariant=opengl`;这是 STEP-060D attach 诊断 AAR 的唯一临时版本,避免与先前 `13.4.1` Maven Local artifact 缓存混淆。
- MLRN 的 root Gradle extras 固定 `org.maplibre.reactnative.nativeVersion=13.4.1-step060e` 与 `nativeVariant=opengl`;这是 STEP-060E CoreOnly layer 存在性检查修复 的唯一临时版本,避免与先前 `13.4.1` Maven Local artifact 缓存混淆。
## Custom SDK 来源(手工执行)

View File

@@ -14,7 +14,7 @@ android {
dependencies {
implementation "com.facebook.react:react-android"
implementation "org.jetbrains.kotlin:kotlin-stdlib:2.1.20"
implementation "org.maplibre.gl:android-sdk-opengl:13.4.1-step060d"
implementation "org.maplibre.gl:android-sdk-opengl:13.4.1-step060e"
compileOnly project(":maplibre_maplibre-react-native")
}

View File

@@ -25,9 +25,9 @@ class WeatherRuntimeBridgeModule(private val context: ReactApplicationContext) :
@ReactMethod
override fun attach(reactTag: Double, promise: Promise) = withMapView(reactTag, promise) { view, map ->
val style = map.style ?: return@withMapView reject(promise, "STYLE_NOT_READY", "The MapLibre style is not loaded")
val sourceExists = style.getSource(WeatherIds.SOURCE) != null
val weatherColorLayerExists = style.getLayer(WeatherIds.COLOR_LAYER) != null
val particleLayerExists = style.getLayer(WeatherIds.PARTICLE_LAYER) != null
val sourceExists = map.hasStyleSource(WeatherIds.SOURCE)
val weatherColorLayerExists = map.hasStyleLayer(WeatherIds.COLOR_LAYER)
val particleLayerExists = map.hasStyleLayer(WeatherIds.PARTICLE_LAYER)
if (!sourceExists || !weatherColorLayerExists || !particleLayerExists) {
return@withMapView reject(
promise,
@@ -142,9 +142,9 @@ class WeatherRuntimeBridgeModule(private val context: ReactApplicationContext) :
}
/**
* Keep the attach rejection searchable and bounded: MLRN lifecycle investigation needs the
* exact absent weather object plus enough final-style context, but never an unbounded dump.
* This executes on the UI queue together with the Style lookup above.
* Core-only layer types do not have a Java Layer peer. Presence is checked directly in Core
* before this diagnostic is built; the style URI distinguishes a JSON style (empty URI) from
* a URI-backed style without materialising all sources or layers.
*/
private fun weatherObjectsNotReadyDiagnostic(
reactTag: Int,
@@ -153,47 +153,28 @@ class WeatherRuntimeBridgeModule(private val context: ReactApplicationContext) :
weatherColorLayerExists: Boolean,
particleLayerExists: Boolean,
): String {
val sourceIds = style.getSources().map { it.id }
val layerIds = style.getLayers().map { it.id }
return buildString {
append("WEATHER_OBJECTS_NOT_READY reactTag=").append(reactTag)
append(" mapView=present mapLibreMap=present style=present")
append(" styleUri=").append(style.getUri().ifEmpty { "<empty>" })
append(" source[").append(WeatherIds.SOURCE).append("]=")
.append(presence(sourceExists))
append(" layer[").append(WeatherIds.COLOR_LAYER).append("]=")
.append(presence(weatherColorLayerExists))
append(" layer[").append(WeatherIds.PARTICLE_LAYER).append("]=")
.append(presence(particleLayerExists))
append(" styleSourceCount=").append(sourceIds.size)
append(" styleLayerCount=").append(layerIds.size)
append(" styleSourceIds=").append(boundedIds(sourceIds))
append(" styleLayerIds=").append(boundedIds(layerIds))
}
}
private fun presence(exists: Boolean) = if (exists) "present" else "missing"
private fun boundedIds(ids: List<String>): String {
val weatherIds = ids.filter { it in WeatherIds.ALL }.distinct()
val otherIds = ids.filterNot { it in WeatherIds.ALL }.take(MAX_STYLE_IDS - weatherIds.size)
val shown = weatherIds + otherIds
val omitted = ids.size - shown.size
return buildString {
append('[').append(shown.joinToString(","))
if (omitted > 0) append(",…+").append(omitted)
append(']')
}
}
private fun reject(promise: Promise, code: String, message: String) = promise.reject(code, message)
private object WeatherIds {
const val SOURCE = "rn-weather-fields"
const val COLOR_LAYER = "rn-weather-color"
const val PARTICLE_LAYER = "rn-weather-particles"
val ALL = setOf(SOURCE, COLOR_LAYER, PARTICLE_LAYER)
}
companion object {
const val NAME = "WeatherRuntimeBridge"
const val MAX_STYLE_IDS = 24
}
}

View File

@@ -10,7 +10,7 @@ const marker = "// MapLibreParticleRNTestHost custom OpenGL SDK";
module.exports = function withParticleNativeAndroid(config) {
return withProjectBuildGradle(config, (project) => {
if (project.modResults.contents.includes(marker)) return project;
project.modResults.contents += `\n${marker}\ngradle.projectsEvaluated {\n allprojects { p ->\n p.repositories.mavenLocal()\n p.repositories.withType(org.gradle.api.artifacts.repositories.MavenArtifactRepository).configureEach { repo ->\n def repoUrl = repo.url.toString()\n if (repoUrl.contains(\"repo.maven.apache.org/maven2\") || repoUrl.contains(\"repo1.maven.org/maven2\")) {\n repo.content { excludeModule(\"org.maplibre.gl\", \"android-sdk-opengl\") }\n }\n }\n }\n}\nrootProject.ext.set(\"org.maplibre.reactnative.nativeVersion\", \"13.4.1-step060d\")\nrootProject.ext.set(\"org.maplibre.reactnative.nativeVariant\", \"opengl\")\n`;
project.modResults.contents += `\n${marker}\ngradle.projectsEvaluated {\n allprojects { p ->\n p.repositories.mavenLocal()\n p.repositories.withType(org.gradle.api.artifacts.repositories.MavenArtifactRepository).configureEach { repo ->\n def repoUrl = repo.url.toString()\n if (repoUrl.contains(\"repo.maven.apache.org/maven2\") || repoUrl.contains(\"repo1.maven.org/maven2\")) {\n repo.content { excludeModule(\"org.maplibre.gl\", \"android-sdk-opengl\") }\n }\n }\n }\n}\nrootProject.ext.set(\"org.maplibre.reactnative.nativeVersion\", \"13.4.1-step060e\")\nrootProject.ext.set(\"org.maplibre.reactnative.nativeVariant\", \"opengl\")\n`;
return project;
});
};

View File

@@ -1113,6 +1113,10 @@ jni::Local<jni::Object<Layer>> NativeMapView::getLayer(JNIEnv& env, const jni::S
return LayerManagerAndroid::get()->createJavaLayerPeer(env, *coreLayer);
}
jni::jboolean NativeMapView::hasStyleLayer(JNIEnv& env, const jni::String& layerId) {
return map->getStyle().getLayer(jni::Make<std::string>(env, layerId)) != nullptr;
}
jni::jboolean NativeMapView::setStyleLayerFloatProperty(JNIEnv& env,
const jni::String& layerId,
const jni::String& propertyName,
@@ -1496,6 +1500,10 @@ jni::Local<jni::Object<Source>> NativeMapView::getSource(JNIEnv& env, const jni:
return jni::NewLocal(env, Source::peerForCoreSource(env, *coreSource, *rendererFrontend, *map));
}
jni::jboolean NativeMapView::hasStyleSource(JNIEnv& env, const jni::String& sourceId) {
return map->getStyle().getSource(jni::Make<std::string>(env, sourceId)) != nullptr;
}
void NativeMapView::addSource(JNIEnv& env, const jni::Object<Source>& obj, jlong sourcePtr) {
assert(sourcePtr != 0);
@@ -1728,6 +1736,7 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
METHOD(&NativeMapView::getLight, "nativeGetLight"),
METHOD(&NativeMapView::getLayers, "nativeGetLayers"),
METHOD(&NativeMapView::getLayer, "nativeGetLayer"),
METHOD(&NativeMapView::hasStyleLayer, "nativeHasStyleLayer"),
METHOD(&NativeMapView::setStyleLayerFloatProperty, "nativeSetStyleLayerFloatProperty"),
METHOD(&NativeMapView::setStyleLayerVisibility, "nativeSetStyleLayerVisibility"),
METHOD(&NativeMapView::setRasterParticleColors, "nativeSetRasterParticleColors"),
@@ -1750,6 +1759,7 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
METHOD(&NativeMapView::removeLayer, "nativeRemoveLayer"),
METHOD(&NativeMapView::getSources, "nativeGetSources"),
METHOD(&NativeMapView::getSource, "nativeGetSource"),
METHOD(&NativeMapView::hasStyleSource, "nativeHasStyleSource"),
METHOD(&NativeMapView::addSource, "nativeAddSource"),
METHOD(&NativeMapView::removeSource, "nativeRemoveSource"),
METHOD(&NativeMapView::addImage, "nativeAddImage"),

View File

@@ -281,6 +281,7 @@ public:
jni::Local<jni::Array<jni::Object<Layer>>> getLayers(JNIEnv&);
jni::Local<jni::Object<Layer>> getLayer(JNIEnv&, const jni::String&);
jni::jboolean hasStyleLayer(JNIEnv&, const jni::String&);
jni::jboolean setStyleLayerFloatProperty(JNIEnv&, const jni::String&, const jni::String&, jni::jfloat);
jni::jboolean setStyleLayerVisibility(JNIEnv&, const jni::String&, jni::jboolean);
jni::jboolean setRasterParticleColors(JNIEnv&, const jni::String&, const jni::Array<jni::jfloat>&, const jni::Array<jni::jfloat>&);
@@ -313,6 +314,7 @@ public:
jni::Local<jni::Array<jni::Object<Source>>> getSources(JNIEnv&);
jni::Local<jni::Object<Source>> getSource(JNIEnv&, const jni::String&);
jni::jboolean hasStyleSource(JNIEnv&, const jni::String&);
void addSource(JNIEnv&, const jni::Object<Source>&, jlong nativePtr);

View File

@@ -179,6 +179,24 @@ public final class MapLibreMap {
}
}
/**
* Returns whether the fully loaded Core style contains {@code layerId}.
*
* This intentionally queries Core by id instead of materialising a Java {@link
* org.maplibre.android.style.layers.Layer} peer. It therefore also supports layer types that
* are registered Core-only.
*/
public boolean hasStyleLayer(@NonNull String layerId) {
return nativeMapView.hasStyleLayer(layerId);
}
/**
* Returns whether the fully loaded Core style contains {@code sourceId}.
*/
public boolean hasStyleSource(@NonNull String sourceId) {
return nativeMapView.hasStyleSource(sourceId);
}
/**
* Replaces this map's style-scoped weather facade. The caller must have already verified the
* configured source and layers in the currently loaded style.

View File

@@ -139,6 +139,8 @@ interface NativeMap {
Layer getLayer(String layerId);
boolean hasStyleLayer(@NonNull String layerId);
boolean setStyleLayerFloatProperty(String layerId, String propertyName, float value);
boolean setStyleLayerVisibility(String layerId, boolean visible);
@@ -182,6 +184,8 @@ interface NativeMap {
Source getSource(@NonNull String sourceId);
boolean hasStyleSource(@NonNull String sourceId);
boolean removeSource(@NonNull String sourceId);
boolean removeSource(@NonNull Source source);

View File

@@ -950,6 +950,11 @@ final class NativeMapView implements NativeMap {
return nativeGetLayer(layerId);
}
@Override
public boolean hasStyleLayer(@NonNull String layerId) {
return !checkState("hasStyleLayer") && nativeHasStyleLayer(layerId);
}
@Override
public boolean setStyleLayerFloatProperty(String layerId, String propertyName, float value) {
return !checkState("setStyleLayerFloatProperty")
@@ -1116,6 +1121,11 @@ final class NativeMapView implements NativeMap {
return nativeGetSource(sourceId);
}
@Override
public boolean hasStyleSource(@NonNull String sourceId) {
return !checkState("hasStyleSource") && nativeHasStyleSource(sourceId);
}
@Override
public void addSource(@NonNull Source source) {
if (checkState("addSource")) {
@@ -1758,6 +1768,9 @@ final class NativeMapView implements NativeMap {
@Keep
private native Layer nativeGetLayer(String layerId);
@Keep
private native boolean nativeHasStyleLayer(String layerId);
@Keep
private native boolean nativeSetStyleLayerFloatProperty(String layerId, String propertyName, float value);
@@ -1811,6 +1824,9 @@ final class NativeMapView implements NativeMap {
@Keep
private native Source nativeGetSource(String sourceId);
@Keep
private native boolean nativeHasStyleSource(String sourceId);
@Keep
private native void nativeAddSource(Source source, long sourcePtr) throws CannotAddSourceException;

View File

@@ -1 +1 @@
13.4.1-step060d
13.4.1-step060e

View File

@@ -532,3 +532,10 @@ WeatherColorLayer 的 `ParticleGpuValidationUBO` designated initializer 补齐 `
- attach 现独立报告 `rn-weather-fields`、`rn-weather-color`、`rn-weather-particles` 状态;`WEATHER_OBJECTS_NOT_READY` 包含 reactTag、MapView/MapLibreMap/Style 状态、三个 ID 的 present/missing、Style source/layer count 和 weather-first 的有界 ID 摘要。
- Linux 生成并发布唯一诊断 AAR:`org.maplibre.gl:android-sdk-opengl:13.4.1-step060d`,build output 与 Maven Local AAR SHA-256 均为 `7391440f02e976f295caa265bc15e895a7c0183883a0e2cb08deef4cf42b0096`,含四 ABI。
- 当前 Expo 生成工程缺失 `NativeWeatherRuntimeBridgeSpec`,标准 codegen task 为 SKIPPED,故 bridge Kotlin compile 不能独立验证;未改生成工程绕过。后续由 CARBON7i 用此唯一 AAR 收集 Android attach 诊断,仍不进入 STEP-061。
# 2026-09-08 — STEP-060E CoreOnly Layer 存在性检查修复
- 根因确认:WeatherColorLayer 与 RasterParticleLayer 属于 CoreOnly layer,Core 能渲染但没有 Java Layer peer;故 STEP-060D bridge 用 Style.getLayer() 会对真实 layer 产生假阴性。
- 新增 MapLibreMap.hasStyleSource/hasStyleLayer,经 Android JNI 直接查询 Core Style;bridge attach 三项 weather object 判断统一使用该真值,保留既有成功路径与 WeatherRuntime 生命周期,不增 Java peer、重试或私有 registry。
- WEATHER_OBJECTS_NOT_READY 改用 styleUri(JSON style 为 <empty>)并移除不可靠的 Java layer/source 枚举;临时 Maven version 升为 13.4.1-step060e。
- 本轮不构建 AAR、不发布 Maven Local,APK/ADB/设备验收待人工构建该版本后进行;仍属于 STEP-060,未进入 STEP-061。