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

Android Studio结课大作业:从环境配置到打包答辩的完整指南

简介一套完整的安卓开发结课大作业资料包由王英强老师编写主要面向初学安卓开发、需要完成课程设计的学生内容涵盖从项目创建、代码编写到打包上线的完整过程。压缩包内共六百二十五个文件整体大小约十九点三八兆字节源码中包含界面布局文件、程序代码以及构建脚本可学习页面生命周期、页面跳转、碎片管理、后台服务、数据存储、权限申请等核心知识点文件列表中还有编译生成的中间产物和可直接运行的安装包能够帮助理解应用从源码到安装包的构建、签名与压缩流程。附带的实训报告则系统记录了需求分析、功能设计、问题排查和优化思路便于借鉴完整的项目开发方法。目前有一万五千二百三十九人浏览学习适合作为结课大作业的参考范例也适合复习安卓开发的工程结构、调试技巧、构建配置与发布流程。1. 结课大作业不是写 App是走通一条交付链路Android Studio 结课大作业到最后关头才动手的人往往不是代码写不出来而是不知道“做到什么程度才算完”。课程评分里有一个隐性维度你的项目能不能在别人电脑上打开、能不能装到手机上跑、代码里有没有明显凑数的地方。这比界面好看更影响成绩。所以这篇内容的思路很直接把结课大作业当作一条完整的交付链路来走——环境准备、项目骨架、功能实现、调试打包、答辩演示每一段都给可复制的命令和参数。项目规模按 3 到 5 个界面、2 到 3 张数据表、1 个网络请求来规划正好覆盖大多数课程的知识点也适合一到两周内写完。2. Android Studio 项目搭建与 Gradle 配置2.1 三个必须提前确认的环境项环境问题的排查成本高于编码尤其是用新电脑打开旧项目时报错往往不是代码引起的。第一SDK 路径。Android Studio 默认把 SDK 放在用户目录Windows 常见路径是C:\Users\用户名\AppData\Local\Android\SdkmacOS 是~/Library/Android/sdk。如果之前有人改过路径新建项目时会直接弹出SDK location not found。处理方式File → Project Structure → SDK Location重新指向 SDK 根目录。注意不要指到platform-tools或build-tools子目录Gradle 需要的是 SDK 根路径下的 platform、build-tools、platform-tools 等并列目录。第二Gradle 仓库源。这是国内环境下第一个“卡住”的地方。Android Studio 默认从google()和mavenCentral()拉依赖但连接速度很不稳定。我一般会在创建项目后立刻修改根目录的settings.gradle.kts在仓库列表前面加镜像pluginManagement { repositories { maven { url uri(https://maven.aliyun.com/repository/google) } maven { url uri(https://maven.aliyun.com/repository/central) } maven { url uri(https://maven.aliyun.com/repository/gradle-plugin) } google() mavenCentral() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { maven { url uri(https://maven.aliyun.com/repository/central) } google() mavenCentral() } }镜像放在最前面Gradle 按顺序查找仓库时能优先命中。FAIL_ON_PROJECT_REPOS的作用是禁止在模块级build.gradle.kts里再单独声明仓库这只适合“所有依赖统一管”的场景如果你用的旧代码里模块级有repositories要把那几行删掉否则直接编译失败。第三模拟器配置。创建虚拟设备时选择带 Google APIs 的 x86_64 系统镜像硬件加速用默认的Windows Hypervisor Platform或AEHD取决于 Android Studio 版本。内存分配建议 2GB 起步低于 1.5GB 会在启动时反复黑屏。如果启动模拟器时报accel相关错误去 BIOS 确认 CPU 虚拟化没有关闭这是最常见的模拟器起不来的原因。2.2 用 Empty Views Activity 创建项目创建项目的入口是New Project → Empty Views Activity。为什么不建议用Empty ActivityCompose 模板课程作业的评分老师通常按 XML 布局知识来理解界面实现Compose 在答辩时容易陷入“这是什么东西”的追问。XML 模板更稳也有现成的布局思路可以参考。创建时三个参数要明确参数建议值原因Package namecom.example.你的项目名作为所有类的命名空间不要带大写字母Minimum SDKAPI 24 或 26覆盖市场 95% 以上设备也避开老版本兼容坑Build configuration languageKotlin DSL新版默认文档和搜索最多的配置语法创建完成后项目会自动生成目录。不要着急写代码先跑一次app模块确认空模板能在模拟器里启动。这一步通过后续添加代码时才能区分“是环境问题还是代码问题”。2.3 模块 build.gradle.kts 里这套依赖组合课程作业用到的依赖不外乎五类UI 兼容、数据库、网络、图片加载、注解处理。把下面这段作为起点注释对应了每个库的作用dependencies { // UI 基础 implementation(androidx.core:core-ktx:1.12.0) implementation(androidx.appcompat:appcompat:1.6.1) implementation(com.google.android.material:material:1.11.0) implementation(androidx.constraintlayout:constraintlayout:2.1.4) // RecyclerView 列表 implementation(androidx.recyclerview:recyclerview:1.3.2) // Room 数据库及编译期代码生成 implementation(androidx.room:room-runtime:2.6.1) implementation(androidx.room:room-ktx:2.6.1) kapt(androidx.room:room-compiler:2.6.1) // Retrofit 网络请求和 JSON 解析 implementation(com.squareup.retrofit2:retrofit:2.9.0) implementation(com.squareup.retrofit2:converter-gson:2.9.0) }房间数据库的room-compiler必须用kapt而不是implementation而且要在plugins块里声明插件plugins { id(com.android.application) id(org.jetbrains.kotlin.android) id(kotlin-kapt) }典型的版本坑是Room 2.6.x 搭配 Lifecycle 2.7.x 时编译可能报cannot find symbol之类的错误屏幕上的堆栈指向 Room 生成的代码。解决办法不是换 Room 版本而是查看build.gradle.kts里是否重复引入了不同版本的lifecycle相关库统一版本号后再试。依赖版本没必要追求“最新”。1.6.1、2.9.0这些版本符号常有人去升级但课程作业用稳定版本就够了AGP 版本和 dependencies 版本组合越不“激进”越少遇到互相不兼容的问题。2.4 分包结构宁可多分也不要全塞一个文件第一版代码建议这样分包com.example.coursework/ ├── MainActivity.kt ├── data/ │ ├── AppDatabase.kt │ ├── ItemDao.kt │ └── ItemRepository.kt ├── model/ │ └── Item.kt └── ui/ ├── ItemAdapter.kt └── detail/DetailActivity.ktmodel放数据实体data放数据库、DAO、网络接口ui放 Activity 和列表 Adapter。这种分法没有任何框架上的深度但答辩时你可以很清楚地讲出“哪层负责什么”。一个 Activity 里塞几百行代码的写法最大的问题是答辩提问环节——老师随便指一个方法问“它为什么放在这里”答不上来反而拉低印象分。分包后写代码的边界也明确Activity 只负责界面事件数据库操作全部丢给 RepositoryRepository 要访问 Room 的 DAO。这比在 Activity 里直接调用 DAO 更符合常规的代码结构评审老师愿意看到这一层。3. 核心功能实现布局、ViewBinding 与 Room 持久化3.1 XML 布局优先记住 ConstraintLayout界面层不要每页都用 LinearLayout 嵌套。课程作业最常见的列表页 详情页列表项布局用 ConstraintLayout 能少一层嵌套渲染开销也小。以下是列表 item 的常见写法androidx.constraintlayout.widget.ConstraintLayout xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto android:layout_widthmatch_parent android:layout_heightwrap_content android:padding12dp TextView android:idid/tvTitle android:layout_width0dp android:layout_heightwrap_content app:layout_constraintStart_toStartOfparent app:layout_constraintTop_toTopOfparent app:layout_constraintEnd_toStartOfid/ivArrow android:textSize16sp android:maxLines1 android:ellipsizeend / ImageView android:idid/ivArrow android:layout_width24dp android:layout_height24dp app:layout_constraintEnd_toEndOfparent app:layout_constraintTop_toTopOfparent app:layout_constraintBottom_toBottomOfparent / /androidx.constraintlayout.widget.ConstraintLayout这里0dp加左右约束是关键它让标题宽度由左右两侧位置决定而不是固定像素这样屏幕适配不会出问题。如果遇到过标题过长把箭头挤出屏幕的情况多半就是宽度写了固定值或没有约束末端。第二点item 高度用wrap_content而不是match_parent否则整个列表会有大量留白。3.2 ViewBinding 替代 findViewById 的模板代码课程作业代码里最不值得出现的重复就是findViewById加类型强转。在模块build.gradle.kts中启用后就能减少很多行android { buildFeatures { viewBinding true } }Activity 里这样写class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.btnSave.setOnClickListener { val title binding.etTitle.text.toString() if (title.isNotBlank()) { saveItem(title) } } } }ActivityMainBinding是根据布局文件名activity_main.xml自动生成的命名规则就是把下划线去掉、首字母大写再加Binding。两个容易失败的细节一是setContentView(binding.root)之后不要再写setContentView(R.layout.activity_main)否则binding里的控件全部无法找到 view界面空白还不报编译错二是如果需要“点击后跳到详情页”用startActivity(Intent(this, DetailActivity::class.java))Intent 参数要传具体 Activity 的 class 而不是字符串。3.3 Room 三件套Entity、Dao、Database如果项目里必须有数据保存功能Room 是比 SQLiteOpenHelper 合适得多的选择。三处代码基本固定。Entity 定义表结构Entity(tableName items) data class Item( PrimaryKey(autoGenerate true) val id: Long 0, val title: String, val detail: String, val createdAt: Long System.currentTimeMillis() )DAO 提供增删查方法Dao interface ItemDao { Insert suspend fun insert(item: Item) Update suspend fun update(item: Item) Delete suspend fun delete(item: Item) Query(SELECT * FROM items ORDER BY createdAt DESC) fun getAll(): ListItem }注意getAll()没有用挂起函数因为 Room 在非挂起返回的情况下会阻塞主线程这里我建议把它改成这样Query(SELECT * FROM items ORDER BY createdAt DESC) suspend fun getAll(): ListItem加suspend后调用方必须在协程或 ViewModel 里执行避免Cannot access database on the main thread的崩溃。上面这段 DAO 里Insert suspend fun insert是标准写法但初写代码时容易在 Activity 里Thread里调用 DAO结果数据写不进去——因为 Room 的 suspend 设计要求调用发生在协程作用域而不是裸的thread {}。Database 类负责创建实例Database(entities [Item::class], version 1) abstract class AppDatabase : RoomDatabase() { abstract fun itemDao(): ItemDao companion object { Volatile private var INSTANCE: AppDatabase? null fun get(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { val db Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, coursework.db ).build() INSTANCE db db } } } }把 context 换成applicationContext是防止泄漏 Activity 引用。version 1表示当前表结构版本如果以后要加字段记得改版本号并写 Migration否则升级安装时直接崩溃。这套写法在答辩时可以讲清楚的逻辑点有三个单例确保数据库实例全局唯一、DAO 接口与 SQL 解耦、suspend保证主线程安全。能说清这三点数据部分基本不会被问倒。在 Activity 里观察数据并更新列表的标准写法lifecycleScope.launch { val items db.itemDao().getAll() adapter.submitList(items) }lifecycleScope是 Lifecycle 库提供的协程作用域Activity destroy 时自动取消。如果你在 ViewModel 里使用要换成viewModelScope。这一行的区别是答辩常问的细节。3.4 网络请求只接 Retrofit 这一套如果结课选题需要展示远程数据比如课程表、新闻列表、天气推荐直接用 Retrofit。以下是带 JSON 解析的最小实现。在model包定义数据类data class ApiResult( SerializedName(code) val code: Int, SerializedName(message) val message: String?, SerializedName(data) val data: ListItem? )SerializedName用于把服务端 JSON 字段映射到 Kotlin 属性。之后定义接口interface ApiService { GET(api/items) suspend fun fetchItems(): ApiResult }然后在 Repository 中创建实例并调用object ApiClient { private val retrofit Retrofit.Builder() .baseUrl(https://example.com/) .addConverterFactory(GsonConverterFactory.create()) .build() val service: ApiService retrofit.create(ApiService::class.java) } class ItemRepository(private val dao: ItemDao) { suspend fun loadFromRemote(): ListItem { val result ApiClient.service.fetchItems() return result.data ?: emptyList() } }Retrofit 的suspend方法自动在线程池执行回调回主线程不需要自己写withContext(Dispatchers.IO)多写反而容易导致性能多一次切换。别忽略addConverterFactory(GsonConverterFactory.create())没有这个.create(ApiService::class.java)会直接抛 “Unable to create call adapter” 的运行时错误。4. 调试、构建打包与常见报错处理4.1 日志关键字比断点更常用真机或模拟器调试时Logcat 里过滤Tag是最快捷的。统一的TAG常量写起来很简单companion object { private const val TAG MainActivity }然后需要的地方打印Log.d(TAG, save button clicked, title$title) Log.e(TAG, network call failed, exception)答辩时老师问“你怎么知道程序跑到了这里”时能说出“打断点验证 日志打点确认”这已经比其他学生高一层。断点调试的使用要点是在 Android Studio 左侧点击代码行号然后用 Debug 模式运行程序会停在断点处可以查看当前的变量值。如果断点不生效检查是否在Run → Edit Configurations里选成了Run而不是Debug。4.2 真机联调比模拟器更贴近交付模拟器适合开发过程最终演示和安装包验证建议用真机。Android Studio 里用 USB 连接手机时有几点特别影响效率手机开启开发者选项USB 调试开关必须打开。部分品牌手机需要选择文件传输模式而不是“仅充电”。无线调试Android 11 以上可以adb pair配对后无线连接适合 USB 口不稳定时使用。无线调试的步骤手机开启无线调试Android StudioFile → Settings → Build, Execution, Deployment → Debugger → Wireless里选择配对输入手机显示的配对码完成后就可以拔掉 USB 线。实际部署时数据线和 USB 口接触不良容易导致 Logcat 断开无线调试能减少这类干扰。4.3 Gradle 构建失败的三类高发错误报错特征原因处理方式Could not find com.android.tools.build:gradle:x.x.x镜像源里没有对应版本或缓存过期确认settings.gradle.kts中 Aliyun 镜像仓库已加File → Sync Project with Gradle Files重新同步tag number over 30 is not supporteddex 方法数超过 65535或 multidex 配置缺失在模块build.gradle.kts里加multiDexEnabled true并把依赖库精简到必要项trouble writing output: Too many methods同上同上先减少不用的依赖不要再往里加 “备用” 库multiDexEnabled是在模块android {}块里android { compileSdk 34 defaultConfig { applicationId com.example.coursework minSdk 26 targetSdk 34 versionCode 1 versionName 1.0 multiDexEnabled true } }如果编译报的是 AGP 版本太新导致 Gradle 版本不兼容日志会明确写出最低支持的 Gradle 版本。这时File → Project Structure → Project里改 Gradle Distribution 的版本号位置在gradle/wrapper/gradle-wrapper.propertiesdistributionUrlhttps\://services.gradle.org/distributions/gradle-8.7-bin.zip不要手动去改 Gradle 的安装目录wrapper 指定的版本才是项目在别人电脑上能否打开的关键。课程作业交到老师电脑上时真正决定能不能跑的就是gradle-wrapper.properties里的这一行。4.4 生成可安装的 APK完成编码后用Build → Generate App Bundle or APK → APK生成安装包。Debug 包可以直接装但答辩演示建议用 Release 签名包因为部分模拟器或手机默认禁止安装 Debug 包。生成 Release 需要签名android { signingConfigs { create(release) { storeFile file(../release.keystore) storePassword your-password keyAlias release keyPassword your-password } } buildTypes { release { signingConfig signingConfigs.getByName(release) isMinifyEnabled false } } }isMinifyEnabled false表示不做混淆课程作业不需要开启 R8 混淆否则可能出现 “类找不到” 的 Release 包崩溃而 Debug 包正常。签名文件是.keystore格式的一个文件由Build → Generate向导自动生成需要记住密码后面更新版本时不能换。这个方法也顺带解决一个问题发布 APK 时如果不想暴露密码在代码里可以从环境变量读取但课程项目建议直接把密码写在自己电脑上的配置里就行简单直接。5. 用 Git 固定交付状态与答辩自检5.1 提交节奏与最终版本冻结代码写完后最容易出的问题不是功能而是“改到哪一版才算最终版”。用 Git 给每个阶段打 tag 是一个很值得养成的习惯。开发过程中按节点提交每个提交对应一个能运行的状态git init git add . git commit -m feat: 完成项目骨架和列表页 git tag v0.1然后继续加功能git add . git commit -m feat: 完成详情页和数据保存 git tag v0.2答辩前一周的最终修改再打一个 taggit add . git commit -m final: 修复数据刷新和空态提示 git tag v1.0-release打 tag 的价值在于一旦后面做演示视频或重新生成 APK 时改坏代码可以立刻回退到v1.0-release而不需要在代码里翻找哪个版本是能用的。git log --oneline --decorate可以快速查看 tag 列表配合git reset --hard v1.0-release就能回到稳定版。5.2 演示前十分钟的自检清单在答辩教室或录演示视频开始之前按下面这张表逐项过一遍命中每一项就能避免大多数现场事故检查项操作合格标准安装包可安装在模拟器或真机上执行安装不出现 “应用未安装” 或签名冲突首次启动不崩溃冷启动 AppLogcat 无红色异常数据可写入添加一条记录并重启应用数据没有丢失网络请求不阻塞进入需要网络的页面2 秒内有反馈或错误提示返回键可用在详情页按返回回到列表页且状态不变屏幕旋转旋转一次屏幕不崩溃字段不丢其中“屏幕旋转”最容易被忽略课程作业里很多崩溃其实是配置变更导致 Activity 重建数据没保存就丢失。如果时间不够最简单的方案是在 Manifest 的 Activity 标签里固定竖屏activity android:name.MainActivity android:screenOrientationportrait /答辩时演示到网络请求如果网络环境不乐观提前准备好一个返回假数据的本地 JSON 文件放在assets目录下在 Repository 里做一层判断没网时读本地文件作为兜底数据。这个设计在答辩时反而是加分项因为它体现了一个工程视角不做无提示的失败。最后演示页面务必确认字体大小和屏幕分辨率匹配。模拟器的默认屏幕与实际手机存在差异在模拟器里布局正常、真机上挤成一团的案例很常见。把布局缩略图View → Tool Windows → Layout Inspector打开扫一眼确认没有控件重叠或文字截断这比任何“我是按规范写的”都有说服力。本文还有配套的精品资源点击获取
分享:

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

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