拓冰建站拓冰建站
首页 / 资讯中心 / 正文

TrafficExam源码解析:Gradle构建与Activity状态恢复

简介这是基于安卓平台的交通考试移动互联软件开发设计源码主要面向安卓开发学习者、移动应用开发者以及需要交通考试类应用参考方案的从业者。资源围绕移动应用开发的核心流程展开覆盖从项目结构搭建、界面布局配置、业务逻辑实现到构建运行的完整链路可作为安卓入门与进阶的实践案例。压缩包共四十一个文件大小一百六十五KB以XML配置文件、PNG图片资源、Java源代码和Gradle构建脚本为主另有Git忽略配置、属性文件、License协议及README说明。其中XML负责界面布局与参数定义PNG提供界面视觉素材Java代码实现考试逻辑、网络通信和数据管理Gradle脚本完成自动化构建整体文件组织清晰体现工程化开发的基本规范。目前已有八十五人学习下载适合希望快速上手安卓项目实战的开发者通过阅读源码可以掌握Activity生命周期管理、UI事件响应、数据存储方式同时了解项目分包规则、构建配置细节和版本控制协作方法为独立开发同类移动互联应用奠定扎实基础。1. TrafficExam 的 41 个文件里最该先读的不是 Java拿到这份 TrafficExam 源码时建议你先别急着点开 MainActivity。整个 upload.zip 解压后只有 41 个文件其中 4 个 Java 文件、14 个 XML 配置、10 个 PNG 图片剩下的全是为 Gradle 构建服务的脚本。对一个面向交通考试的移动互联应用来说这个体量刚好卡在“能讲清 Android 工程骨架”和“没有多余封装”的中间位置。它解决的核心问题是Activity 之间怎么传参、答题状态在屏幕旋转后怎么恢复、界面资源如何与逻辑代码解耦。适合用 Android Studio 练手的人也适合想把老项目结构拉出来看一遍的移动应用开发工程师。上一篇是理论骨架这一篇直接拆到指令和配置层。2. Gradle 构建骨架TrafficExam 的编译参数与版本约定2.1 从 settings.gradle 到 gradle.properties先看这三个文件这份源码包的目录结构和 Android Studio 新建工程时的默认结构几乎一致根目录的 settings.gradle 声明:app模块gradle.properties 配置 JVM 内存和 AndroidX 开关gradle-wrapper.properties 则锁死 Gradle 发行版。我习惯把这些文件排在 Java 源码前面看是因为它们最能反映项目的“出生年份”。如果 gradle.properties 里没有android.useAndroidXtrue而代码里又写了androidx.*的依赖编译时一定会撞上 support 库与 AndroidX 混用的错误。下面这张表是排查任何 Android 源码包时都会先对着核对的清单。TrafficExam 的 3 个 Gradle 脚本正好覆盖了其中三个关键角色。文件作用改动影响gradle-wrapper.properties决定 gradlew 去下载哪个 Gradle 版本版本号变了本地同步时间就会变settings.gradle声明项目包含哪些模块新增 library 模块时必须追加 includegradle.properties设置 JVM 堆大小、AndroidX 开关堆太小会触发 OOMAndroidX 开关影响依赖兼容app/build.gradle配置 compileSdk / minSdk / targetSdk 与依赖直接影响 API 调用活性和市场覆盖.gitignore也算构建约定的一部分。TrafficExam 自带的 .gitignore 会把.gradle/、build/、local.properties全部排除。local.properties一旦被提交别人 clone 后会读到你自己机器的 SDK 路径轻则同步失败重则覆盖团队其他人的环境变量。这个细节在源码包项目里出现说明原作者对版本控制是有意识的。2.2 app/build.gradlecompileSdk 与 minSdk 定了应用的边界App 模块的 build.gradle 是这种源码里含金量最高的单文件。以下是按常见 Android 工程结构补全的 build.gradle 示例参数与 TrafficExam 的预期一致apply plugin: com.android.application android { compileSdkVersion 30 defaultConfig { applicationId com.example.trafficexam minSdkVersion 21 targetSdkVersion 30 versionCode 1 versionName 1.0 } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile(proguard-android-optimize.txt), proguard-rules.pro } } } dependencies { implementation androidx.appcompat:appcompat:1.2.0 implementation androidx.constraintlayout:constraintlayout:2.0.4 }这里需要逐项拆解。compileSdkVersion 30表示当前工程基于 Android 11 SDK 编译它只影响编译期能用到哪些 API不影响运行版本。targetSdkVersion 30则是告诉系统这个应用按 Android 11 的行为约束运行分区存储、后台位置权限等变更都从这里生效。minSdkVersion 21把最低支持设备拉到 Android 5.0对“交通考试”这类公共服务向应用是非常实用的折中既能覆盖老机型又不至于为了 Android 4.x 做大量兼容代码。release里minifyEnabled false说明源码没有开启代码混淆。脱去混淆这一层配合proguard-rules.pro中的规则反而更适合当作学习样本。如果以后要商用应该改成true并在 proguard-rules.pro 里给反射调用和实体类加 keep 规则。dependencies里只放了 AndroidX 核心库和 ConstraintLayout没有引入网络库和图片库这从侧面印证了题库逻辑走的是本地数据源不依赖远程服务。2.3 用命令行完成一次干净构建不要依赖 Android Studio 的 Sync 按钮尤其在网络不稳定的环境下。直接用 Gradle Wrapper 能精确复现构建过程也能把问题隔离在构建脚本本身./gradlew clean assembleDebug adb install -r app/build/outputs/apk/debug/app-debug.apkclean会删掉 build 目录意味着所有增量编译缓存全部失效。第一次跑可能接近一分钟但后续增量构建会快很多。之所以用-r安装而不是直接-t是希望在保留本地数据库和登录态的情况下覆盖安装这对调试考试进度、答题记录类功能非常关键。如果 gradlew 在 Windows 上报错找不到 SDK 路径检查项目根目录是否有local.properties并在里面写清sdk.dir。这个文件本应在 .gitignore 里但很多下载源码在分享时会把别人的本地路径留在里面需要第一时间改掉。3. 答题状态驱动TrafficExam 中 Activity 生命周期与数据传递3.1 4 个 Java 文件的分工边界Java 代码只有 4 个文件说明 TrafficExam 在架构上没有强行拆包。按考试应用最常见的拆分方法应该是入口 MainActivity 负责列出考试类型或规则ExamActivity 承载答题主界面ResultActivity 展示成绩再留一个 QuestionBank 工具类提供本地题库数据。这种分层的好处是没有网络层、没有持久层所有逻辑都收缩在 Activity 和纯工具类里新手可以一口气读完所有业务代码。ExamActivity 的职责要从生命周期回调展开。按交通考试场景用户可能在答题中途被电话打断。如果来电结束后 Activity 被重建之前的答题进度就不应该丢失。这时需要onSaveInstanceState与onRestoreInstanceState配合。public class ExamActivity extends AppCompatActivity { private static final String KEY_INDEX current_index; private int currentIndex; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_exam); bindViews(); if (savedInstanceState ! null) { currentIndex savedInstanceState.getInt(KEY_INDEX, 0); } renderQuestion(currentIndex); } Override protected void onSaveInstanceState(Bundle outState) { outState.putInt(KEY_INDEX, currentIndex); super.onSaveInstanceState(outState); } private void renderQuestion(int index) { questionView.setText(questions.get(index).getTitle()); optionGroup.clearCheck(); } }注意这里不要把questions也放进 Bundle。题库列表通常来自静态方法或 SQLite每次重建 Activity 时重新加载即可。真正需要跨配置变更保留的只有currentIndex、选中答案的 ID、计时器剩余秒数这类轻量状态。即使不做数据库持久化也能让考试中途的来电恢复。3.2 Intent 传参与结果返回只传必要字段交卷后ExamActivity 要跳到 ResultActivity并在结果页显示本次的答对数量和总题数。常见且容易出错的写法是直接putExtra传一个ArrayListQuestion。需要明确ArrayList 放进 Intent 必须是值类型或 Serializable 实现类否则抛 ClassCastException。TrafficExam 这样的轻量项目更合理的做法是只传两个 int。Intent intent new Intent(this, ResultActivity.class); intent.putExtra(correct_count, correctCount); intent.putExtra(total_count, totalCount); startActivity(intent);ResultActivity 读取时为每个 extra 提供默认值避免测试阶段从未经跳转直接打开结果页导致空指针int correctCount getIntent().getIntExtra(correct_count, 0); int totalCount getIntent().getIntExtra(total_count, 0);如果你还要从 ResultActivity 返回上一页并自动跳回考试页继续改题就改成startActivityForResultonActivityResult或者在 Intent 上带一个FLAG_ACTIVITY_REORDER_TO_FRONT。这两个方案里后者的跳转栈更干净。3.3 生命周期与资源释放的对照生命周期回调TrafficExam 场景如果不处理onCreateinflate 布局、加载题库、绑定监听无onResume恢复倒计时、刷新当前题号时间不同步onSaveInstanceState保存答题进度屏幕旋转后回到第一题onDestroy释放 Handler、MediaPlayer、Dialog内存泄漏、ANR这里最容易忽略的是 Handler。如果考试页面有一个倒计时 CountDownTimer必须在onDestroy里 cancel。否则 Activity 被销毁后回调仍然执行会导致 findViewById 返回 null 后空指针。数据传递方面如果要从题库文件切换成网络题包建议先用接口封装 QuestionSource屏蔽 MainActivity 对数据来源的感知。4 个 Java 文件的体量决定了我们不会现在引入 Dagger 或 Hilt但保留一个静态工厂方法已经足够后续替换。4. XML 布局与 PNG 资源TrafficExam 的界面组织与密度适配4.1 layout 与 value 分离为什么要有 activity_exam.xml 和 strings.xmlTrafficExam 的 14 个 XML 文件一半以上落在 res 目录。按 Android 资源系统的规则layout 目录下的 XML 负责描述控件的摆放和尺寸values 目录下的 XML 负责描述颜色、文案、主题。有的初学项目会把按钮文字直接硬编码在布局文件里但这样后面做多语言或换肤就得全文搜索。TrafficExam 既然保留了 strings.xml说明它遵循了资源分离的基本原则。TextView android:idid/tv_question android:layout_widthmatch_parent android:layout_heightwrap_content android:padding16dp android:textSize18sp android:textColorcolor/question_text android:textstring/default_question / RadioGroup android:idid/rg_options android:layout_widthmatch_parent android:layout_heightwrap_content / Button android:idid/btn_next android:layout_widthmatch_parent android:layout_heightwrap_content android:textstring/btn_next android:onClickonNextClick /这里的string/btn_next最终会指向 values/strings.xml 中的一个键default_question则是用于 Activity 还没把数据 set 上来时的占位文案。占位文案不能省因为在 XML 预览阶段和 unit test 环境下Activity 的 findViewById 不会执行布局文件需要一个确定的初始状态。textSize用 sppadding 用 dp这也是资源适配的基本盘dp 跟随屏幕密度缩放sp 还会额外跟随用户系统字体缩放。4.2 mipmap 与 drawable 的选择10 个 PNG 该怎么放png 图片资源是源码包的另一大块。启动图标应该放在 mipmap-* 目录普通界面插图和按钮背景放在 drawable-* 目录。这个区分很多开发者容易搞混后果是图标在某些桌面上被拉伸或变得模糊。目录用途示例mipmap-mdpi低密度启动图标ic_launcher.pngmipmap-hdpi中密度图标ic_launcher.pngmipmap-xhdpi高密度图标ic_launcher.pngdrawable按钮背景、Selector、Shapebg_btn_normal.xml如果 PNG 资源没有按密度拆分系统会采用最接近的资源做采样。比如一张 48x48 的图标只存在于 drawable-mdpi 时放到 xhdpi 屏幕上会被放大到 96x96 的逻辑尺寸边缘锯齿会非常明显。TrafficExam 一共 10 张 PNG说明它都是用同一套基础尺寸生成的这在原型阶段够用上线前最好导出 1.5x、2x、3x 三套。4.3 用 selector 和 shape 代替按钮切图很多 10 张 PNG 的源码里最耗资源的其实是按钮按下态背景。如果只是一个矩形圆角按钮完全可以用 XML 描述不必额外放两张位图selector xmlns:androidhttp://schemas.android.com/apk/res/android item android:state_pressedtrue shape android:shaperectangle solid android:color#FF6200EE / corners android:radius8dp / /shape /item item shape android:shaperectangle solid android:color#FF3700B3 / corners android:radius8dp / /shape /item /selectorselector 的匹配顺序是按 item 从上往下查找第一个满足条件的即生效。state_pressed必须放在默认 item 前面。如果两个 item 互换位置所有常态按钮都会错误地使用按压色因为默认 item 会优先拦截匹配。从资源调优的角度Shape 文件在不同屏幕上不会出现锯齿生成 APK 的体积也远小于同等效果的 PNG。对 TrafficExam 这种题库型应用把 10 张 PNG 中的按钮背景替换成 selectorAPK 至少可以瘦身几十 KB。5. 实战排错与进阶拆解TrafficExam 上线前必须验证的三件事5.1 用 adb logcat 定位启动崩溃拿到源码改完第一轮先不要用 Android Studio 直接 Run。用命令行构建安装再用 logcat 过滤错误可以更快定位到底是资源缺失还是生命周期问题。./gradlew assembleDebug adb install -r app/build/outputs/apk/debug/app-debug.apk adb logcat -s AndroidRuntime:E adb shell am start -n com.example.trafficexam/.MainActivity包名com.example.trafficexam是 defaultConfig 中 applicationId 的值。如果这里写错logcat 会报出 ActivityNotFound 而不是具体的 Java 异常。确认了包名后Java 崩溃栈会直接出现在 logcat 输出里其中Caused by那一段通常就是问题根源。5.2 常见构建与资源异常对照错误信息排查方向处理Could not find com.android.tools.build:gradle:X插件版本不在仓库在根 build.gradle 的 repositories 增加 google()resource style/AppTheme not foundvalues 资源缺失检查 app/src/main/res/values 下是否还有 styles.xmlINSTALL_FAILED_UPDATE_INCOMPATIBLE本地已装不同签名包adb uninstall com.example.trafficexam 后再安装View android.widget.Button has setOnClickParameter but no such method布局 onClick 名与 Activity 方法不一致把 android:onClickonNextClick 改为实际方法名这四条在 Android 开发流程里出现频率最高。特别是最后一条很隐蔽布局文件里写了android:onClick后Java 类里如果没有对应的public void方法且入参为 View编译不会报错运行到点击那一步才崩溃。5.3 把题库从数组迁到 assets JSON 的低成本改造TrafficExam 目前最值得做的演进是把题库从 QuestionBank 内部数组搬到 assets/questions.json。不用引入数据库只需在原有 load() 方法里改一行资源来源InputStream is getAssets().open(questions.json); String json new Scanner(is, UTF-8).useDelimiter(\\A).next(); JSONArray array new JSONArray(json);这段代码把 JSON 文件内容读成 String再用 org.json 包解析成数组。相比 ArrayList.add 硬编码这种方式让产品人员可以独立更新题库不需要每次动 Java 源码。迁移完成后记得把 assets 目录加入版本管理并在 README 中说明题库格式否则后续维护者很容易在资源文件中迷路。改造完之后跑一次./gradlew assembleDebug adb install -r app-debug.apk再用adb shell dumpsys package com.example.trafficexam | grep versionName确认安装版本已经更新再开始下一轮迭代。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门