简介:AR-Android是一套面向Android开发者的增强现实开发框架资源,适合希望入门AR技术、快速搭建AR应用场景的工程师或学习者。资源包内含完整的Unity工程目录、Android构建配置、材质与场景文件、可安装的Test.apk以及项目设置文件,并涉及追踪、图像处理、3D渲染等关键模块,能够帮助理解AR应用从开发到打包的完整链路。压缩包共49个文件,以asset、meta、unity工程配置和json清单为主,另有apk可直接体验,整体仅17.68MB,轻量且便于对照学习。目前已有188人学习浏览,适合结合Android与Unity基础边读边练。通过本资源可获得一个可直接运行的AR案例工程、工程结构解析和常用配置参考,有助于后续在SLAM标记识别、虚拟物体叠加与交互方面快速迁移实践。 AR-Android这个项目,说白了就是一句话:让Android手机摄像头画面里,稳定“长”出可交互的3D虚拟内容。我当时做这个项目的直接动机,是给一个家居电商客户做一个AR预览模块——用户在App里看中一款沙发或者书架,不需要去实体店,打开摄像头对着客厅扫一圈,AR系统识别出地面和墙面,以真实尺寸把沙发摆进画面里,用户能绕圈看、拖动位置、旋转角度、替换颜色,满意再下单。这个需求乍一听很炫酷,但实际落地远比普通App难得多:它同时涉及相机、传感器融合、三维渲染、手势交互、网络资源调度,任何一个环节抖动,体验都会瞬间拉垮。
这个项目适合两类人来参考。一类是完全没有接触过AR的Android开发者,想了解ARCore的接入成本和运行逻辑;另一类是产品经理或者技术负责人,在考虑要不要自研AR功能,想评估它的风险和复杂度。我下面写的不是官方文档的翻译,而是我把一套AR功能从验证到发布完整走下来的记录,包含踩坑、取舍和那些文档里不会写清楚的细节。
1. 项目概述:AR-Android 到底要解决什么问题
1.1 业务场景与最终交付形态
我们最终交付的不是一个玩具Demo,而是嵌在购物App里的一个业务模块。用户路径是这样:商品详情页点击“AR预览”进到AR相机界面,系统自动检测水平面,用户点击地面放置1:1尺寸模型,模型支持拖动、缩放、旋转、切换材质,确认满意再点“加入购物车”返回。这个路径看着短,但每一步都有大量细节。比如“自动检测水平面”就牵涉到相机扫视范围、平面置信度;“1:1尺寸”依赖于AR场景的单位换算和终端标定,模型文件差几厘米,摆在真实客厅里一眼就能看出来。
AR预览界面也不是全屏一镜到底,页面底部还保留了一个半透明的商品信息卡片,显示商品名、价格、颜色选项,卡片和AR场景需要同时存在于同一屏上。这一块后面我会专门讲,它看着像是常规UI问题,但和ARView叠加之后,会出现触摸事件穿透、锚点被UI遮挡、动画状态抢占等奇怪现象,处理起来非常费精力。
1.2 为什么技术路线选原生而不是 Unity
立项时团队内部开过一次会,有人说直接用Unity,用Vuforia或AR Foundation,将来跨平台还能继续用。我当时坚持先评估Android原生方案,理由有三个。
第一,我们只做Android端,为了单平台引入一整套Unity引擎,包体至少增加几十MB,而且Unity和原生App之间的桥接层会带来通信成本和版本兼容成本,调试起来很痛。第二,这个功能高度依赖系统相机、蓝牙、文件访问等能力,用原生ARCore做,可以无缝使用Android生态里成熟的第三方工具,比如图片加载、手势框架、性能分析工具。第三,Unity的AR方案底层也是基于ARCore或ARKit再做一层封装,既然底层没变,直接掌握ARCore的原生控制力反而更自由。当然,如果项目一开始就要iOS、Android、甚至Vision Pro多端跑,那AR Foundation会合理很多,取舍要看场景,而不是盲目跟风。
2. 技术选型:ARCore 是绕不开的基础设施
2.1 三大核心能力拆解
ARCore是Google针对Android推出的增强现实SDK,它解决的是AR里最底层的三件事:运动追踪、环境理解、光照估算。你可以把这三件事理解成一个AR场景的地基。运动追踪负责回答“手机在哪里、朝哪个方向”,它通过摄像头画面里的视觉特征点,加上手机内置的IMU惯性测量单元做融合,不断推算6自由度位姿。这有点像你在陌生城市里走路,光看建筑不够,还要靠脚感和方向感一起判断自己走到哪了。
环境理解负责回答“房间的地砖和墙面在哪里”,ARCore会识别出画面中的水平面和垂直面,并抽象成Plane对象给你,方便后续做物体放置和碰撞判断。光照估算负责回答“这个房间的光线是冷是暖”,它从相机画面中提取环境光强度与色温,帮你把虚拟模型的材质调成和真实环境相协调的样子。这三项能力对外表现就是Session、Frame、Plane、Anchor、HitResult这一组API对象,理解了它们的职责,就相当于拿到了进入AR领域的钥匙。
2.2 不同层级的选型对比
实操层面我们把技术方案分成三个层级,你可以根据团队情况对号入座。
| 方案 | 底层 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|---|
| 原生ARCore API | ARCore | 控制力强、无额外依赖、运行稳定 | 渲染管线要自己维护,工作量较大 | 团队有OpenGL基础,追求极致性能 |
| SceneView / Sceneform | ARCore | 封装了模型加载、渲染循环、触摸处理 | 部分特性滞后于官方,模型格式受限 | 中小团队快速出Demo、中小型业务 |
| AR Foundation + Unity | ARCore/ARKit | 跨平台、编辑器可视化 | 包体大、桥接层复杂 | 需要多端发布、做复杂3D内容的产品 |
我们最终选择了原生ARCore + SceneView组合:用ARCore负责环境感知,用SceneView做3D场景渲染,手势交互则在View层面自己处理。这个组合对中小团队最友好,既保留了原生的能力,又不用从光照渲染开始全部手写。后面我会按这个组合来讲解。
3. 环境准备与工程搭建
3.1 版本匹配:Android Studio、AGP 与 Kotlin 版本
AR-Android项目的工程环境,很多新人卡在第一步就翻车。先说结论,我用的这套组合目前很稳:Android Studio Hedgehog(2023.1.1 Patch 2)搭配AGP 8.2.2,Gradle 8.2,Kotlin 1.9.22,minSdk 24,targetSdk 34。经常有人问Hedgehog是否支持AGP 8版本,答案很明确:Hedgehog默认支持AGP 8.2.x,AGP 8.0、8.1也都能跑,但建议直接上8.2,低版本的AGP在配置新SDK和ARCore依赖时偶尔会有编译资源找不到的问题。
这里强调一个原则:版本不匹配是IDE里一片红最多的原因。AGP、Gradle、Android Studio三者有严格的兼容矩阵,升级Studio后别急着拉旧工程,先去SDK Manager把对应的Build Tools和Platform Tools装齐。我的建议是新建项目时让Studio自动生成配套版本,再把ARCore相关依赖一段段贴进去,这样几乎不会踩版本雷。
3.2 权限、硬件特性与 ARCore 可用性检查
AR用到的最基础权限是相机,Android 6.0及以上需要运行时权限申请。同时要在AndroidManifest中声明AR特性,这样不支持AR的设备会直接在应用市场被过滤,或者在进入AR功能时给用户友好提示。
<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera.ar" android:required="true" />另外,如果模型需要从服务器下载,记得加INTERNET权限。这里有一个产品层面的细节:如果required设为true,代码里可以少写一层兼容判断,但你的App在无AR能力的手机上会被商店直接过滤;如果App还有普通商品浏览功能,建议改成false,运行时判断当前设备是否支持AR,支持才展示AR入口。这个产品判断我们做得比较早,避免了很多客诉。
3.3 初始化 ARCore 会话与相机画面输出
ARCore的会话Session是整个AR世界的中枢。初始化时要干两件事:先确认当前设备是否支持ARCore,再配置工作模式。如果设备首次启动没装ARCore服务,官方SDK会引导去应用商店下载,开发时如果看到这个弹窗,多半是模拟器没有安装ARCore,真机一般不会。初始化代码大致长这样:
class ArActivity : AppCompatActivity() { private lateinit var session: Session override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_ar) if (!Session.isSupported(this)) { Toast.makeText(this, "当前设备不支持ARCore", Toast.LENGTH_LONG).show() finish() return } session = Session(this) val config = Config(session) config.focusMode = Config.FocusMode.AUTO config.planeFindingMode = Config.PlaneFindingMode.HORIZONTAL_AND_VERTICAL session.configure(config) } }注意,Session的创建要在onResume之前完成,onPause和onResume时分别调用session.pause()和session.resume(),这样才能在切后台再回来时保持AR世界不飞出天际。相机画面默认不会显示,需要在GLSurfaceView的GLRenderer中把摄像头纹理和虚拟世界一起渲染,这个渲染循环就是后面所有视觉内容挂载的地方。
4. 核心功能实现:从平面检测到模型放置
4.1 相机画面渲染与 AR 世界的坐标系
如果你用SceneView,渲染循环已经被封装好,XML里直接放ARSceneView即可。如果走原生渲染,需要在GLSurfaceView的onDrawFrame里先调用session.setCameraTextureName(textureId)把相机画面绑定到OpenGL纹理,再调用session.update()拿到最新帧。这一步决定了相机画面和虚拟物体的显示顺序,写反了会出现画面黑屏或模型闪烁。
我第一次接触AR时,困扰很久的是坐标系问题。ARCore用右手坐标系,以启动AR时的设备位置为原点,x轴向右、y轴向上、z轴指向人。你在真实世界里放一个虚拟物,本质就是给它一个相对于这个原点的坐标,并让这个坐标在设备移动时保持相对世界稳定,而不是相对屏幕稳定。这和我们平时用View坐标系做UI完全是两套思维,一开始不适应很正常,但千万别绕过去,坐标系理解不到位,后面所有交互都会乱。
4.2 命中测试:把虚拟物“放”到你想放的位置
放置物体的核心API是Frame.hitTest。用户点击屏幕坐标,ARCore会从相机位置发射一条射线,检测它和已识别平面、物体的交点,返回一组HitResult。我们对平面命中做过滤,找到最近的一个平面点,然后在该点创建锚点Anchor,这样即使设备移动,锚点也会被ARCore“钉”在三维地图上,虚拟物就不会漂走。
fun handleTap(tap: MotionEvent, frame: Frame) { val hits = frame.hitTest(tap.x, tap.y) val planeHit = hits.firstOrNull { it.trackable is Plane } if (planeHit == null) { // 没点到平面,给用户一个提示动画 showRetryHint() return } val anchor = planeHit.createAnchor() // 在anchor位置渲染3D模型,后续模型的所有变换都挂在anchor上 renderModelAt(anchor) }命中测试有一个关键点:它只能作用于当前Frame的有效结果,要确保在session.update()之后立刻调用。如果没命中任何东西,多半是平面还没有足够特征点覆盖,让用户把手机左右缓慢扫一下,等ARCore“看清”地面再点。这个提示文案一定要做进用户引导里,否则用户会以为App坏了。
4.3 模型加载、移动、旋转与动画队列
模型我们统一用glTF格式,支持PBR材质,在SceneView里加载很方便。放置模型的完整流程是:先根据Anchor创建AnchorNode,再把glTF模型作为子节点挂进去,设置局部缩放让真实尺寸一致。所谓1:1尺寸,就是glTF模型按米为单位建模,ARCore的坐标系本身也是米制,所以模型文件里建1.8米的物体,放到AR场景里就是1.8米。
交互部分,我建议单独写一个手势控制器,不要在业务Activity里堆逻辑。移动物体时监听ACTION_MOVE,每次用hitTest打到地面新位置,然后调用anchorNode的setLocalPosition;旋转用双指旋转角度,缩放用双指缩放比例。这里有一个容易被忽视的坑:当多个手势同时触发时,位移和旋转状态会互相抢节点,导致模型跳变。我的做法是把每个手势指令放入一个队列,串行执行,保证同一时间只处理一个变换动作。这个“动画队列”思路也可以延伸到模型切换、材质替换等场景,避免连续点击导致渲染状态错乱。
5. UI 集成与画面增强
5.1 与 CoordinatorLayout 和底部面板的共存方案
AR界面不是全屏黑色,它要叠商品信息面板。我们的页面结构是根布局用CoordinatorLayout,ARSceneView放在最底层,上面叠一个带Banner轮播和商品卡片的BottomSheetBehavior面板。初版上线前,我们花了整整两天解决两类奇怪问题。
第一类是触摸事件穿透。ARSceneView全屏接收手势,但底部面板所在区域的触摸被View层次吃掉了,用户想拖模型时,手指从面板边缘滑到屏幕中心,模型只动了一下又不动了。解决方案是在面板折叠状态下主动区分触摸事件:面板区域拦截点击和滑动,非面板区域直接传给ARView处理。第二类是页面动效抢焦点。Banner轮播自带定时器,商品卡片的折叠和展开又触发CoordinatorLayout的Behavior回调,两个动画同时执行会把界面卡出掉帧。我们把需要串行执行的动画放进同一个AnimatorSet里,配合AR侧的指令队列,体验才顺滑起来。
5.2 光照估计与遮挡效果
光有真实感模型还不够,模型要“融进”环境里,关键在光照和遮挡。ARCore的光照估算会返回环境光强度和环境色温,我们把这两个值取出来设置到模型材质上。效果很直观:室内暖黄灯光下,AR沙发的材质会自动偏向暖色,不会显得像一块贴图挂在画面里。
遮挡效果就是让真实物体能遮住虚拟物体,比如你放了一个沙发模型,人从沙发前面走过,人应该挡在沙发前面。ARCore的Depth API可以输出一帧深度图,OpenGL渲染时用它做遮挡测试,就能实现比较自然的遮挡。但这个API依赖设备上的深度传感器或算法,不是所有机器都支持。我们的策略是:支持深度API的设备开启遮挡,不支持的自动降级为透明混合模式,避免强行渲染出破碎画面。
5.3 适配 AR 眼镜与车载等特殊形态
做完手机端AR之后,我们还做了两件延伸适配,一件是AR眼镜,一件是车载副驾屏。AR眼镜的核心问题不是ARCore,而是交互方式的转换和使用时长限制。眼镜端没有触控屏,所有手势要换成头动控制或外接控制器,用户操作路径要做到极简,界面元素必须放在眼动舒适区,不能像手机一样把按钮铺满屏幕。车载端的问题则是性能功耗和视角,汽车前挡风玻璃后面那块屏离人远,画面刷新不稳定容易晕车,所以我们对渲染帧率、模型面数做了严格的向下兼容。
这两个形态都不是ARCore要额外处理的工作,但它们都在提醒同一个道理:AR功能不只是技术问题,更是交互设计和内容策略的问题。技术栈可以复用,但产品差异会非常大。
6. 常见问题与调试实录
6.1 设备兼容性:ARCore 服务缺失与不支持列表
ARCore并不是所有Android手机都支持,Google维护了一份支持设备名单。因此线上App必须做前置检查。三件事:第一,在App启动时检测Session.isSupported,不支持的设备隐藏AR入口,或者提示替代方案;第二,进入AR页面时判断ARCore服务是否最新,可以主动调用ARCore安装请求接口,让用户一键更新;第三,模拟器上开发无法使用AR功能,这是正常的,我们后来都是真机调试,建议项目里准备一台Pixel和一台主流国产机型,覆盖不同传感器情况。
我见过很惨痛的一个案例,同事在模拟器上调试了一周,一直以为代码有问题,后来发现是模拟器没有摄像头管线,白白浪费了时间。所以提前把这层判断写清楚,能省很多事。
6.2 性能与稳定性:掉帧、发热、闪退
AR应用比普通应用敏感得多,因为整个渲染是持续在跑的。性能问题主要来自三方面:模型面数过高、相机纹理分辨率过大、后台任务抢占CPU。我们的经验是,单个模型像素面数控制在10万以内,纹理控制在2K以内;尽量用glTF Draco压缩;相机画面分辨率不要直接拉满,1280x720对大多数场景都够用。实测下来,同样一个模型,压缩前掉帧明显,压缩后帧率能稳定在30帧以上。
发热问题一样要重视。AR本身耗电,如果同时开启相机闪光灯、蓝牙、GPS,那手机几分钟就发烫。我们在AR页面限制了蓝牙扫描和GPS更新频率,换用系统广播触发的低频率模式,虽然日志里偶尔会提示定位不准,但用户体感反而更好。
内存闪退则多半是因为模型重复加载。同一个沙发模型,用户每次进AR页都new一份,来回几次就把内存吃满了。后来我们做了一个简单的模型缓存池,全局只保留一份实例,效果非常明显。
6.3 几个容易被忽略的小坑
第一个坑是Android 11及以后的网络访问权限变化。我们的在线模型下载地址如果用的是HTTP明文链接,在targetSdk 30以上默认被拦截,表现为AR模型一直加载不出来。这个问题在手机浏览器里看是正常的,一进App就失败,排查半天才发现是网络安全配置问题,需要在manifest里用networkSecurityConfig放行指定域名,或者把服务端切到HTTPS。
第二个坑是Activity重建。从后台切回来时,系统可能重建Activity,如果Session没有正确恢复,AR场景会变成黑屏或者锚点全飞。我们用LifecycleCallback统一管理Session的pause/resume,并且在onSaveInstanceState里保存当前锚点列表,恢复后重建锚点,配合检查Session状态,基本解决了这个问题。
第三个坑是模型加载耗时。用户已经进入AR页面,模型还在网络下载,如果没有任何提示,用户会以为功能卡住了。我们需要在加载状态做一个极简loading动画,并且一旦模型加载失败,要允许用户重试,而不是一直转圈。这个交互细节技术含量不高,但它决定了用户对AR功能的第一印象。
7. 写在最后:几条实战心得
项目做完以后,我最深的感受是:AR-Android不是一个天花板很高的领域,但它对细节的要求是普通App开发的好几倍。它强迫你去理解相机、传感器、图形渲染、UI事件分发,甚至产品文案怎么写。回头看,最值得庆幸的是我们没有一上来就奔着大而全的AR体验去,而是先做了“放一个模型在平面上”这个最小闭环,验证性能、稳定性和用户接受度,再逐步叠加材质切换、遮挡、眼镜适配,每一步都有明确依据。如果让我给后来者一个建议,那就是先别急着追求炫酷效果,把命中测试、锚点生命周期和资源复用这三件事做稳,你的AR功能已经超过市面上七成应用了。
最后再分享一个小技巧:调试AR时,务必开着系统的“显示触摸操作”和“指针位置”,配合ARCore的DebugDraw显示平面网格,调试效率能提高一倍。这个不起眼的设置,在我整个项目周期里帮我节省了至少一个星期的排查时间。
本文还有配套的精品资源,点击获取