简介:面向安卓初学者的Android Studio学生信息管理App课程设计完整版,涵盖环境搭建、XML布局设计、Gradle构建配置、Activity生命周期管理、页面跳转与数据传递、按钮事件监听等核心开发环节,整体难度适中,适合作为高校移动开发课程的综合实践项目。压缩包内含1426个文件,以XML布局、class字节码、rawproto、png图片、json配置及java源码等为主,整体大小21.14MB,便于直接导入Android Studio运行与二次开发。内容上不仅展示了LinearLayout、RelativeLayout、ConstraintLayout等布局管理器的实际用法,也演示了RecyclerView列表展示、SQLite与SharedPreferences两种数据存储方案,并覆盖自定义Adapter、对话框、菜单、输入验证、运行时权限申请、Logcat调试以及模拟器与真机测试方法。在项目组织上,源码、资源文件与Gradle脚本一一齐备,可清晰对照学习Android应用的完整工程结构,还可按步骤生成APK并了解发布流程。已有3730人浏览学习,适合课程设计、毕业设计参考或作为安卓开发作品集项目,帮助开发者同时巩固理论基础与调试排错能力。 安卓课设季又到了,每年这个时候都有大量同学在找“现成的学生信息管理App”。有人是从网盘下了各种“完整版”却跑不起来,有人是想抄代码结果连项目结构都看不懂,还有人是做好了功能却不知道答辩时该讲什么。这篇博文就围绕“Android Studio学生信息管理App”这条线,把这个课设从技术选型、数据库设计、界面逻辑到常见坑位拆开讲清楚。不管你现在手里是一个打不开的rar包,还是打算从零手写交作业,这篇文章都能直接拿来用。
需要说明的是,网上流传的“Mystudent完整版.rar”这类压缩包,本质是一个基于SQLite的本地增删改查Demo,技术栈一般是Java + RecyclerView/ListView + SQLiteOpenHelper,适合课程设计但离企业级应用差距很大。我会把这个层级的东西讲透,同时告诉你哪些地方可以升级、哪些地方是老师爱问的得分点。
1. 你手里那个“完整版”到底是什么:一个典型课设项目的功能拆解
先别急着解压跑代码。任何一个学生信息管理App,不管叫什么名字,核心功能翻来覆去就那几个:学生的增加、删除、修改、查询,也就是常说的CRUD。加上一些辅助功能,比如按学号精确查找、按姓名模糊搜索、统计各班人数、导出数据之类的。你拿到的Mystudent项目,大概率跑不出这个框。
我用一个表把典型的学生信息管理App的功能模块和对应实现方式列出来,你对照着检查自己手里的项目缺了什么:
| 功能模块 | 典型实现方式 | 课程设计评分权重 |
|---|---|---|
| 学生列表展示 | RecyclerView + Adapter | 高 |
| 添加学生 | 弹窗Dialog或独立Activity、EditText输入 | 高 |
| 修改学生 | 点击列表项回填数据再保存 | 高 |
| 删除学生 | 长按列表项删除或点击删除按钮 | 高 |
| 按学号查询 | EditText + 查询按钮+SQL条件查询 | 中 |
| 按姓名模糊查询 | LIKE语句 + 重新加载Adapter | 中 |
| 数据持久化 | SQLiteOpenHelper建库建表 | 高 |
| 界面美化 | Material Design控件、自定义主题 | 低 |
没有SQLite的话,这个课设基本只能叫“页面Demo”,不能叫“信息管理”。因为一旦App进程被杀死,数据全没了,这在答辩现场是很尴尬的。所以我建议你在看项目代码时,第一件事就是找到数据库相关的类,确认有没有extends SQLiteOpenHelper,里面有没有onCreate建表语句。
项目结构上,早期的课设模板喜欢用ListView,16:9的旧教程太多,导致很多人还在复制粘贴ListView的写法。其实RecyclerView也没难多少,但视觉效果和流畅度完全不一样。老师看惯了ListView的作业,突然来一个RecyclerView+卡片布局,印象分直接高一截。
还有一个容易被忽略的功能是“学号唯一性校验”。很多同学做添加时没想这个问题,结果数据库里出现两个学号一模一样的“学生”,这在答辩时非常容易被问倒。稍微好一点的课程设计,会在插入前先用学号查一遍,存在就提示“该学号已存在”。一个if判断的事,但老师会认为你考虑了数据完整性。
2. 技术选型为什么要这样定:Java还是Kotlin、SQLite还是Room
2.1 Java还是Kotlin:课设求稳就选你有把握的
打开你手里的Mystudent项目,先看MainActivity里是Java还是Kotlin。如果是Java,那我建议你老老实实用Java交。不是Kotlin不好,而是课设的时间节点通常在大三前后,很多人Kotlin语法本来就不熟,改两行代码报一堆红,查错的时间够你把Java版本改完了。
当然,如果你打开项目发现是Kotlin写的,也别慌着换。Kotlin和Java在Android Studio里可以混编,同一个项目里既有Java类又有Kotlin类完全没问题。你只需要看懂Kotlin的基本语法,比如:
// Kotlin的findViewById简写 val btnAdd = findViewById<Button>(R.id.btn_add) btnAdd.setOnClickListener { // 点击逻辑 }对应Java是这样:
Button btnAdd = findViewById(R.id.btn_add); btnAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 点击逻辑 } });说句实在话,课设评分主要看功能完成度和能不能讲清楚代码,语言本身不是扣分点。哪个熟用哪个,别在最后两周临时切换技术栈,这是很多项目烂尾的直接原因。
2.2 SQLite还是Room:课设选SQLite,答辩更好讲
现在Google官方推荐用Room数据库,但课程设计这个场景我建议你用SQLite,原因也很实在:
第一,SQLiteOpenHelper的逻辑非常直白,就是onCreate建表、onUpgrade升级表、getWritableDatabase拿数据库实例,这三板斧。你给老师讲的时候可以指着代码一行行说“这里建了一张student表,表里有id、name、age三个字段”,老师一听就懂。
第二,网上现成的课设项目九成都是SQLite。你下载的Mystudent.rar大概率就是SQLite版本,改起来资料多,遇到问题百度一下全是答案。Room的报错信息对新手来说相对抽象,注解处理器一报错,很多人直接懵。
第三,SQLite的SQL语句就是标准SQL,你在数据库课上学的SELECT、INSERT、UPDATE、DELETE全都能用上。这也算是课设和理论课的一次对应,答辩时老师问到数据层,你直接说“我用的SQLite,增删改查对应四条SQL语句”,干净利落。
2.3 界面用XML还是Jetpack Compose
看到“XML布局”不要觉得老,课设选XML依然是最稳的选择。Compose是趋势不假,但趋势不等于课设友好——Compose的思维模型和传统View差别很大,状态提升、重组这些概念没搞懂的话,做一个列表页都要折腾半天。XML你拖拖控件,LinearLayout套一层、RecyclerView放中间、Button放底部,一个标准的管理界面半小时就搭出来了。
而且最关键的是:Compose现在还不太适合答辩。老师大概率没精力看你的Compose代码,但一定看得懂XML里的控件层级。用XML,你能把“LinearLayout垂直布局、上面放输入框、下面放列表”这种话讲明白。Compose你要解释的是“为什么UI状态要提升到ViewModel”,这个坑对课设来说太深了。
3. 数据库设计与核心CRUD代码:照着抄也要知道每一行的含义
3.1 建表语句应该是这个样子的
一个标准的学生信息表,字段至少要有:id(主键自增)、学号、姓名、年龄、班级。有些项目还会加性别、电话、邮箱,这些都行,但核心是前五个。
CREATE TABLE student ( id INTEGER PRIMARY KEY AUTOINCREMENT, stu_no TEXT NOT NULL UNIQUE, name TEXT NOT NULL, age INTEGER, clazz TEXT );注意这里有两个容易被忽略的点:
stu_no一定要加UNIQUE约束,从数据库层面保证学号不重复。哪怕你在Java代码里忘了做唯一性校验,数据库也会帮你拦住重复数据,抛一个SQLiteConstraintException。- 班级字段名不要用
class,因为class是Java的关键字。虽然SQLite里可以加反引号避开,但多一事不如少一事,我见过有同学真的踩了这个坑,查半天发现是字段名冲突。
3.2 SQLiteOpenHelper的标准写法
public class DBHelper extends SQLiteOpenHelper { private static final String DB_NAME = "student.db"; private static final int DB_VERSION = 1; public DBHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE student (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "stu_no TEXT NOT NULL UNIQUE," + "name TEXT NOT NULL," + "age INTEGER," + "clazz TEXT)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS student"); onCreate(db); } }这段代码里最值得跟老师讲的是DB_VERSION这个变量。它是数据库版本号,以后如果你要加字段,比如加一个“籍贯”列,只需要把DB_VERSION改成2,然后在onUpgrade里写ALTER TABLE student ADD COLUMN ...。这样老用户升级App时数据不会丢。课设阶段你当然用不到这个,但知道这个机制,老师问起来你能答上,这就是加分项。
3.3 DAO层:增删改查四个方法别写在Activity里
很多同学图省事,把数据库操作直接写在Activity的按钮点击事件里。写起来确实快,但有个问题:Activity职责太重,又是管界面、又是管数据、又是管逻辑,答辩时你自己都容易绕晕。
更好的做法是单独写一个StudentDao类,把CRUD封装成方法。比如这样:
public class StudentDao { private DBHelper dbHelper; public StudentDao(Context context) { dbHelper = new DBHelper(context); } // 添加学生 public long insert(Student student) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("stu_no", student.getStuNo()); values.put("name", student.getName()); values.put("age", student.getAge()); values.put("clazz", student.getClazz()); long result = db.insert("student", null, values); db.close(); return result; } // 删除学生 public int deleteByStuNo(String stuNo) { SQLiteDatabase db = dbHelper.getWritableDatabase(); int result = db.delete("student", "stu_no = ?", new String[]{stuNo}); db.close(); return result; } // 修改学生 public int update(Student student) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("name", student.getName()); values.put("age", student.getAge()); values.put("clazz", student.getClazz()); int result = db.update("student", values, "stu_no = ?", new String[]{student.getStuNo()}); db.close(); return result; } // 查询全部学生 public List<Student> findAll() { List<Student> list = new ArrayList<>(); SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor cursor = db.rawQuery("SELECT * FROM student", null); while (cursor.moveToNext()) { Student student = new Student(); student.setId(cursor.getInt(cursor.getColumnIndexOrThrow("id"))); student.setStuNo(cursor.getString(cursor.getColumnIndexOrThrow("stu_no"))); student.setName(cursor.getString(cursor.getColumnIndexOrThrow("name"))); student.setAge(cursor.getInt(cursor.getColumnIndexOrThrow("age"))); student.setClazz(cursor.getString(cursor.getColumnIndexOrThrow("clazz"))); list.add(student); } cursor.close(); db.close(); return list; } }这里有个细节:.rawQuery后面一定要cursor.close()和db.close()。很多同学不关游标和数据库,运行几次就报SQLiteException: database is locked。其实Android有自动回收机制,但频繁操作不关闭,总有一天会出问题。把这行代码养成习惯,能帮你省下大量排错时间。
另外getColumnIndexOrThrow这个方法值得记住,它是通过列名拿索引,哪怕你SQL语句里字段顺序换了,代码依然不用改。有些人用getColumnIndex("name"),返回-1也不报错,后面再getString(-1)就直接崩了,排查半天不知道是列名写错。getColumnIndexOrThrow至少会帮你快速暴露问题。
4. 界面与数据交互:Adapter才是列表的灵魂
4.1 ListView还是RecyclerView:手把手讲清RecyclerView怎么用
如果你手里的项目模板是ListView,我建议你有余力的话改成RecyclerView。不用怕,两者套路差不多,但RecyclerView的ViewHolder模式能避免列表滑动时卡顿,用起来也没复杂多少。
步骤拆开看就三步:
- 在
build.gradle里加依赖:implementation 'androidx.recyclerview:recyclerview:1.3.2' - XML布局里放
RecyclerView控件 - 写一个Adapter类,继承
RecyclerView.Adapter<你的ViewHolder>
Adapter的代码是所有列表类App共用的一套逻辑:
public class StudentAdapter extends RecyclerView.Adapter<StudentAdapter.StudentViewHolder> { private List<Student> studentList; private OnItemClickListener listener; public StudentAdapter(List<Student> studentList) { this.studentList = studentList; } // 定义点击接口,方便Activity处理点击逻辑 public interface OnItemClickListener { void onItemClick(int position); void onItemLongClick(int position); } public void setOnItemClickListener(OnItemClickListener listener) { this.listener = listener; } @NonNull @Override public StudentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_student, parent, false); return new StudentViewHolder(view); } @Override public void onBindViewHolder(@NonNull StudentViewHolder holder, int position) { Student student = studentList.get(position); holder.tvName.setText(student.getName()); holder.tvStuNo.setText(student.getStuNo()); holder.tvClazz.setText("班级:" + student.getClazz()); } @Override public int getItemCount() { return studentList.size(); } static class StudentViewHolder extends RecyclerView.ViewHolder { TextView tvName, tvStuNo, tvClazz; public StudentViewHolder(@NonNull View itemView) { super(itemView); tvName = itemView.findViewById(R.id.tv_name); tvStuNo = itemView.findViewById(R.id.tv_stu_no); tvClazz = itemView.findViewById(R.id.tv_clazz); } } }onCreateViewHolder负责创建item的View,onBindViewHolder负责把数据绑定到指定位置的View上,getItemCount告诉RecyclerView一共有多少条数据。这三个方法对应着列表从无到有的全过程,答辩时你就按这个顺序讲,思路非常清晰。
4.2 点击弹窗改数据:把Dialog封装成一个方法
很多同学改数据是跳一个新Activity,填完再跳回来刷新。这样做功能上没错,但体验很割裂。我更推荐在列表页直接用AlertDialog弹一个编辑框,改完点确定,数据直接更新,视觉效果利落得多。
private void showEditDialog(final Student student, final int position) { // 用布局填充器加载dialog_edit.xml View dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_edit, null); EditText etName = dialogView.findViewById(R.id.et_edit_name); EditText etAge = dialogView.findViewById(R.id.et_edit_age); etName.setText(student.getName()); etAge.setText(String.valueOf(student.getAge())); new AlertDialog.Builder(this) .setTitle("修改学生信息") .setView(dialogView) .setPositiveButton("保存", (dialog, which) -> { String newName = etName.getText().toString().trim(); int newAge = Integer.parseInt(etAge.getText().toString().trim()); student.setName(newName); student.setAge(newAge); dao.update(student); studentList.set(position, student); adapter.notifyItemChanged(position); }) .setNegativeButton("取消", null) .show(); }这段代码最大的亮点是notifyItemChanged(position),它只刷新被修改的那一行,而不是整个列表。数据量小的时候看不出差别,但这是一个好习惯——以后做更大的项目,你会知道局部刷新能避免多少性能问题。
4.3 列表数据刷新的彩蛋:别忘了一个隐蔽错误
很多同学做完增删改查后,发现一个诡异的问题:添加一条新数据,返回列表页却看不到,非得退出App重进才显示。这个问题的根源是,Activity的onResume里没有重新查询数据库。
解决方案很简单:把加载数据的逻辑放在onResume里,而不是onCreate里。
@Override protected void onResume() { super.onResume(); refreshData(); } private void refreshData() { studentList.clear(); studentList.addAll(dao.findAll()); adapter.notifyDataSetChanged(); }Activity从B页面返回A页面时会执行onResume,这时候重新findAll(),列表自然就是最新的了。这个坑几乎每个做过数据库课设的人都踩过,你现在知道了,就能少走一次弯路。
5. 课设项目最常翻车的几个地方:从导入到运行的完整排坑指南
5.1 Gradle同步失败:“每次新建项目都要下载Gradle”
很多同学解压了Mystudent.rar,兴冲冲打开Android Studio,结果卡在Gradle sync大半天,最后还报错。原因很简单:别人项目里的Gradle版本和你的Android Studio不匹配,或者Gradle发行包下载超时。
解决办法有三个,按推荐程度排序:
- 用Android Studio自带的Gradle,不下载项目指定的版本。具体做法是打开
gradle\wrapper\gradle-wrapper.properties文件,把distributionUrl改成你本地已有的Gradle版本。 - 用国内镜像源。在项目根目录的
build.gradle或settings.gradle里配置阿里云镜像,google()和mavenCentral()仓库替换成阿里的地址。这一步能解决依赖下载慢的问题。 - 离线模式碰运气。这个不推荐,因为缺什么依赖你根本不知道。
说句经验之谈:项目导入后第一件事不是跑起来,而是先看File > Project Structure里SDK版本、Java版本跟本机是否一致。很多报错都是compileSdk和Java版本不匹配造成的,对不上就先调对,别急着找“为什么报错”。
5.2 模拟器连不上ADB:不如直接用真机调试
经常有同学说“模拟器启动成功了,但Android Studio一直显示No target device”。这种情况八成是模拟器和ADB的版本不匹配,或者ADB被占用。排查命令是:
adb devices能列出设备就说明正常连通。如果列表为空,试试重启ADB:
adb kill-server adb start-server说实话,学生管理这种轻量App,真机调试比模拟器痛快得多。USB连上手机,手机打开开发者模式,把“USB调试”打开,Android Studio一般几秒钟就能识别。这里提醒一句:小米、OPPO、vivo这些手机,插上数据线后要在通知栏里把USB模式从“仅充电”改成“传输文件”,否则ADB识别不到。
5.3 Android 11及以上版本的分区存储:老项目容易在这个地方崩
前几年写的课设项目,如果在Android 10以上跑,可能会遇到FileNotFoundException,尤其是在导出文件到公共目录时。Android 10开始强制分区存储,App不能随便往公共存储目录写文件了。
如果你手里的项目有“导出数据库到/sdcard”这种功能,要么把targetSdkVersion降到28或29(学聪明点,降级可以,但答辩时别主动提),要么改用MediaStore接口写入公共目录。我建议课设阶段用后面这种保守方案:把导出的文件写到getExternalFilesDir()目录,这是App专属目录,不需要任何权限,难度最低,也不会踩坑。
File file = new File(getExternalFilesDir(null), "students_backup.db");5.4 中文字符乱码:检查文件编码
解压别人项目后打开Java文件发现中文全成了乱码,这个太常见了。在Android Studio右下角,看当前文件的编码格式,如果是GBK,改成UTF-8。改完可能需要重新打开文件才生效。这个坑不致命,但影响心情,密恐患者看到乱码真的会头皮发麻。
6. 拿到“完整版”之后,怎么把项目改成自己的课设
6.1 先跑通,再改名,最后加功能
这个顺序非常关键,千万别倒过来。我见过太多同学拿到别人的项目,先花三天改包名,结果改完包名启动就崩,定位不到错误,最后整个项目废了。正确姿势是:
- 第一步,原封不动把项目跑起来,确保能增删改查。
- 第二步,改应用名和桌面图标。
AndroidManifest.xml里的android:label改应用名,mipmap图标换掉。这一步成本最低,但一眼就能看出“这是我自己的作品”。 - 第三步,改包名。右键
com.example.xxx,Refactor > Rename,选“Rename package”。改完编译跑一遍,确保没报错。 - 第四步才是加新功能。
每改一步跑一遍,出了问题就能快速定位是改动导致的还是原来就有问题。这比一次性改完再排错效率高太多了。
6.2 最值得加的三个“小亮点”功能
如果时间允许,我强烈建议你在基础CRUD之上加一个亮点功能,不用复杂,但要有得讲。推荐三个:
- 数据统计图表:统计各班人数,用
MPAndroidChart画一个饼图。这个库用起来不算难,加一个依赖,几行代码就能出图,但视觉冲击力很强,答辩现场一亮相,老师基本都会抬头看屏幕。 - 搜索功能精化:除了按学号精确查询,再加一个“按姓名模糊搜索”。其实就是一个
LIKE '%关键字%'的SQL语句,十几行代码的事,但功能列表里能多写一项。 - 导出数据:把数据库里的学生信息导出成CSV或TXT文件,分享给别人。代码量不大,但是能把SQLite、文件流、Intent分享几个知识点串起来,是很好的答辩素材。
这三个功能选一个加就行,贪多嚼不烂。功能不在多,在于你能讲明白。
6.3 答辩时老师最爱问的几个问题,提前准备
根据我看到的答辩现场,学生信息管理这个题目,老师翻来覆去问的就那几类问题:
- “你的数据存在哪里?App退出后数据还在吗?”——答案是SQLite,数据库文件存在
/data/data/包名/databases/目录下。 - “数据库版本升级怎么做?”——答案就是上面提到的
onUpgrade方法。 - “列表为什么用RecyclerView,跟ListView有什么区别?”——RecyclerView的ViewHolder是强制使用的,列表复用效率高,还支持局部刷新和动画。
- “如何进行模糊查询的?”——把SQL语句背出来:
SELECT * FROM student WHERE name LIKE '%?%'。 - “如何保证学号不重复?”——两个层面:数据库
UNIQUE约束加代码层插入前校验。
这些问题都不难,但你要是不提前准备,现场很容易卡壳。毕竟代码是你复制的,不是一行行敲出来的,但答辩要求你能讲清楚每一步的设计意图。所以拿到项目后,别急着关掉它,把数据库类和Adapter这两块代码通读一遍,不懂的查清楚,这比功能本身更重要。
7. 课设验收之后的几条经验建议
做完这个项目之后,不管你以后是继续做安卓开发还是转行做后端、前端,有几件事我建议你顺手做掉,收益远大于启动一个App本身。
第一,把项目提交到Gitee或GitHub的私有仓库。你以后找工作,简历上写“熟悉Android开发”,最好能附一个项目链接。哪怕是课程设计级别的项目,也能说明你有独立完成一个App从开发到调试的闭环能力。你投简历的时候就知道,能拿出来给人看的项目,比空口说自己“熟悉”要有说服力得多。
第二,写一份简单的README。项目是做什么的、怎么导入、有哪些功能、用到了什么技术。这既方便你自己以后回看,也方便老师或面试官快速了解项目。写README也是一个复盘的过程,写着写着你会发现哪些技术点自己其实还没完全理解。
第三,把项目里用到的技术点挨个画个图理一遍:界面层有哪些Activity、数据层有哪些DAO、数据库有哪些表、它们之间怎么调用。这里说的画图是指手绘示意或Visio、ProcessOn这类工具,跟写README一样,这个梳理过程本身就是最好的学习方式。你梳理完了,答辩时不管老师问哪个点,你都清楚它在整个链路里的位置。
我在大学期间做过不少类似的小项目,最大的体会是:课程设计的价值不在评分,而在于它逼着你把课堂上学的东西串成一个完整的产物。数据库课上学了SQL,安卓课上学了Activity,Java课上学了面向对象,但只有当你把这些东西拧在一起做一个能跑、能点、能存数据的东西时,你才算真正理解它们各自是干什么用的。
本文还有配套的精品资源,点击获取