Android Studio ARCore多图跟踪锚点未移除致卡顿崩溃问题求解
ARCore多图像跟踪功能异常修复方案
问题说明
现有基于ARCore实现的多图像跟踪功能可完成基础识别渲染,但存在两类异常:
- 摄像头移出标记识别范围后,对应Anchor未被自动移除,场景内残留无效渲染内容
- 扫描识别超过2个标记时出现明显性能卡顿,最终触发应用崩溃
此前尝试在AugmentedImage的TrackingState为LAST_KNOWN_POSE状态时执行锚点分离、场景节点移除操作,未达到预期效果。
原有参考代码
MainActivity基础结构
public class MainActivity extends AppCompatActivity implements Scene.OnUpdateListener { private CustomArFragment arFragment; public DataBaseHelper mydb; public TextView mytext ; private Button button; AnchorNode anchorNode; Anchor anchor; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); arFragment=(CustomArFragment)(Object)getSupportFragmentManager().findFragmentById(R.id.scene_frag); assert arFragment != null; arFragment.getArSceneView ().getScene ().addOnUpdateListener (this); } }
AugmentedImageDatabase配置逻辑
public void setupdataBase(Config config, Session session){ Bitmap mark1= BitmapFactory.decodeResource(getResources(),R.drawable.marks1); AugmentedImageDatabase aid=new AugmentedImageDatabase(session); Bitmap mark2= BitmapFactory.decodeResource(getResources(),R.drawable.marks2); Bitmap mark3= BitmapFactory.decodeResource(getResources(),R.drawable.marks3); Bitmap mark4= BitmapFactory.decodeResource(getResources(),R.drawable.marks4); Bitmap mark5= BitmapFactory.decodeResource(getResources(),R.drawable.markss5); //添加识别标记 aid.addImage("mark1",mark1); aid.addImage("mark2",mark2); aid.addImage("mark3",mark3); aid.addImage("mark4",mark4); aid.addImage("mark5",mark5); config.setAugmentedImageDatabase(aid); }
帧更新回调核心逻辑
@Override public void onUpdate(FrameTime frameTime) { Frame frame=arFragment.getArSceneView ().getArFrame (); assert frame != null; Collection<AugmentedImage> images=frame.getUpdatedTrackables (AugmentedImage.class); for (AugmentedImage image:images) { String marker=image.getName (); String[] tempname={"mark1","mark2","mark3","mark4","mark5"}; for (String s : tempname) { if (s.equals (marker)) { createModel (image, marker); } } } } @SuppressLint("SetTextI18n") private void createModel(AugmentedImage image, String marker) { ViewRenderable .builder () .setView (this,R.layout.text_main) .build () .thenAccept (viewRenderable -> setupModel(viewRenderable,image,marker)); }
渲染逻辑
@SuppressLint("SetTextI18n") private void setupModel(ViewRenderable viewRenderable, AugmentedImage image, String marker) { mytext=(TextView)viewRenderable.getView ().findViewById (R.id.planetInfoCard); anchor=image.createAnchor (image.getCenterPose ()); anchorNode=new AnchorNode (anchor); anchorNode.setRenderable (viewRenderable); anchorNode.setParent (arFragment.getArSceneView ().getScene ()); arFragment.getArSceneView ().getScene ().addChild (anchorNode); }
问题根因
- 锚点残留问题:错误选择了清理触发时机,
LAST_KNOWN_POSE属于追踪临时中断的PAUSED状态,此时ARCore仍在尝试恢复追踪,并非识别完全丢失;同时未给每个标记建立独立的锚点、节点映射,清理时无法定位到对应标记的关联资源。 - 卡顿崩溃问题:
- 帧回调每触发一次、只要识别到已入库标记就重复创建ViewRenderable、Anchor、AnchorNode实例,同一张标记会生成大量重复对象占用内存
- 仅用单个全局变量存储最新的anchor和anchorNode,多图识别时旧实例的引用被覆盖,无法被回收造成内存泄漏,累积后触发OOM崩溃
- 重复调用节点添加逻辑,
setParent已经完成节点挂接,额外调用addChild会触发渲染层异常 - 配置图像数据库时未传入标记物理尺寸,ARCore需要额外计算位姿,额外消耗性能
修复实现
第一步:替换成员变量
将原有单个anchor、anchorNode变量替换为映射表,同时增加可复用的渲染实例缓存,避免重复创建对象:
// 存储每个标记对应的锚点、渲染节点,key为标记名称 private final HashMap<String, Anchor> markerAnchorMap = new HashMap<>(); private final HashMap<String, AnchorNode> markerNodeMap = new HashMap<>(); // 提前加载可复用的文本渲染实例,避免帧回调中重复加载 private ViewRenderable sharedTextRenderable;
第二步:提前加载渲染资源
在onCreate中提前完成渲染资源加载,不要在识别触发时动态加载:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); arFragment=(CustomArFragment)(Object)getSupportFragmentManager().findFragmentById(R.id.scene_frag); assert arFragment != null; // 预加载渲染资源 ViewRenderable.builder() .setView(this, R.layout.text_main) .build() .thenAccept(renderable -> sharedTextRenderable = renderable); arFragment.getArSceneView().getScene().addOnUpdateListener(this); }
第三步:重写帧回调逻辑
区分三种追踪状态,识别到有效追踪时仅在未创建节点的情况下新建实例,追踪完全停止时彻底清理对应资源:
@Override public void onUpdate(FrameTime frameTime) { Frame frame = arFragment.getArSceneView().getArFrame(); // 帧为空或渲染资源未加载完成时直接跳过 if (frame == null || sharedTextRenderable == null) return; Collection<AugmentedImage> updatedImages = frame.getUpdatedTrackables(AugmentedImage.class); List<String> validMarkers = Arrays.asList("mark1","mark2","mark3","mark4","mark5"); for (AugmentedImage image : updatedImages) { String markerName = image.getName(); // 跳过无效识别结果 if (!validMarkers.contains(markerName)) continue; TrackingState trackState = image.getTrackingState(); switch (trackState) { case TRACKING: // 仅当该标记未创建过渲染节点时新建,避免重复生成 if (!markerNodeMap.containsKey(markerName)) { createMarkerRender(image, markerName); } break; case STOPPED: // 追踪完全终止,彻底清理关联资源 if (markerNodeMap.containsKey(markerName)) { AnchorNode targetNode = markerNodeMap.get(markerName); Anchor targetAnchor = markerAnchorMap.get(markerName); // 从场景移除节点 arFragment.getArSceneView().getScene().removeChild(targetNode); // 分离锚点、释放引用 targetNode.setRenderable(null); targetNode.setParent(null); targetAnchor.detach(); // 从映射表移除记录 markerNodeMap.remove(markerName); markerAnchorMap.remove(markerName); } break; case PAUSED: // 即原LAST_KNOWN_POSE状态,临时丢帧不做处理,等待追踪恢复或终止 break; } } }
第四步:简化渲染创建逻辑
直接使用预加载的渲染资源,将实例存入映射表,避免重复添加节点:
@SuppressLint("SetTextI18n") private void createMarkerRender(AugmentedImage image, String markerName) { // 创建锚点和节点 Anchor newAnchor = image.createAnchor(image.getCenterPose()); AnchorNode newNode = new AnchorNode(newAnchor); newNode.setRenderable(sharedTextRenderable); newNode.setParent(arFragment.getArSceneView().getScene()); // 存入映射表 markerAnchorMap.put(markerName, newAnchor); markerNodeMap.put(markerName, newNode); // 给当前标记的文本赋值,不要使用全局TextView引用避免串位 TextView cardText = (TextView) sharedTextRenderable.getView().findViewById(R.id.planetInfoCard); cardText.setText(markerName); // 替换为对应标记要显示的实际内容 }
第五步:优化图像数据库配置
添加标记时传入物理实际宽度(单位为米),可大幅提升追踪稳定性、降低性能消耗:
// 示例:标记实际宽度为10厘米时传0.1f,根据实际标记尺寸修改 aid.addImage("mark1", mark1, 0.1f); aid.addImage("mark2", mark2, 0.1f); aid.addImage("mark3", mark3, 0.1f); aid.addImage("mark4", mark4, 0.1f); aid.addImage("mark5", mark5, 0.1f);
额外优化建议
- 若不同标记需要显示完全不同的视图内容,不要复用单个ViewRenderable,可提前将各标记对应的渲染实例加载完成后存入缓存,不要在帧回调中动态加载
- 若同时识别的标记数量较多,可设置识别距离上限,过远的标记不创建渲染节点,降低渲染压力
- 不要在onUpdate帧回调中做任何耗时操作(如数据库读写、网络请求、大图解码),避免阻塞渲染线程
内容的提问来源于stack exchange,提问作者Harshal Patil
相关产品推荐
相关产品推荐

