简介:StoneAgeMobileApp是一份面向安卓与iOS双平台的移动端游戏源码,适合移动开发者和开源爱好者研究跨平台应用结构。既能帮助入门者理解App整体架构,也能为进阶开发者提供模块级实现细节。项目围绕‘石器时代’主题,覆盖Java/Kotlin与Swift/Objective-C两种原生技术栈,能清晰看到Activity/Service等安卓组件、UIKit/SwiftUI界面搭建、网络请求与数据存储等典型模块;同时整合SDL、FLAC等多媒体底层库,演示了应用层与C/C++库混编的系统级开源组织方式。资源包共10335个文件,以C/C++/Objective-C源码、头文件、工程配置(vcproj/vcxproj/xcodeproj)及HTML/TXT/MD说明文档为主,总体积37.05MB;目录同时包含安卓、iOS工程与第三方库源码,便于按模块对照排查逻辑。已有1065人学习,通过阅读可快速理清双平台架构,掌握原生与底层库协同的写法,也能为后续跨平台开发或功能移植提供可复用参考。
1. 先看 StoneAgeMobileApp 是什么:一套能跑的端游移植源码,而不是个包
做手游源码逆向或者接手老项目的人,看到 StoneAgeMobileApp 这个名字时,第一反应可能和我一样:这是不是个编好的石器时代单机包?实际上它是一份双端源代码工程,Android 和 iOS 两个壳工程共享同一套 C++ 核心逻辑,地图、战斗、宠物系统、资源加载全被编译进原生代码里。你拿到它,能做的事情不是直接开玩,而是在本机把两个端都编起来,改 UI、换资源、接自己的游戏服务器,最后产出一个能安装的 APK 和 IPA。下面按我自己的动手顺序,把从源码到双端可运行包的完整链路讲清楚,适合想快速验证这套源码、或者打算二次开发的团队。提前说一句:老代码的坑不在功能逻辑,而在工具链和包体配置,后面你会反复遇到。
2. 做编译前的目录体检:先分清壳工程、核心代码与资源
2.1 根目录结构:Android、iOS 两个壳工程包着同一份 C++ 核心
老牌端游移植到手机端,最常见做法不是把游戏逻辑用 Java 或 Swift 重写一遍,而是保留 C++ 核心,只把平台相关的入口、生命周期、输入输出放到各自的壳工程里。StoneAgeMobileApp 这类源码如果结构正常,顶层目录通常长这样:
StoneAgeMobileApp/ ├── android/ # Android 壳工程,Gradle 构建 ├── ios/ # iOS 壳工程,Xcode project + workspace ├── src/ # C++ 核心:战斗、地图、宠物、网络协议 ├── Resources/ # 美术、音频、地图配置 ├── Tools/ # 打包脚本、资源处理脚本 └── README.md拿到压缩包后先别急着开 IDE,用命令行把目录摸一遍:
find . -maxdepth 2 -type d | sort ls -la android ios src Resources file src/*.cpp | head -20第一行命令看整体,第二行确认双端工程是否存在,第三行用file检查源码格式。这里有个容易误判的点:有些打包者会把资源单独抽成加密包,Resources目录下只有.pak或.dat,看不到散落的.png。看到这种结构别慌,继续找加载入口,通常 C++ 代码里会有专门的解包类,负责把资源包映射到内存。
判断标准很简单:android和ios目录都在,且src下有大量.cpp/.h,说明是完整源码;如果只有assets和一堆二进制配置,那只能算资源包,不是标题里说的源代码。
2.2 引擎与工具链版本:决定你能不能编过的是这条线
双端壳工程意味着你同时需要 Android 和 Apple 两套工具链。老项目最头疼的不是代码,是当时用的 SDK 和 NDK 版本。Android 端如果工程写在 2018 年前后,Gradle 插件版本会很低,直接拿最新 Android Studio 打开大概率会报Minimum supported Gradle version之类的错。这时候强行升级 Gradle Plugin 不一定正确,C++ 代码往往依赖旧版 NDK,比如 armeabi 架构在 NDK r23 之后被移除,老库直接编不过。
我的建议是先看工程里声明的版本:
cat android/build.gradle | grep -E "classpath|gradle" cat android/gradle/wrapper/gradle-wrapper.propertiesgradle-wrapper.properties里的distributionUrl是这套工程最保守的构建版本。常见做法的第一步不是升级,而是按工程声明去安装对应版本的 Gradle 和 NDK。iOS 端同样,ios/Podfile里如果写了平台版本platform :ios, '8.0',你拿 Xcode 15 打开会有一堆 deprecation 警告,但一般还是能编;真正卡人的是 C++ 标准库和 bitcode 开关,这个后面在避坑章展开。
2.3 最小环境配置:JDK、NDK、Xcode 命令行一把查
不要凭感觉装环境,用命令确认缺失项。Android 端需要 JDK、Android SDK、NDK,iOS 端需要 Xcode 和 Command Line Tools。我一般在终端里依次跑:
java -version xcodebuild -version sdkmanager --list | grep -E "ndk|cmake" adb devices参数说明:sdkmanager --list | grep ndk能看到当前 SDK 已安装的 NDK 版本,如果你需要 r17 而机器上只有 r23,系统会绕过工程声明用新 NDK 去编译,最后报一堆unknown type name 'uint32_t'之类的奇怪错误。所以这条命令不是看一眼,是要对照工程里的ndkVersion或者.cxx配置。adb devices确认测试机是否连上,老项目很多逻辑只在真机上暴露问题,模拟器反而跑得顺。
如果缺 NDK,用 Android Studio 的 SDK Manager 勾选历史版本,或者用sdkmanager直接装:
sdkmanager "ndk;21.4.7075529" "cmake;3.10.2.4956307"版本号这里不重要,关键是版本要和build.gradle里的ndkVersion对齐。iOS 端如果xcodebuild -version报Xcode未安装,说明你还需要先装 Command Line Tools;真机调试还要确认手机上打开开发者模式,这个放到 iOS 章节细说。
3. Android 端从 Gradle 调整到出 APK:完整编译链路
3.1 先改 gradle:ABI 过滤、STL 选择、CMake 参数
Android 壳工程的核心工作是把 C++ 核心编译成 so 库再包进 APK。最常见的编译失败都发生在externalNativeBuild配置上。我先给出一段能直接落到工程里的app/build.gradle片段:
android { compileSdkVersion 28 defaultConfig { applicationId "com.stoneage.mobile" minSdkVersion 19 targetSdkVersion 28 ndk { abiFilters "armeabi-v7a", "arm64-v8a" } externalNativeBuild { cmake { cppFlags "-std=c++11 -fexceptions -frtti" arguments "-DANDROID_STL=c++_shared" } } } externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" } } }几个参数我解释一下。abiFilters决定打入最终包里的 CPU 架构,armeabi-v7a 覆盖绝大多数老 Android 手机,arm64-v8a 是后续主流;如果你要用模拟器跑 x86 镜像,还得临时把x86加回去,否则会报failed to load native library。ANDROID_STL=c++_shared是关键中的关键,老工程经常写成c++_static,编完装到手机上,一旦多个 so 库同时引用 STL,启动或切场景时直接崩溃;c++_shared把 STL 做成共享库,虽然包体会大几十 KB,但稳定性好得多。cppFlags里的-fexceptions和-frtti要确认代码里是否用了try/catch和dynamic_cast,老代码很多地方依赖这些特性,不开就会编不过。
改完 gradle 后先执行一次构建,看后面的错误,不要急着继续配资源:
cd android ./gradlew assembleDebug --stacktrace第一次构建会下依赖,需要几分钟。看到BUILD SUCCESSFUL再往后走;如果这里就失败,八成是 NDK 版本和 CMakeLists 里的要求冲突,先解决这个问题,再继续改资源。
3.2 资源合并:assets 目录、启动进度条和包体驱动
Android 端资源加载路径是个隐性坑。C++ 引擎读取资源时,无外乎从assets读,或从可写目录/storage/emulated/0/android/data/com.tencent.tmgp.sgame/...这类沙盒路径读(现在 Google 限制了android/data的访问,老代码最容易翻车)。看工程里的资源管理器类,多数是FileUtils::setSearchPath配置搜索路径。常见的做法是让引擎先查assets,再查外部存储。
把Resources下的内容压进assets目录时,注意不要直接把整个Resources文件夹塞进去,应该按引擎约定压缩成.zip或.pak,因为 Android 资源包要避免几千个小散文件带来的 IO 损耗。命令行做法是:
cd android/app/src/main/assets cp -r ../../../../Resources/* . find . -type f | wc -l如果你看到文件数超过 5000,建议用工具打包成单个资源文件,否则安装包会巨大且构建超慢。另外启动进度条是个值得关注的点,老游戏在启动时依赖引擎preload一段资源列表,如果列表里的路径比实际资源多一个层级,进度条会卡在 80% 左右不动,看起来像死机,实际是缺资源。遇到进度条卡住,优先对照列表路径和 assets 里真实路径。
3.3 命令行出包与签名:assembleRelease 到 apksigner
开发阶段直接用assembleDebug就行,但要给别人装、或者模拟上架,一定走 release 流程。release 流程由两步组成:编译打包 + 签名。老项目常犯的错是只跑assembleRelease然后直接安装,结果系统提示“应用未安装”,因为 release 包默认是未签名的。
cd android ./gradlew assembleRelease --info find app/build/outputs/apk -name "*release*.apk"--info会打印每次编译的完整记录,适合定位资源压缩卡死、NDK 交叉编译警告这类黑匣子问题。生成的未签名包在app/build/outputs/apk/下,名字类似app-release-unsigned.apk。接着用 Android SDK 自带的apksigner签名:
apksigner sign --ks my-release.keystore --ks-key-alias stoneage --ks-pass pass:changeit app-release-unsigned.apk zipalign -v 4 app-release-unsigned.apk stoneage-release.apk注意参数顺序:zipalign要在签名之后跑,建议直接拿apksigner verify --verbose stoneage-release.apk检查签名状态。很多环境装了旧版jarsigner,对 APK 签名后也能装,但在 Android 7.0 以上设备会因为签名方案过旧被拒绝,所以统一用apksigner。如果你的工程里已经配了signingConfigs,这条命令行可以省略,但我建议手工走一次,能看清整个包产物链路,后面排查问题时你会感谢这个流程。
4. iOS 端从 Xcode 工程到真机运行:证书、签名与模拟器的取舍
4.1 打开工程前的预处理:pod install 和脚本修复
iOS 壳工程一般以.xcodeproj或.xcworkspace形式存在。看到.xcworkspace说明用了 CocoaPods 管理第三方库,必须先在ios目录下执行:
cd ios pod install --repo-updatepod install会依据Podfile拉取依赖库,例如微信登录 SDK、崩溃统计库、加密库等,并生成xcworkspace。这部分最容易出问题的是 Pod 仓库里的库版本和当前 Xcode 不兼容,比如use_frameworks!导致导出成动态库后签名报错。我的做法是,如果pod install失败,先看报错是来自哪个 pod,再回Podfile里锁定旧版本号。
打开工程前,还要检查有没有生成阶段脚本(Build Phases -> Run Script)。老工程喜欢在编译前调用脚本拷贝资源或处理图标:
ls ios/*.sh cat ios/CMakeLists.txt 2>/dev/null一些源码包会通过 CMake 先生成部分.a静态库再交给 Xcode 编译,这类工程不能直接打开.xcodeproj,得先把 CMake 步骤跑完,否则编译期会提示找不到头文件。这一步的坑在于:图片资源、info.plist路径、签名配置分散在多个目录,脚本一旦失败,错误信息又藏在 Xcode 日志的中间段,新手特别容易被误导。
4.2 签名与 bundle id:真机运行绕不开的两个门槛
iOS 与 Android 最大的区别是签名闭环。模拟器不需要签名,但真机安装 IPA 必须有一对证书描述文件。如果你的 Apple 开发者账号还没配好,最容易卡的地方是Signing & Capabilities里显示 “Signing for ‘StoneAgeMobileApp’ requires a development team”,此时需要做三件事:
- 在 Xcode 中选择自己的 Team;
- 修改 Bundle Identifier 为唯一值,比如
com.yourcompany.stoneage-mobile,老工程默认的com.stoneage.mobile可能被占用; - 在真机上打开“开发者模式”(iOS 16 及以上设置里的 Developer Mode 条目),否则 Xcode 会提示没有权限安装。
真机调试前,我习惯先用xcodebuild做一次无界面构建,避免反复点击 Xcode 按钮浪费时间:
xcodebuild -workspace StoneAgeMobileApp.xcworkspace \ -scheme StoneAgeMobileApp \ -configuration Debug \ -destination 'generic/platform=iOS' \ CODE_SIGNING_ALLOWED=NO参数的落点是:CODE_SIGNING_ALLOWED=NO先关闭签名,看纯编译是否通过;如果纯编译都过不了,签名问题还没资格出现。编译通过后,再用 Xcode 连接真机跑一次。真机首次连接时,手机屏幕会弹出“信任此电脑”的提示,没有点击信任就会一直卡在 “device locked” 状态,这是新手最容易误判为编译失败的一环。
4.3 模拟器与真机的差异:资源路径、沙盒与 GPU
模拟器能跑、真机闪退这类问题,在 iOS 端比 Android 更常见。先看两处:
第一处是资源读取路径。模拟器文件系统对大小写不敏感,真机敏感。老引擎在 Windows 或 mac 编译时生成的资源列表里,路径可能是Texture/Pet/001.png,而实际磁盘文件是Texture/pet/001.png,模拟器能读到,真机直接fopen失败,黑屏或模型丢失。
第二处是沙盒目录不同。模拟器的应用数据目录是 mac 本机路径,真机上则是类似.../Library/Caches的受限目录。老代码常会把日志写到完全没有权限的路径,导致启动时崩溃。排查时用真机连接 Xcode,直接看 Console 输出,比靠感觉插桩快得多。还可以用lldb在main.cpp启动入口打一个断点,逐行看资源管理器初始化是否成功。
我的习惯是:在 iOS 端优先用真机验证功能,用模拟器只做 UI 布局和连点测试。尤其是涉及 IAP、微信回调、推送这类系统能力的功能,模拟器基本等于没有,用真机走一遍才能确认代码里的回调真的触发了。
5. 避坑:编译失败、闪退和数据不通的 5 条排查记录
5.1 现象:链接时大量 undefined symbol;原因:NDK 的 STL 类型不一致
Android 端编译到一半,报错刷屏,__cxa_begin_catch、std::__1::basic_string全是未定义符号。原因是工程的多个静态库.a用了不同 STL:一个编译时选了c++_static,另一个选了c++_shared,链接阶段符号表对不上。解决方式是把所有模块统一的编译参数写进CMakeLists.txt,并且让 gradle 里的arguments不要重复覆盖:
# CMakeLists.txt 里强制 STL 一致 set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions -frtti")改完后重新./gradlew clean assembleDebug。这里要特别注意,clean 必须执行,旧缓存里的.a不会自动替换,容易遇到“明明改了还不生效”的假象。
5.2 现象:启动后闪退回桌面,没有任何报错;原因:C++ 异常没被系统捕获
Android 端安装后点图标,回到桌面,logcat 没看到 Java 异常。这类崩溃九成发生在 native 层。老代码里try/catch覆盖不到所有 C++ 异常,一个空指针就触发SIGSEGV,系统强制杀掉进程。先用adb抓 tombstone:
adb logcat -b crash -d adb pull /data/tombstones .//data/tombstones需要 root 才能拉,在一台开发机上可以尽量用adb shell看tombstone_*文件里第一行的>>>进程名和signal编号;如果没 root,更实际的办法是给 C++ 入口加全局信号捕获,比如signal(SIGSEGV, handler),再配合addr2line解析 crash 时打印的返回地址。启动闪退还不只是代码问题,资源加载失败同样会走到这条路径,所以排查顺序应该是资源路径 → 系统库加载 → C++ 异常。
5.3 现象:地图黑块、宠物模型缺失;原因:资源路径大小写和打包层级
Android 上地图读取正常,进了战斗却黑块;iOS 真机上头像显示一半。检查资源管理器搜索路径时,发现代码写的是Resource/Pet/,而实际资源在Resources/pet_001/。大多数引擎默认大小写敏感,Windows 打包时复制错层级,多套一层client/目录,读不到就黑块。解决是通过日志把每次加载失败的路径打印出来,对比磁盘路径。批量修正时,用 Python 脚本统一归一化:
import os root = "Resources" for dirpath, _, files in os.walk(root): for name in files: if name != name.lower(): new_name = name.lower() os.rename( os.path.join(dirpath, name), os.path.join(dirpath, new_name) )脚本只做文件名转小写,不改扩展名。这样至少让 Android、iOS 两端路径保持一致。老代码里对路径的拼法千奇百怪,有个预览版在 Windows 上不区分大小写,到手机全暴露,这就是典型的“开发期没被坑过,上线期被玩家骂死”的问题。
5.4 现象:能进游戏但连不上服务器;原因:服务器地址写死在配置里
App 装好后能启动,进登录界面就转圈,最后报“网络连接失败”。导出的 APK 里,如果服务器 IP 被写死在 C++ 配置宏里,翻代码找 IP 要费很大力气。常见情况是 IP 被拆成多段字符串拼起来,甚至做了 base64 混淆。先全局搜索端口号:
grep -r "8080\|9001\|serverip" android/src ios/src --include="*.cpp" -n找到后改成从外部配置读取,而不是重新编译。老项目调试服务端时,改一次 IP 编一次包太浪费时间。我的做法是加一个启动参数优先读取外部文件:先查getExternalFilesDir(Android 不受android/data目录限制)或 iOS 的Documents目录下的server.cfg,没有才用内置默认值。这样在测试机上改个文本文件就能切服务端环境,不用重新出包。
5.5 现象:iOS 模拟器能跑,真机闪退;原因:bitcode 或证书关联文件缺失
模拟器正常运行,真机装上去一开就闪退,连启动页都没稳住。排查顺序:先看 Xcode Console 的dyld报错,基本是找不到动态库或签名失效;再看 Build Settings 里的Enable Bitcode=YES,旧框架如果没开 bitcode,在真机上会被桥接层拒绝。解决方式是关闭 Bitcode,并检查项目里的.framework是否完整,特别是有加密库时,模拟器 x86_64 架构和真机 arm64 架构的 slice 不一样,重新pod install拉取真机 slice 通常能解决。
另一个隐藏点:iOS 真机的资源文件如果被放到.gitignore里,而模拟器从 mac 目录可见,也会导致差异。做 iOS 验证前先把Resources文件夹拖进工程,不要用 Create folder references 换掉真实目录,否则路径关系会在真机上错位。
6. 从能跑通到能上线:验证清单、切服技巧和日志习惯
APK 和 IPA 都能装上、能进游戏,接下来才是真正拉开差距的部分。我的验证分三块:启动、核心功能、稳定性。启动不只是看能不能打开,要看冷启动耗时和资源加载进度条是否在目标机器上流畅;核心功能按“登录→创建角色→进出地图→战斗→背包→宠物→保存/读取”排一条路径,每个关键步骤做一次手动埋点;稳定性则用连续切换场景 50 次、弱网 30 秒断线重连来压。
切服这个需求最容易在临上线时爆发。把服务器地址外置到配置文件的技巧见 5.4,这里补充一个细节:配置文件名不要叫server.cfg,容易被打包工具过滤或校验,可以放到assets下叫local_config.ini,引擎加载时先读它。内容保持极简:
[server] host=192.168.1.10 port=9001代码里注意用足够大的缓冲区,老代码的 socket 缓冲区经常只有 128 字节,塞一个 IPv6 地址就崩。
最后讲一个我自己的血泪经验:接手老项目时,永远不要先删日志系统。石器时代这种老牌端游的日志,看起来是最不重要的一层,实际上它记录了资源加载的每一次失败路径、服务器回包的原始字节流、甚至崩溃前的操作序列。我见过有人嫌弃日志文件太大,直接把log_info全注释掉,结果上线后玩家反馈的场景丢失完全没法复现,最后只能重新开日志、打热更、再等 48 小时回收日志,耽误了一个发布周期。保留日志、配上按天滚动、压缩上传,才是负责任的维护方式。我的习惯是每周跑一次双端构建并验证一条完整核心路径,很多藏在老代码里的问题都是在这种机械重复中提前暴露的,希望帮到你。
本文还有配套的精品资源,点击获取