iOS 中 URLImage.createMaskAdapter() 为何无法创建中等尺寸遮罩?
结论
这些结果表明,iOS 上的问题与图像尺寸有关,可以按“疑似回归”继续排查。目前还无法确认具体根因、引入问题的版本或修复版本,也不能认定 410 × 410 超出了遮罩尺寸限制。
异常出现在 URLImage.createMaskAdapter() 调用的 Image.createMask() 内部,此时尚未下载或处理远程图片。应先检查遮罩生成过程,以及实际构建使用的 Codename One 实现。
目前没有直接证据表明此问题与 UIScene 有关。
遮罩写法本身是否有问题?
在黑色背景上绘制白色圆角矩形,符合 Image.createMask() 的用法。
该方法使用图像的蓝色通道,也就是像素值的最低 8 位,生成遮罩:
- 黑色对应
0,应用遮罩后为透明。 - 白色对应
255,应用遮罩后为不透明。 - 中间值对应不同程度的透明度。
因此,0xff000000 背景与 g.setColor(0xffffff) 的组合没有问题,无需为了修复异常而把背景改成透明色。官方 Image API 文档
小尺寸成功、大尺寸失败,只能说明尺寸会影响故障是否触发。原因可能在像素读取、缓冲区分配或尺寸相关的优化分支中,但这段堆栈没有异常消息,仅凭它无法区分。
先把失败阶段拆开
直接调用 createMask(),可以进一步确认问题是否独立于 adapter 创建逻辑。以下代码可在最小测试应用的 EDT 上运行:
import com.codename1.io.Log;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.URLImage;
private void testMask(int width, int height) {
String stage = "createImage";
try {
Image roundMask =
Image.createImage(width, height, 0xff000000);
stage = "draw";
Graphics g = roundMask.getGraphics();
g.setColor(0xffffff);
g.fillRoundRect(0, 0, width, height, 60, 60);
stage = "createMask";
Object mask = roundMask.createMask();
stage = "createMaskAdapter";
URLImage.ImageAdapter adapter =
URLImage.createMaskAdapter(mask);
Log.p("Mask OK: " + width + "x" + height);
} catch (RuntimeException ex) {
Log.p("Mask failed: " + width + "x" + height
+ ", stage=" + stage);
Log.e(ex);
}
}
使用 createMaskAdapter(Object mask) 重载,是为了在日志中区分这两个阶段。提前调用 createMask() 并不能绕过底层故障。官方 URLImage API 文档
再单独做一次像素读取测试,在绘制完成后执行:
int[] pixels = roundMask.getRGB();
Log.p("Pixel count: " + pixels.length);
这项测试应独立运行,避免提前读取像素改变后续执行状态。
如果 getRGB() 也失败,应先检查图像像素读取路径;如果读取成功而 createMask() 失败,就重点检查后续的转换、分配和优化代码。两种情况都需要结合对应构建的源码确认。
如何确认是否为近期回归?
用同一份最小代码,在同一台 iOS 设备上对比已知正常的旧构建和失败的新构建,是最有用的验证方法。
建议记录以下信息并完成对照测试:
- 设备型号、iOS 版本,以及 Codename One 依赖版本和构建日期。
- 原有四组尺寸的结果,并在成功与失败的尺寸之间补测,寻找故障边界。
- 固定宽度只改变高度,再固定高度只改变宽度,判断故障更接近总像素数还是某一边长。
- 不绘制圆角矩形时是否仍然失败,以区分图形内容与尺寸的影响。
如果旧安装包正常,相同代码重新构建后却失败,就更有理由怀疑构建产物发生了变化。要认定是框架回归,仍需核对两次构建的依赖、iOS 原生实现和构建工具是否一致。
堆栈中的 Image.createMask:1090 必须对应实际构建所用的源码。直接查看最新源码的第 1090 行,可能会找到完全不同的位置。
可增加 SIMD 开关对照
官方资料说明,部分 Image 操作使用了 SIMD 优化,其中包括 createMask;iOS 与 Android、JavaSE simulator 的执行路径也有差异。可以沿这个方向验证,但还不能认定此次故障由 SIMD 引起。官方 SIMD 实现说明
如果项目使用的 API 包含 Image.setSimdOptimizationsEnabled(boolean),可以在单独的测试构建中关闭优化,再运行相同用例:
Image.setSimdOptimizationsEnabled(false);
// 运行与默认设置下完全相同的尺寸测试
testMask(410, 410);
testMask(410, 614);
当前官方 API 文档列出了该开关,但旧版依赖未必提供,使用前应检查项目实际依赖。SIMD 开关 API
如果关闭优化后恢复正常,就有了进一步排查优化路径的证据,也可以评估是否将关闭优化作为临时处理办法。同时需要检查性能影响,并把对照结果提交给维护者。
临时处理与注意事项
最终解决方案需要有可复现的结果和对应修复作为依据。目前无法承诺升级到某个版本就能解决。
排查期间,可以采取以下措施:
- 如果已验证某套旧依赖和构建环境正常,可暂时固定该组合。
- 如果较小尺寸能满足显示需求,可暂时缩小遮罩,但需同步调整 placeholder 和输出尺寸,并检查清晰度。
- 提交 issue 时,附上最小工程、尺寸测试结果、完整设备日志,以及正常和失败构建的信息。
URLImage 的遮罩 adapter 要求结果尺寸与遮罩尺寸相符,因此缩小遮罩时,不能只修改 Image.createImage() 中的宽高。官方尺寸约束说明
不要仅靠捕获异常后返回空 adapter 来处理,否则图片可能失去预期的遮罩效果。RuntimeException - null 只表示异常没有提供可用消息,无法据此判断是内存不足或某个特定的 iOS 错误。
备注:内容仅供参考。
