Unity调用原生安卓入门:JNI桥接、AAR打包与权限回调工具集
简介这份工具集面向需要在 Unity 中调用原生安卓能力的开发者尤其适合刚接触 Unity 与 Android 交互、希望快速跑通基础链路的入门者。内容围绕 UnityActivity、UnityAppContext、PackageManager、RunOnUIThread 等基础方法展开并覆盖 Toast、Log 以及 Java 与 C# 字符串互转等常用操作同时整理了获取 App 列表、判断服务与应用是否运行、打开/安装/卸载 App、发送广播以及获取 WiFi 状态、安卓版本、原生类型 ID、内置 SD 卡路径等实用封装。资源共 45 个文件以 cs 脚本、asset 配置、meta 与 xml 文件为主另含 jar 包和 unity 场景压缩包约 48KB结构轻量便于直接导入参考。目前已有 743 人学习适合作为 Unity 调用原生安卓的入门跳板与日常开发速查工具。1. 从一次打包翻车说起这套 Unity 调用原生安卓工具集到底解决什么去年帮一个做跨平台应用的小团队看包Unity 侧功能都跑通了一打安卓真机就出问题权限弹窗不弹、Toast 不显示、拿不到系统电量日志里全是ClassNotFoundException。他们一开始想用纯 C# 硬扛结果发现安卓那套东西绕不过去最后还是得回到 Unity 调用原生安卓这条路上。这套「Unity 调用原生安卓 入门参考 工具集」就是干这个的——它把 Unity 和 Android 之间那层 JNI 桥怎么搭、AAR 怎么打、权限怎么回调、常见 API 怎么封装整理成一套能直接照着复现的参考工程和工具脚本。适合两类人刚接触 Unity 安卓混合开发、被 JNI 报错劝退的新手以及想把手头零散调用代码收敛成规范工具集的熟手。下面我按「原理立住 → 动手复现 → 避坑排查 → 进阶封装」的顺序拆一遍中间该贴的代码和参数一个不省。2. Unity 与 Android 的通信底座JNI、AAR 与 AndroidJavaClass 怎么串起来2.1 为什么不能只用 C#非得走 JNIUnity 的 C# 运行在 Mono/IL2CPP 上安卓系统 API 是 Java/Kotlin 写的两者不在一个运行时里。想让 Unity 拿到安卓原生的能力——读取Build信息、调用Vibrator、申请运行时权限、拉起系统分享——中间必须有一座桥这座桥就是 JNIJava Native Interface。Unity 把这层桥封装成了AndroidJavaClass、AndroidJavaObject、AndroidJavaProxy三个类你写 C# 的时候感觉像在调 Java底层其实是 JNI 在传引用、转类型。常见做法是把要用的安卓逻辑写成一个 Android Library 模块编译成 AAR丢进 Unity 的Assets/Plugins/Android目录然后在 C# 里用AndroidJavaClass找到对应的 Java 类去调。这套工具集的价值就在于它把「Java 侧怎么写、AAR 怎么出、C# 侧怎么调」这条链路固化成了模板不用每次从零配 Gradle。2.2 环境与目录先把工程骨架搭对动手前先确认版本边界这是最容易翻车的地方。Unity 2019 之后安卓插件目录结构变过2021 LTS 和 2022 LTS 对 Gradle 模板的处理也不一样。我一般按下面这套来项目建议值说明Unity 版本2021.3 LTS 及以上低于此版本 Gradle 模板差异大AAR 冲突多Android Gradle Plugin7.0与 Unity 内置 Gradle 对齐避免重复类minSdkVersion24覆盖绝大多数真机权限 API 稳定targetSdkVersion33注意 Android 13 的通知权限变化AAR 存放路径Assets/Plugins/Android放错目录 Unity 不会打进包目录骨架大致是这样工具集里也是这个结构Assets/ Plugins/ Android/ unitycallandroid.aar # 编译好的安卓库 AndroidManifest.xml # 权限与组件声明 Scripts/ AndroidBridge.cs # C# 调用入口 PermissionHelper.cs # 权限回调封装提示AndroidManifest.xml如果和 AAR 里的清单冲突Unity 打包时会合并合并规则以主工程为准权限声明建议统一写在主工程的清单里别散落在各处。2.3 Java 侧写一个能被 Unity 找到的类安卓库这边核心是暴露一个静态方法给 C# 调。工具集里给的是一个UnityBridge类负责返回设备信息和触发 Toastpackage com.example.unitybridge; import android.app.Activity; import android.content.Context; import android.widget.Toast; import android.os.Build; public class UnityBridge { // 静态方法C# 侧通过 AndroidJavaClass.CallStatic 调用 public static String getDeviceModel() { return Build.MANUFACTURER Build.MODEL; } // 需要 Activity 上下文的方法用 runOnUiThread 保证在主线程弹 Toast public static void showToast(final Activity activity, final String msg) { activity.runOnUiThread(new Runnable() { Override public void run() { Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show(); } }); } }逻辑说明getDeviceModel是纯静态、无上下文依赖的方法C# 侧调用最简单showToast涉及 UI安卓要求必须在主线程操作所以用runOnUiThread包一层否则会抛CalledFromWrongThreadException。参数上Activity要从 Unity 侧传进来不能在里面 new因为安卓的 Activity 生命周期由系统管。编译这个模块用 Gradle./gradlew :unitybridge:assembleRelease # 产物在 unitybridge/build/outputs/aar/unitybridge-release.aar把 AAR 复制到Assets/Plugins/AndroidUnity 会自动识别。注意 AAR 里如果带了classes.jar之外的依赖要么一起打进去要么在 Unity 侧用mainTemplate.gradle补依赖否则运行时报NoClassDefFoundError。2.4 C# 侧AndroidJavaClass 的正确打开方式C# 这边调用要区分「静态方法」和「实例方法」工具集里AndroidBridge.cs把两种都封了using UnityEngine; public class AndroidBridge { private const string BRIDGE_CLASS com.example.unitybridge.UnityBridge; // 调用静态方法用 AndroidJavaClass public static string GetDeviceModel() { using (var bridge new AndroidJavaClass(BRIDGE_CLASS)) { return bridge.CallStaticstring(getDeviceModel); } } // 调用需要 Activity 的方法先拿当前 Activity再传进去 public static void ShowToast(string msg) { using (var unityPlayer new AndroidJavaClass(com.unity3d.player.UnityPlayer)) using (var activity unityPlayer.GetStaticAndroidJavaObject(currentActivity)) using (var bridge new AndroidJavaClass(BRIDGE_CLASS)) { bridge.CallStatic(showToast, activity, msg); } } }逻辑说明AndroidJavaClass对应 Java 的类AndroidJavaObject对应实例。currentActivity是 Unity 暴露的静态字段拿到它才能做需要上下文的操作。using包裹是为了及时释放 JNI 引用长时间不释放会累积导致内存问题这点新手很容易忽略。参数上CallStaticT的泛型是返回类型传参顺序必须和 Java 方法签名严格一致类型对不上会直接崩。3. 权限、回调与线程把运行时权限和异步结果接回 C#3.1 运行时权限为什么不能只写清单Android 6.0 之后危险权限相机、定位、存储、录音光在AndroidManifest.xml里声明没用必须在运行时动态申请用户点了「允许」或「拒绝」之后还要拿到回调。Unity 自己不带这套回调机制得靠原生侧实现onRequestPermissionsResult再通过 JNI 把结果传回 C#。这是 Unity 调用原生安卓里最容易断链的一环。工具集的做法是Java 侧写一个PermissionHelperC# 侧用AndroidJavaProxy接收回调。先看 Java 侧package com.example.unitybridge; import android.app.Activity; import android.content.pm.PackageManager; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; public class PermissionHelper { public static final int REQ_CODE 1001; public static boolean hasPermission(Activity activity, String permission) { return ContextCompat.checkSelfPermission(activity, permission) PackageManager.PERMISSION_GRANTED; } public static void requestPermission(Activity activity, String permission) { ActivityCompat.requestPermissions(activity, new String[]{permission}, REQ_CODE); } }逻辑说明hasPermission先查当前状态避免重复申请requestPermission用ActivityCompat发起申请REQ_CODE是自定义请求码回调时靠它区分是哪个请求。注意这里依赖了androidx.coreAAR 打包时要把这个依赖带上否则运行时报找不到ActivityCompat。3.2 用 AndroidJavaProxy 接住权限回调C# 侧要接住onRequestPermissionsResult得继承AndroidJavaProxy把 Java 接口映射过来using UnityEngine; using System; public class PermissionCallback : AndroidJavaProxy { private Actionstring, bool _onResult; // 构造时传入要实现的 Java 接口全名 public PermissionCallback(Actionstring, bool onResult) : base(androidx.core.app.ActivityCompat$OnRequestPermissionsResultCallback) { _onResult onResult; } // 方法名和签名必须和 Java 接口一致 void onRequestPermissionsResult(int requestCode, string[] permissions, int[] grantResults) { for (int i 0; i permissions.Length; i) { bool granted grantResults[i] 0; // 0 PERMISSION_GRANTED _onResult?.Invoke(permissions[i], granted); } } }逻辑说明AndroidJavaProxy的构造参数是 Java 接口的全限定名$用于表示内部接口。方法名、参数类型、返回类型必须和 Java 侧完全对应大小写都不能错否则回调静默失败——这是最阴的坑不报错但就是不触发。grantResults[i] 0里的 0 对应PackageManager.PERMISSION_GRANTED别硬编码成别的值。3.3 线程与生命周期回调回来时 Activity 可能已经没了安卓的权限回调是异步的用户可能过几秒才点这期间如果 Unity 场景切换、Activity 重建回调回来时上下文就失效了。血泪经验是回调里不要直接操作 UI 或假设 Activity 还在先判空再处理。工具集里在 C# 侧加了一层保护public static void RequestCamera(Actionbool callback) { using (var unityPlayer new AndroidJavaClass(com.unity3d.player.UnityPlayer)) using (var activity unityPlayer.GetStaticAndroidJavaObject(currentActivity)) using (var helper new AndroidJavaClass(com.example.unitybridge.PermissionHelper)) { if (activity null) { callback?.Invoke(false); return; } bool has helper.CallStaticbool(hasPermission, activity, android.permission.CAMERA); if (has) { callback?.Invoke(true); return; } helper.CallStatic(requestPermission, activity, android.permission.CAMERA); // 实际回调通过 PermissionCallback 注册此处省略注册细节 } }逻辑说明先判activity是否为空再查权限已有权限直接回调没有才发起申请。参数上权限字符串必须用安卓标准常量写错一个字母就永远申请不到。这套流程跑通后相机、定位、录音都能套同一个模板。4. 避坑与排查Unity 调用原生安卓最常见的五类翻车4.1 现象运行时报 ClassNotFoundException原因通常是 AAR 没打进包或者包名和 C# 里写的对不上。Unity 只识别Assets/Plugins/Android下的 AAR放别处不生效另外 Java 包名大小写敏感com.example.UnityBridge和com.example.unitybridge是两个类。解决确认 AAR 路径正确用AndroidJavaClass前先打印一次类名核对打包后用 Android Studio 的 APK 分析器看classes.dex里有没有这个类。4.2 现象Toast 不显示日志无报错多半是没在主线程调用。安卓的 UI 操作强制在主线程Unity 的 C# 回调线程不一定是主线程。解决Java 侧统一用runOnUiThread包一层或者 C# 侧用AndroidJavaObject拿到 Activity 后runOnUiThread。别指望它自己切线程它不会。4.3 现象权限弹窗弹了但 C# 收不到回调AndroidJavaProxy的方法签名和 Java 接口对不上是最常见原因。比如 Java 接口参数是int[]C# 写成int[]没问题但写成AndroidJavaObject就静默失败。解决对照 Java 接口源码逐字核对方法名和参数类型requestCode也要对上回调里加日志确认是否进入。4.4 现象打包报 duplicate class 冲突AAR 里带的依赖和 Unity 内置的androidx版本不一致或者多个 AAR 引了同一个库。解决在mainTemplate.gradle里用exclude group排掉重复依赖或者统一用implementation而非api减少传递依赖。工具集里建议只保留一份androidx.core。4.5 现象真机上偶发崩溃模拟器正常常见于 JNI 引用泄漏或 Activity 生命周期错配。AndroidJavaObject用完不Dispose长时间运行引用表爆掉。解决所有AndroidJavaClass、AndroidJavaObject都用using包裹回调里判空别在OnDestroy之后还去调原生方法。5. 进阶把零散调用收敛成可复用工具集5.1 用泛型封装统一的调用入口调多了会发现每个功能都写一遍AndroidJavaClass很啰嗦。我一般会封一个泛型入口把类名和方法名参数化public static T CallStaticT(string className, string method, params object[] args) { using (var clazz new AndroidJavaClass(className)) { return clazz.CallStaticT(method, args); } } // 用法 string model CallStaticstring(com.example.unitybridge.UnityBridge, getDeviceModel);逻辑说明params object[]让参数数量可变T指定返回类型。注意泛型不能是void无返回值的方法单独封一个CallStaticVoid。这样新增功能时只改类名和方法名不用重复写 JNI 样板。5.2 验证工具集是否真的生效别只在编辑器里跑编辑器根本不走 JNI。验证必须打真机包按这个清单过一遍验证项方法预期类能否找到调getDeviceModel返回真实机型字符串权限流程首次安装申请相机弹窗出现允许后回调 true拒绝分支拒绝后再次申请回调 false不崩溃线程安全连续弹 10 次 Toast全部显示无错乱内存循环调用 1000 次无持续增长无崩溃5.3 一个我踩过的坑和后来的习惯早期我图省事把AndroidJavaObject当普通 C# 对象缓存起来复用结果跑久了必崩查了半天才发现是 JNI 引用没释放。从那以后我每次写调用都强制走using回调里先判空再干活打包前必用真机跑一遍权限拒绝分支。这套工具集我建议你拿到后先别急着加功能把示例里的设备信息、Toast、相机权限三个跑通再照模板扩自己的 API。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

OpenSpec:用OpenAPI规范驱动全链路开发的工程实践

OpenSpec:用OpenAPI规范驱动全链路开发的工程实践

1. OpenSpec 是什么?它解决的不是“要不要写文档”,而是“文档怎么活起来”OpenSpec 这个名字乍看像某个开源协议或规范草案,但实际它是一套以 API 规范为唯一信源、驱动全链路开发协作的技术实践体系。核心关键词非常明确:OpenSp…

2026/10/18 18:19:56 阅读更多 →
周红伟:OpenClaw 升级到 2026.3.28 后,微信 ClawBot 插件更新指南与 TaoToken 通道配置

周红伟:OpenClaw 升级到 2026.3.28 后,微信 ClawBot 插件更新指南与 TaoToken 通道配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 18:19:56 阅读更多 →
AI Agent 上线前最容易忽略的安全问题:提示词注入、工具越权与敏感数据泄露实战解析

AI Agent 上线前最容易忽略的安全问题:提示词注入、工具越权与敏感数据泄露实战解析

一、引言:Agent 把"模型风险"变成了"系统风险" 过去两年,绝大多数团队对 LLM 的安全认知还停留在"内容安全"层面——输出有没有违规、有没有幻觉、会不会骂人。这类问题最坏的结果是用户体验受损。 而 AI Agent 改变了风险…

2026/10/18 18:18:56 阅读更多 →

最新新闻

MCP Client 与 LLM 协作实战:工具调用与上下文管理全解析

MCP Client 与 LLM 协作实战:工具调用与上下文管理全解析

1. 从项目背景说起:MCP 到底解决了什么问题说到 MCP(Model Context Protocol,模型上下文协议),我先讲一个痛点场景。假设你手里有 5 个 AI 应用,想给每个应用接上 3 类外部能力:数据库查询、文件…

2026/10/18 20:42:12 阅读更多 →
Lambda表达式详解:语法糖、闭包捕获与主流语言对比

Lambda表达式详解:语法糖、闭包捕获与主流语言对比

我第一次对lambda表达式产生好感,是在一个排序逻辑改了第三次之后。那是个处理订单列表的功能,每次新增一种排序规则,就要新写一个匿名内部类,光模板代码就占了大半屏。后来把排序逻辑整个换成lambda,十几行变成一行&a…

2026/10/18 20:42:12 阅读更多 →
Claude Code终端AI编程助手:配置、权限与高效工作流实战

Claude Code终端AI编程助手:配置、权限与高效工作流实战

1. 为什么把 AI 编程搭在终端里:Claude Code 的定位与它解决的问题最近一个月我基本把日常编码的主流工作流搬进了一个命令行工具里,就是标题里提到的 Claude Code。先说结论:这是一个跑在终端里的 AI 编程助手,它跟网页端聊天窗口…

2026/10/18 20:42:12 阅读更多 →
影院订票系统毕设避坑指南:并发控制、动态建模与超时释放

影院订票系统毕设避坑指南:并发控制、动态建模与超时释放

简介:本资源是一套基于JavaSpringBootVueMySQL开发的影院订票系统高分毕业设计项目,面向计算机专业本科生、课程设计学习者及毕设开发者,解决传统影院人工售票效率低、用户购票体验差、后台管理分散等实际问题。压缩包共771个文件&#xff0c…

2026/10/18 20:42:12 阅读更多 →
MATLAB中用1D-CNN做时间序列预测的完整实践指南

MATLAB中用1D-CNN做时间序列预测的完整实践指南

简介:本资源是一份面向计算机、电子信息工程及数学等专业本科生的CNN时序预测实践工具包,聚焦深度学习在金融、气象、交通等场景中的时间序列建模需求,助力课程设计、期末大作业与毕业设计落地。压缩包共6个文件,含1个主程序main.…

2026/10/18 20:42:12 阅读更多 →
720VR全景源码系统实战:从部署到二次开发全解析

720VR全景源码系统实战:从部署到二次开发全解析

简介:这是一套面向开发者与建站团队的720度VR全景网站源码系统,用于快速搭建可旋转、缩放浏览的沉浸式全景展示平台,适合需要自建全景图制作与分享站点、又不想从零开发的中高级技术人员。压缩包共1946个文件,约104.53MB&#xff…

2026/10/18 20:41:11 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →