尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Unity移动端Mod系统实战:JSON、AssetBundle与反射注入
1. 为什么要在Unity里自己造一个Mod轮子移动端游戏做Mod系统很多人第一反应是这玩意儿不是PC端才玩得转吗。我一开始也这么想直到有个项目需要在安卓和iOS上让玩家替换角色皮肤、加载自定义关卡数据才发现市面上现成的方案要么太重要么根本不支持移动端的文件权限模型。Unity本身没有任何内置的Mod框架AssetBundle虽然能打包资源但热更和Mod是两码事——热更是你官方推内容Mod是玩家自己造内容。这两者的权限边界、加载时机、安全校验完全不一样。我踩过的第一个坑就是直接把PC端的Mod加载逻辑搬到安卓上结果Application.dataPath在安卓里是个只读的压缩包路径File.ReadAllBytes直接抛异常。后来才搞明白移动端要先把Mod文件放到Application.persistentDataPath下面这个路径在安卓上是/storage/emulated/0/Android/data/包名/files在iOS上是应用沙盒的Documents目录只有这里才是可读写的。所以这套Mod系统的核心思路就三条用JSON做数据层的Mod描述用AssetBundle做资源层的Mod载体用反射做逻辑层的Mod注入。PC端和移动端共用同一套加载代码只在路径获取和权限申请上做平台分支。下面我把整个实现过程拆开讲包括我实际跑通之后觉得最值得分享的几个设计决策。2. Mod系统的三层架构拆解与平台路径处理2.1 数据层、资源层、逻辑层各管什么先把这个三层架构说清楚不然后面代码会乱。数据层负责描述这个Mod是什么。我用一个ModManifest类来承载字段包括modId、modName、version、author、description、entryAssembly逻辑层入口DLL名、assetBundleName资源层AB包名、dependencies依赖的其他Mod ID列表。这个Manifest序列化成JSON放在Mod文件夹根目录加载时第一件事就是读它。为什么用JSON不用XML或者二进制因为JSON在Unity里用JsonUtility就能解析不需要引入第三方库移动端包体敏感能省一点是一点。资源层就是AssetBundle。玩家把自定义的贴图、模型、音频打包成AB文件放在Mod文件夹里。这里有个关键决策我要求Mod作者用和主工程相同的Unity版本和构建目标来打AB否则跨版本加载会直接崩。实测下来Unity 2021 LTS打的安卓AB包在2022 LTS的播放器里加载有大概三成概率报The file can not be loaded because it was created with a newer version of Unity。所以我在Manifest里加了一个unityVersion字段加载前先比对不匹配就弹提示而不是硬加载。逻辑层是最麻烦的部分。移动端不允许Assembly.LoadFrom直接加载外部DLLIL2CPP模式下更是完全封死了动态加载。我的方案是逻辑层的Mod代码必须提前编译进主工程通过接口IModBehaviour暴露扩展点Mod作者实现这个接口然后在Manifest里写清楚要激活哪个实现类。加载时用反射Type.GetType找到类Activator.CreateInstance创建实例再调用接口方法。这样虽然牺牲了完全动态的能力但在移动端是唯一稳妥的路子。PC端Mono模式下可以放宽到真正加载DLL但为了代码统一我建议都走接口反射这条路。2.2 移动端和PC端的路径差异处理路径处理我封装了一个ModPathResolver静态类核心方法就一个GetModRootPath()。public static string GetModRootPath() { #if UNITY_ANDROID !UNITY_EDITOR return Path.Combine(Application.persistentDataPath, Mods); #elif UNITY_IOS !UNITY_EDITOR return Path.Combine(Application.persistentDataPath, Mods); #else return Path.Combine(Application.dataPath, ../Mods); #endif }PC端我放在工程目录上一级的Mods文件夹方便开发时直接拖文件进去。移动端统一用persistentDataPath。这里有个细节安卓11以上引入了分区存储persistentDataPath对应的目录虽然应用自己可读写但如果你想让玩家用文件管理器手动放Mod进去需要额外申请MANAGE_EXTERNAL_STORAGE权限这个权限在Google Play上架时会被重点审查。我的做法是在游戏内做一个导入Mod按钮调用系统文件选择器让玩家选zip包然后解压到persistentDataPath/Mods下面这样完全不需要额外权限。iOS更麻烦persistentDataPath在沙盒里玩家根本访问不到。所以iOS端必须走应用内导入或者从iCloud Drive导入的路子。我在Info.plist里加了UIFileSharingEnabled和LSSupportsOpeningDocumentsInPlace两个键这样玩家可以通过iTunes文件共享或者文件App把Mod包拷进Documents目录。实测这个方案在iOS 15以上都工作正常。注意安卓端解压zip时一定要做路径穿越校验防止Mod包里包含../这样的恶意路径把文件写到系统目录去。我用的校验逻辑是解压前把每个entry的fullName和目标根路径做Path.GetFullPath比对不在根路径下的直接跳过。2.3 Mod加载的完整生命周期一个Mod从被发现到生效要经过这几个阶段扫描阶段遍历Mods根目录下所有子文件夹找manifest.json文件。找不到就跳过找到就尝试解析。校验阶段检查modId是否重复、unityVersion是否匹配、依赖的Mod是否都已加载。任何一项不过就标记为Invalid记录原因但不中断其他Mod的加载。资源加载阶段用AssetBundle.LoadFromFile加载AB包把里面的资源注册到一个全局的ModAssetRegistry里key用modId/assetName的格式避免不同Mod之间的资源名冲突。逻辑注入阶段反射创建entryClass实例调用OnModLoaded()方法把ModContext传进去里面包含这个Mod能访问的所有资源和配置。激活阶段所有Mod加载完后统一调用OnModActivated()这时候Mod之间可以互相引用了。这个顺序很重要。我一开始把资源加载和逻辑注入混在一起做结果某个Mod的逻辑代码在OnModLoaded里就去访问另一个还没加载完的Mod的资源直接空引用。后来改成两阶段激活就稳了。3. Manifest设计与AssetBundle加载的实操细节3.1 Manifest字段的取舍与版本兼容ModManifest我最终定下来的字段是这样的字段名类型必填说明modIdstring是全局唯一标识建议用反向域名格式modNamestring是显示名称versionstring是语义化版本号如1.0.0authorstring否作者名descriptionstring否简介unityVersionstring是打包AB时的Unity版本entryClassstring否逻辑层实现类的全名assetBundleNamestring否AB文件名不含扩展名dependenciesstring[]否依赖的modId列表loadOrderint否加载优先级数字小的先加载loadOrder这个字段是后来加的。起因是有两个Mod都要修改同一个UI预制体加载顺序不同结果完全不同。加了loadOrder之后Mod作者可以自己声明优先级冲突时高优先级的覆盖低优先级的。默认值给0范围建议-100到100。版本兼容方面我在Manifest里只存了unityVersion没有存游戏版本。因为游戏版本更新频繁如果每次更新都要求Mod作者改Manifest体验太差。我的做法是在代码里维护一个ModApiVersion常量Manifest里加一个apiVersion字段只有API大版本变化时才需要Mod作者更新。小版本更新保持向后兼容。3.2 AssetBundle的加载与内存管理移动端加载AB包我强烈建议用AssetBundle.LoadFromFile而不是LoadFromFileAsync除非你的AB包特别大。原因很简单同步加载在移动端虽然会卡一帧但代码逻辑简单太多不用处理协程和加载中的状态。实测一个10MB左右的AB包中端安卓机同步加载大概80到120毫秒完全可以接受。超过50MB的包才需要考虑异步。加载完AB后资源不要立刻LoadAsset出来而是先注册。我写了一个ModAssetRegistrypublic class ModAssetRegistry { private Dictionarystring, AssetBundle _bundles new(); private Dictionarystring, Object _assetCache new(); public void RegisterBundle(string modId, AssetBundle bundle) { _bundles[modId] bundle; } public T LoadAssetT(string modId, string assetName) where T : Object { string key ${modId}/{assetName}; if (_assetCache.TryGetValue(key, out var cached)) return cached as T; if (!_bundles.TryGetValue(modId, out var bundle)) { Debug.LogError($Mod {modId} 的AssetBundle未注册); return null; } var asset bundle.LoadAssetT(assetName); if (asset ! null) _assetCache[key] asset; return asset; } public void UnloadMod(string modId, bool unloadAllLoadedObjects) { if (_bundles.TryGetValue(modId, out var bundle)) { bundle.Unload(unloadAllLoadedObjects); _bundles.Remove(modId); } // 清理缓存中属于该Mod的资源 var keysToRemove _assetCache.Keys.Where(k k.StartsWith(modId /)).ToList(); foreach (var key in keysToRemove) _assetCache.Remove(key); } }这里有个坑Unload(false)和Unload(true)的区别。false只卸载AB包本身已经Load出来的资源还在内存里true会把所有从这个AB加载的资源都销毁。如果你的Mod资源还被场景里的GameObject引用着用true会导致引用丢失变成粉色。我的策略是Mod卸载时用false然后在场景切换时统一调Resources.UnloadUnusedAssets()来清理。提示移动端内存紧张建议在Mod管理界面加一个卸载未使用Mod的按钮调用UnloadMod(modId, false)然后手动触发GC。实测能释放30%到50%的Mod相关内存。3.3 资源命名冲突的解决思路不同Mod作者很可能给资源起一样的名字比如都叫icon.png。如果直接按资源名加载后加载的会覆盖先加载的。我的解决方案是强制命名空间隔离每个Mod的资源在注册时都加上modId/前缀加载时也必须带上前缀。这样即使两个Mod都有icon.pngmodA/icon.png和modB/icon.png互不干扰。但这样有个副作用Mod作者在Unity里打AB时资源名是固定的没法自动加前缀。所以我在加载AB后遍历bundle.GetAllAssetNames()把每个资源用modId/原始名的格式重新注册一遍。这样Mod作者不需要改任何打包设置加载端自动做隔离。public void RegisterBundleWithNamespace(string modId, AssetBundle bundle) { _bundles[modId] bundle; var assetNames bundle.GetAllAssetNames(); foreach (var name in assetNames) { // name格式类似 assets/mods/icon.png string shortName Path.GetFileNameWithoutExtension(name); string key ${modId}/{shortName}; // 这里只注册映射关系实际加载还是走bundle.LoadAsset _assetNameMap[key] name; } }4. 逻辑层注入反射、接口与IL2CPP的边界4.1 为什么移动端不能直接加载DLL这个问题我被问过很多次这里一次性说清楚。Unity在移动端有两种脚本后端Mono和IL2CPP。Mono模式下理论上可以用Assembly.Load加载外部DLL但安卓和iOS的App Store政策都不允许动态执行未审核的代码所以实际项目里移动端基本都切到IL2CPP。IL2CPP会把所有C#代码提前转译成C再编译成原生代码运行时根本没有C#的元数据Assembly.LoadFrom直接抛NotSupportedException。所以移动端的Mod逻辑层只有一条路Mod代码提前编译进主工程通过接口暴露扩展点。Mod作者拿到你的SDK实现IModBehaviour接口编译成DLL后你把它合并进主工程重新出包。这听起来很麻烦但对于移动端来说这是唯一合规且稳定的方案。PC端可以放宽用Mono的Assembly.LoadFrom真正动态加载但为了代码统一我建议PC端也走接口反射只是加载方式不同。4.2 IModBehaviour接口的设计接口我设计得很克制只有四个方法public interface IModBehaviour { void OnModLoaded(ModContext context); void OnModActivated(); void OnModUpdate(float deltaTime); void OnModUnloaded(); }OnModLoaded在资源加载完后调用这时候可以访问context.Assets拿资源但不要访问其他Mod。OnModActivated在所有Mod都加载完后调用这时候可以互相引用了。OnModUpdate每帧调用但只有标记了needsUpdate的Mod才会被调用避免不必要的性能开销。OnModUnloaded在卸载时调用用来释放自己申请的非托管资源。ModContext里我放了这些public class ModContext { public string ModId; public ModManifest Manifest; public ModAssetRegistry Assets; public ModConfig Config; // 从config.json读的键值对 public Actionstring, object SendEvent; // 跨Mod通信 }SendEvent是后来加的起因是两个Mod需要联动一个Mod改了角色属性另一个Mod要根据属性变化改UI。没有事件机制的话它们只能互相硬引用耦合太深。加了SendEvent之后Mod A发player.health.changed事件Mod B监听这个事件完全解耦。4.3 反射创建实例的性能与缓存反射创建实例在移动端是有性能开销的。我实测过Activator.CreateInstance在安卓中端机上大概0.1到0.3毫秒一次。单个Mod只创建一次这个开销可以忽略。但如果你在Update里反复反射调用方法那就不可接受了。我的做法是创建实例时用反射创建完后把接口方法缓存成委托。OnModUpdate这种每帧调用的缓存成Actionfloat委托后调用开销和直接调用虚方法差不多。private IModBehaviour _instance; private Actionfloat _cachedUpdate; public void Initialize(Type behaviourType) { _instance (IModBehaviour)Activator.CreateInstance(behaviourType); _cachedUpdate (Actionfloat)_instance.GetType() .GetMethod(OnModUpdate) .CreateDelegate(typeof(Actionfloat), _instance); } public void Tick(float dt) { _cachedUpdate?.Invoke(dt); }注意CreateDelegate在IL2CPP下需要类型提前注册否则会报ExecutionEngineException。解决办法是在link.xml里保留所有Mod实现类的引用或者在代码里显式引用一下这些类型防止被裁剪。5. 移动端特有的权限、存储与性能坑5.1 安卓分区存储的适配安卓10开始引入分区存储安卓11强制启用。persistentDataPath对应的目录在分区存储下是应用专属目录应用自己读写没问题但其他应用包括文件管理器访问不到。这意味着玩家不能再用文件管理器把Mod包拷进Android/data/包名/files/Mods了。我的适配方案是双路径优先读persistentDataPath/Mods同时扫描GetExternalFilesDirs返回的所有外部存储路径下的Mods目录。这样如果玩家通过USB或者某些文件管理器把Mod放到了外部存储的应用专属目录也能被扫描到。但说实话最稳的还是应用内导入。public static Liststring GetAllModRootPaths() { var paths new Liststring(); paths.Add(Path.Combine(Application.persistentDataPath, Mods)); #if UNITY_ANDROID !UNITY_EDITOR var externalDirs GetExternalFilesDirs(); foreach (var dir in externalDirs) { if (!string.IsNullOrEmpty(dir)) paths.Add(Path.Combine(dir, Mods)); } #endif return paths; }5.2 iOS沙盒的文件共享配置iOS端要让玩家能放Mod进去必须在Info.plist里加两个键keyUIFileSharingEnabled/key true/ keyLSSupportsOpeningDocumentsInPlace/key true/加完之后玩家连上电脑在iTunes或者macOS的Finder里就能看到应用的Documents目录直接把Mod文件夹拖进去。或者在iOS的文件App里也能看到这个应用可以把iCloud Drive里的Mod包拷进来。但这里有个坑persistentDataPath在iOS上指向的是Documents目录而Documents目录会被iCloud自动备份。如果你的Mod包很大会占用用户的iCloud空间审核时可能被拒。解决办法是把Mod放到Library/Application Support目录下这个目录不会被备份。但Application.persistentDataPath默认指向Documents所以需要手动改路径#if UNITY_IOS !UNITY_EDITOR string modRoot Path.Combine(Application.persistentDataPath, ../Library/Application Support/Mods); #else string modRoot Path.Combine(Application.persistentDataPath, Mods); #endif5.3 移动端性能优化的几个实测数据我在这套系统上做过一轮性能测试设备是一台中端安卓机骁龙7系和一台iPhone 12。测试场景是加载10个Mod每个Mod包含一个2MB左右的AB包。操作安卓耗时iOS耗时扫描10个Mod目录15ms8ms解析10个Manifest5ms3ms加载10个AB包同步850ms420ms反射创建10个实例3ms1.5ms每帧Update调用10个Mod0.05ms0.02ms从数据看AB包加载是绝对的大头。所以我在加载界面做了一个进度条每加载完一个Mod就更新进度避免玩家以为卡死了。另外AB包加载我建议放在Loading场景里做不要在游戏主场景里同步加载否则会掉帧。还有一个优化点AssetBundle.LoadFromFile在安卓上如果AB包没有压缩加载速度会快很多。我建议Mod作者打包时用BuildAssetBundleOptions.UncompressedAssetBundle虽然包体会大一些但加载时不需要解压省CPU。实测未压缩的AB包加载速度比LZ4压缩的快40%左右。6. 从零跑通一个Mod的完整流程6.1 主工程侧的准备主工程需要做这几件事创建ModManager单例在Awake里初始化调用ScanMods()。定义IModBehaviour接口和ModContext类放在一个独立的程序集里方便Mod作者引用。在link.xml里保留Mod相关类型防止IL2CPP裁剪。提供一个ModSDK包包含接口定义和示例代码给Mod作者用。link.xml的内容大概是这样linker assembly fullnameModSDK type fullnameModSDK.IModBehaviour preserveall/ type fullnameModSDK.ModContext preserveall/ /assembly assembly fullnameAssembly-CSharp type fullnameMods.* preserveall/ /assembly /linker6.2 Mod作者侧的打包流程Mod作者拿到SDK后流程是这样的新建一个Unity工程版本和主工程一致。导入ModSDK包。写一个类实现IModBehaviour比如MyCharacterMod。把自定义资源放到Assets/Mods/目录下设置好AssetBundle名。用BuildPipeline.BuildAssetBundles打包目标平台选安卓或iOS。创建manifest.json填好modId、entryClass、assetBundleName等字段。把AB包和manifest.json一起打包成zip发给玩家。这里有个细节entryClass要写完整的命名空间加类名比如MyMod.MyCharacterMod。如果写错了反射找不到类型Mod会加载失败但不会崩溃只是在日志里报错。我建议在Mod管理界面把加载失败的Mod用红色标出来并显示失败原因方便玩家排查。6.3 玩家侧的使用流程玩家拿到Mod zip包后打开游戏进入Mod管理界面。点击导入Mod选择zip包。游戏解压zip到Mods/目录下自动扫描并显示新Mod。勾选启用重启游戏或者热重载。进入游戏Mod生效。热重载这块我做了但默认关闭。因为热重载需要卸载所有Mod资源再重新加载如果Mod逻辑里有静态变量或者事件订阅没清理干净很容易出问题。默认让玩家重启游戏虽然体验差一点但稳定。7. 几个我踩过的坑和对应的解法7.1 AB包跨平台不兼容PC上打的AB包放到安卓上加载会报The file can not be loaded because it was created with a different platform。这个坑我踩过两次第一次是忘了在BuildAssetBundles时指定BuildTarget.Android第二次是Mod作者用Windows编辑器打的包直接发过来了。解法就是在Manifest里加platform字段加载前比对Application.platform不匹配直接拒绝。7.2 反射找不到类型IL2CPP裁剪会把没有显式引用的类型裁掉。我一开始没加link.xml结果Mod实现类在编辑器里跑得好好的打包到安卓上就报Type.GetType返回null。加了link.xml之后解决。但link.xml需要手动维护每加一个Mod就要加一条。后来我改成在ModManager里用一个静态数组显式引用所有Mod类型这样IL2CPP就会保留它们private static readonly Type[] ModTypes new Type[] { typeof(MyCharacterMod), typeof(MyLevelMod), // 新增Mod在这里加一行 };7.3 资源引用丢失导致粉色材质Mod卸载时如果用了Unload(true)而场景里还有GameObject引用着这个Mod的材质材质就会变成粉色。解法是卸载前先遍历场景把所有引用了该Mod资源的GameObject的引用置空或者替换成默认资源。这个操作比较重所以我一般不建议运行时卸载Mod而是在切换场景时统一卸载。7.4 安卓11以上无法用文件管理器放Mod前面说过分区存储导致Android/data目录玩家访问不到。我的解法是应用内导入但有些玩家就是习惯用文件管理器。所以我在游戏里加了一个导出Mod目录路径的按钮点击后把persistentDataPath/Mods的完整路径复制到剪贴板玩家可以粘贴到文件管理器的地址栏。部分文件管理器比如某些第三方文件管理器支持直接跳转到这个路径。虽然麻烦但至少给了玩家一个选择。8. 后续可以扩展的方向这套系统目前只支持资源替换和有限的逻辑注入。如果想做得更开放可以考虑这几个方向脚本层用Lua或者ILRuntime。Lua在移动端有成熟的解释器可以真正实现动态逻辑。ILRuntime可以在IL2CPP下解释执行C# DLL性能比Lua好但包体会大一些。这两个方案我都评估过Lua更适合小游戏ILRuntime更适合中大型项目。Mod市场与自动更新。现在Mod是玩家手动导入的如果做一个Mod市场支持在线浏览、下载、更新体验会好很多。但这涉及到服务器成本和内容审核看项目需求决定。Mod冲突检测与自动合并。现在两个Mod改同一个资源后加载的覆盖先加载的。如果做一个冲突检测机制在加载时分析每个Mod修改了哪些资源冲突时提示玩家选择会更友好。性能监控面板。在游戏里加一个开发者面板显示每个Mod的内存占用、Update耗时、资源数量方便排查性能问题。这个我在内部测试版里做了上线版可以隐藏。这套Mod系统我从零开始搭前后迭代了三个版本踩的坑基本都写在上面了。核心思路就是移动端不要想着完全动态接口反射加资源隔离是最稳的路子。PC端可以放宽但为了代码统一建议走同一套逻辑。如果你也在做类似的东西希望这些经验能帮你少走点弯路。
RELATED

相关推荐

HTML程序员表白网页源码:纯前端交互式页面开发与部署指南

HTML程序员表白网页源码:纯前端交互式页面开发与部署指南

1. 一个前端人的浪漫:从标题拆解这个项目的真实需求“html程序员表白前端网页源码”这个标题,第一次看到的时候我笑了——又是一个被技术耽误的浪漫选手。但笑完之后仔细想想,这个标题其实精准地戳中了一个非常具体的需求场景:一个…

📅 2026/10/18 2:57:07
从零搭建Django全栈博客系统:数据建模到部署的完整实践

从零搭建Django全栈博客系统:数据建模到部署的完整实践

如果你问我,作为一个刚接触Web开发的同学,第一个拿得出手的Django全栈项目该做什么,我大概率会推荐博客系统。不是因为它简单,而是因为它的覆盖面太合适了——一个完整可用的博客,刚好多达覆盖了Django从数据建模、URL…

📅 2026/10/18 2:57:07
在Mac M1/M2上部署OpenClaw:arm64架构下智能体环境搭建实战

在Mac M1/M2上部署OpenClaw:arm64架构下智能体环境搭建实战

在Mac的M1/M2芯片上跑OpenClaw,我一开始以为就是把安装包拖进应用程序文件夹的事儿,结果真正操作起来差点没被各种编译链路的问题整疯。后来把整个Apple Silicon生态梳理清楚,才发现核心矛盾就一句话:大多数教程默认你在x86架构下…

📅 2026/10/18 2:57:07
MORE NEWS

更多资讯

📰

工程师的瓶颈,已经不是代码了:代码之外的四种能力决定职业天花板

1. 从某位同事的经历说起:他什么都会,但就是上不去我所在的团队里曾经有这样一位工程师,他的代码能力在同批人里算顶级的。无论是重构老系统,还是设计新模块的边界,他都能交出质量很高的方案。代码评审里,别…

📰

Gazebo十年演进:从ROS仿真工具到数字孪生平台

2015 年,很多机器人团队还没有“仿真先行”的概念。那一年提到 Gazebo,大家的第一反应通常是“ROS 里的那个仿真工具”。十年后再看,这句描述已经不够用了——Gazebo 从 ROS 生态里的一个辅助组件,逐步演变成覆盖传感器建模、物理…

📰

163MusicLyrics:免费音乐歌词批量下载完整指南,双平台支持5分钟跑通

163MusicLyrics:免费音乐歌词批量下载完整指南,双平台支持5分钟跑通 【免费下载链接】163MusicLyrics 云音乐歌词获取处理工具【网易云、QQ音乐】 项目地址: https://gitcode.com/GitHub_Trending/16/163MusicLyrics 播放器里歌曲没有歌词显示&am…

📰

豆包+扣子实战:从零搭建能落地干活的智能体全流程

我发现一个特别有意思的现象:很多人搭出来的智能体,看起来像模像样,用起来却跟“人工智障”差不多。你问它什么,它都能说上几句漂亮话,可一旦让它干点具体的事——比如查一下昨天某个订单走到哪一步了、把一份长文档按…

📰

tiny11builder 指南:三步做出轻量版 Windows 11 ISO

tiny11builder 指南:三步做出轻量版 Windows 11 ISO 【免费下载链接】tiny11builder WinUI app for building smaller, customized Windows 11 images. 项目地址: https://gitcode.com/GitHub_Trending/ti/tiny11builder tiny11builder 是一个 PowerShell 开…

📰

第 27 篇:推理引擎怎么证明“这个答案真是这台机器算的“(零门槛入门系列)

第 27 篇:推理引擎怎么证明"这个答案真是这台机器算的"(零门槛入门系列)上一篇:第 26 篇《从发布方到设备的信任链》 | 下一篇:第 28 篇《怎么测才算数》一句话导读:推理引擎把权重指…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬