简介这是一份面向高校学生与Android初学者的校园图书管理系统课程设计完整源码基于Android Studio开发可作为移动应用开发课程的满分课设参考。项目围绕用户管理、图书查询、虚拟货币充值、评论评分等核心业务展开覆盖Activity与Fragment、XML布局与Material Design组件、SQLite数据持久化、网络请求与JSON解析、权限管理、用户认证、支付回调及FCM消息推送等知识点适合希望系统串联Android开发技能的学习者。资源包共101个文件以43个xml布局、25个java源码、18个png图片资源为主另含gradle构建脚本、gson与gradle-wrapper等jar依赖及配置文件压缩包约440KB结构紧凑便于导入运行。目前已有315人学习下载可帮助读者快速理解一个完整App从界面搭建、数据存储到网络交互的实现思路并作为课设答辩与二次开发的参考模板。1. 校园图书管理系统课设从选题到跑通Android Studio 这条路值不值得走每年期末计科和软工的学生都会在同一个问题上卡住课设做什么。校园图书管理系统几乎是出现频率最高的选题之一原因很直接——需求清晰、功能边界好画、答辩时老师一听就懂。但真正动手时很多人会在 Android Studio 的 Gradle 同步、SQLite 建表、Activity 跳转传值这三件事上反复翻车。我带过几届课设见过太多人把时间耗在环境配置上最后功能只做了个登录页。这篇笔记面向的是准备用 Android Studio 做校园图书管理系统课设的人。不管你是刚装完 Android Studio 的新手还是已经能写 Activity 但没搭过完整项目的老手下面这套路径都能直接照着走。我会把技术选型理由、数据库设计、核心页面实现、打包提交这几个环节拆开讲重点放在「为什么这么选」和「哪里容易出问题」上。整套方案不依赖任何后端服务纯本地 SQLite 就能跑通答辩演示零风险。2. 技术选型与项目骨架为什么是 SQLite 而不是 MySQL2.1 课设场景下的存储方案对比校园图书管理系统的核心数据是图书信息、读者信息、借阅记录这三张表。课设环境下数据量不会超过几百条并发访问基本为零。这个前提下选型的第一原则是「不引入额外依赖」。MySQL 需要单独装服务端、配账号密码、处理网络连接答辩时老师电脑上没装 MySQL 就直接演示不了。SQLite 是 Android 系统内置的一个SQLiteOpenHelper子类就能建库建表数据文件跟着 APK 走换台电脑装个 APK 就能跑。这是课设场景下最稳妥的选择。方案部署成本答辩演示风险适合场景SQLite零配置极低单机课设、演示MySQL需装服务端高多端共享、真实项目Room需加依赖低想展示架构能力文件存储零配置中数据量极小Room 是 SQLite 的 ORM 封装代码更简洁但需要改 Gradle 依赖新手容易在版本匹配上卡住。如果你对 Gradle 已经比较熟用 Room 能加分如果只想稳直接用原生 SQLite。2.2 用 Android Studio 建项目的最小步骤打开 Android Studio选 Empty Views Activity语言选 Java课设答辩时 Java 比 Kotlin 更好讲最低 SDK 选 API 24。建完之后先别急着写代码等 Gradle 同步完成确认左下角没有红色报错。# 检查 Gradle 是否同步成功在 Terminal 里执行 ./gradlew tasks # 如果输出一堆任务列表说明环境没问题 # 如果报错先看 File Project Structure SDK Location 是否配了 JDKGradle 同步失败是新手第一个翻车点。常见原因是 JDK 版本不匹配Android Studio 自带 JDK在 Settings Build Gradle Gradle JDK 里选 Embedded JDK 就行。另一个原因是网络问题导致依赖下载失败在settings.gradle里确认仓库地址是google()和mavenCentral()。2.3 项目目录结构规划建完项目后先在java/com/example/library下建几个包把代码分开放db放DBHelper.java负责建库建表bean放Book.java、User.java、BorrowRecord.java实体类dao放BookDao.java、UserDao.java封装增删改查ui放所有 Activity这个结构不是必须的但答辩时老师看到分层清晰会加分。更重要的是你自己改代码时不会在一堆文件里翻半天。3. 数据库设计与建表三张表撑起整个系统3.1 表结构设计与字段说明校园图书管理系统的数据关系不复杂三张表足够图书表存书的信息用户表存读者信息借阅记录表关联前两张表。-- 图书表 CREATE TABLE book ( id INTEGER PRIMARY KEY AUTOINCREMENT, isbn TEXT UNIQUE NOT NULL, title TEXT NOT NULL, author TEXT, publisher TEXT, total_count INTEGER DEFAULT 1, available_count INTEGER DEFAULT 1 ); -- 用户表 CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_no TEXT UNIQUE NOT NULL, name TEXT NOT NULL, password TEXT NOT NULL, role INTEGER DEFAULT 0 -- 0读者 1管理员 ); -- 借阅记录表 CREATE TABLE borrow_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, book_id INTEGER NOT NULL, user_id INTEGER NOT NULL, borrow_date TEXT NOT NULL, return_date TEXT, status INTEGER DEFAULT 0 -- 0借出 1已还 );isbn加 UNIQUE 约束是为了防止重复录入同一本书。available_count和total_count分开存是因为借出后库存要减但总藏书量不变。borrow_record里用book_id和user_id做外键关联查询时用 JOIN 就能拼出「谁借了哪本书」。3.2 DBHelper 的写法与版本管理public class DBHelper extends SQLiteOpenHelper { private static final String DB_NAME library.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 book (...)); db.execSQL(CREATE TABLE user (...)); db.execSQL(CREATE TABLE borrow_record (...)); // 插入默认管理员账号 db.execSQL(INSERT INTO user (student_no, name, password, role) VALUES (admin, 管理员, 123456, 1)); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(DROP TABLE IF EXISTS book); db.execSQL(DROP TABLE IF EXISTS user); db.execSQL(DROP TABLE IF EXISTS borrow_record); onCreate(db); } }DB_VERSION从 1 开始以后改表结构就加 1onUpgrade里写迁移逻辑。课设阶段直接 DROP 重建最省事但要注意这会清空数据。onCreate里插入默认管理员账号是个实用技巧省得你每次重装都要手动注册一个管理员。注意onCreate只在数据库文件不存在时调用一次。如果你改了建表语句但没改版本号新语句不会生效这是很多人调试时「改了代码没反应」的原因。3.3 封装 BookDao 的增删改查public class BookDao { private DBHelper helper; public BookDao(Context context) { helper new DBHelper(context); } public long insert(Book book) { SQLiteDatabase db helper.getWritableDatabase(); ContentValues values new ContentValues(); values.put(isbn, book.getIsbn()); values.put(title, book.getTitle()); values.put(author, book.getAuthor()); values.put(total_count, book.getTotalCount()); values.put(available_count, book.getAvailableCount()); long id db.insert(book, null, values); db.close(); return id; // 返回-1表示插入失败 } public ListBook queryAll() { ListBook list new ArrayList(); SQLiteDatabase db helper.getReadableDatabase(); Cursor cursor db.rawQuery(SELECT * FROM book, null); while (cursor.moveToNext()) { Book book new Book(); book.setId(cursor.getInt(cursor.getColumnIndexOrThrow(id))); book.setTitle(cursor.getString(cursor.getColumnIndexOrThrow(title))); book.setAvailableCount(cursor.getInt(cursor.getColumnIndexOrThrow(available_count))); list.add(book); } cursor.close(); db.close(); return list; } }insert返回的 long 值是自增主键返回 -1 说明插入失败通常是 ISBN 重复触发了 UNIQUE 约束。queryAll里用getColumnIndexOrThrow而不是getColumnIndex这样列名写错时会直接抛异常比返回 -1 再静默出错好排查。4. 核心页面实现登录、图书列表与借阅逻辑4.1 登录页与角色判断登录页要做两件事验证账号密码、根据角色跳不同界面。布局用 LinearLayout 竖排两个 EditText 和一个 Button 就够。public class LoginActivity extends AppCompatActivity { private EditText etStudentNo, etPassword; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); etStudentNo findViewById(R.id.et_student_no); etPassword findViewById(R.id.et_password); findViewById(R.id.btn_login).setOnClickListener(v - { String no etStudentNo.getText().toString().trim(); String pwd etPassword.getText().toString().trim(); if (no.isEmpty() || pwd.isEmpty()) { Toast.makeText(this, 账号密码不能为空, Toast.LENGTH_SHORT).show(); return; } UserDao dao new UserDao(this); User user dao.login(no, pwd); if (user null) { Toast.makeText(this, 账号或密码错误, Toast.LENGTH_SHORT).show(); return; } Intent intent; if (user.getRole() 1) { intent new Intent(this, AdminActivity.class); } else { intent new Intent(this, ReaderActivity.class); } intent.putExtra(user_id, user.getId()); startActivity(intent); finish(); }); } }trim()去掉首尾空格很关键手机输入法很容易在末尾带空格。putExtra(user_id, ...)把用户 ID 传给下一个页面后续借书时要用来写借阅记录。finish()让登录页出栈按返回键不会退回登录页。4.2 图书列表用 RecyclerView 展示RecyclerView 是 Android Studio 里最常用的列表控件比 ListView 灵活。先在build.gradle的 dependencies 里加implementation androidx.recyclerview:recyclerview:1.3.2然后写 Adapter。public class BookAdapter extends RecyclerView.AdapterBookAdapter.VH { private ListBook data; private OnItemClickListener listener; public interface OnItemClickListener { void onClick(Book book); } public BookAdapter(ListBook data, OnItemClickListener listener) { this.data data; this.listener listener; } NonNull Override public VH onCreateViewHolder(NonNull ViewGroup parent, int viewType) { View view LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_book, parent, false); return new VH(view); } Override public void onBindViewHolder(NonNull VH holder, int position) { Book book data.get(position); holder.tvTitle.setText(book.getTitle()); holder.tvInfo.setText(库存 book.getAvailableCount() / book.getTotalCount()); holder.itemView.setOnClickListener(v - listener.onClick(book)); } Override public int getItemCount() { return data.size(); } static class VH extends RecyclerView.ViewHolder { TextView tvTitle, tvInfo; VH(View itemView) { super(itemView); tvTitle itemView.findViewById(R.id.tv_title); tvInfo itemView.findViewById(R.id.tv_info); } } }Adapter 里用接口回调把点击事件传出去Activity 里实现这个接口处理借书逻辑。这种写法比在 Adapter 里直接写业务代码更清晰答辩时也更好讲。4.3 借书逻辑与库存扣减借书要做三件事检查库存、插入借阅记录、扣减可借数量。这三步必须放在一个事务里否则可能出现库存扣了但记录没写进去的情况。public boolean borrowBook(int bookId, int userId) { SQLiteDatabase db helper.getWritableDatabase(); db.beginTransaction(); try { // 1. 检查库存 Cursor cursor db.rawQuery( SELECT available_count FROM book WHERE id ?, new String[]{String.valueOf(bookId)}); if (!cursor.moveToFirst() || cursor.getInt(0) 0) { cursor.close(); return false; } cursor.close(); // 2. 扣减库存 db.execSQL(UPDATE book SET available_count available_count - 1 WHERE id ?, new Object[]{bookId}); // 3. 插入借阅记录 ContentValues values new ContentValues(); values.put(book_id, bookId); values.put(user_id, userId); values.put(borrow_date, new SimpleDateFormat(yyyy-MM-dd, Locale.getDefault()) .format(new Date())); values.put(status, 0); db.insert(borrow_record, null, values); db.setTransactionSuccessful(); return true; } finally { db.endTransaction(); db.close(); } }beginTransaction和setTransactionSuccessful之间的代码要么全成功要么全回滚。还书逻辑反过来把available_count加 1把借阅记录的status改成 1填上return_date。5. 避坑与排查课设答辩前最容易翻车的五个点5.1 现象Gradle 同步卡在 Download 不动原因默认仓库地址在国外网络不稳定。解决在settings.gradle里把google()和mavenCentral()放到最前面如果还是慢在gradle.properties里加org.gradle.jvmargs-Xmx2048m加大内存。实在不行用离线模式但前提是依赖已经下载过。5.2 现象APP 装上后闪退Logcat 报 NullPointerException原因findViewById返回 null通常是布局文件里控件 ID 写错或者setContentView之前就调用了findViewById。解决先确认setContentView在第一行再检查 XML 里android:id和 Java 里R.id.xxx是否一致。Logcat 里看Caused by那一行能直接定位到出错的代码行。5.3 现象数据库改了表结构但查询报 no such column原因DB_VERSION没加onUpgrade没触发旧表结构还在。解决把DB_VERSION加 1或者卸载重装 APP。调试阶段最省事的办法是在onCreate里先DROP TABLE IF EXISTS再建表但记得提交前改回来。5.4 现象RecyclerView 显示空白但数据源有数据原因忘记调adapter.notifyDataSetChanged()或者LayoutManager没设置。解决在setAdapter之前先recyclerView.setLayoutManager(new LinearLayoutManager(this))数据更新后调notifyDataSetChanged。如果是在 Activity 的onResume里刷新数据记得每次重新查询后都要通知。5.5 现象打包 APK 时报错提示签名冲突原因之前装过同包名的调试版签名不一致。解决在build.gradle里把applicationId改一个后缀比如加.v2或者先在手机上卸载旧版本。正式打包用 Build Generate Signed Bundle / APK创建一个新的 keystore记住密码答辩提交时把 APK 和 keystore 一起交。6. 打包提交与答辩演示的加分技巧6.1 生成正式签名的 APK课设提交通常要求交 APK 文件老师会装到手机上验收。用 Build Generate Signed Bundle / APK选 APK创建 keystore。keystore 文件要保存好下次更新还得用同一个。# 也可以用命令行打包在项目根目录执行 ./gradlew assembleRelease # 生成的 APK 在 app/build/outputs/apk/release/ 下如果只是想快速出一个能装的包用assembleDebug也行但 debug 包在有些手机上会被安全软件拦截。正式提交建议用 release 包。6.2 答辩演示的数据准备答辩前在数据库里预置几条数据别让老师看到空列表。可以在DBHelper.onCreate里插入几本测试图书或者写一个「初始化数据」的按钮。预置数据要真实一点别用「测试1」「测试2」用《数据结构》《操作系统》这种书名老师一看就知道你认真做了。6.3 演示流程设计演示时按这个顺序走先登录管理员账号展示图书管理增删改查再登录读者账号展示借书和还书最后展示借阅记录查询。每一步操作前先说一句「我现在演示的是XX功能」让老师跟上你的节奏。提前在脑子里过三遍流程别到现场现找按钮。6.4 代码提交的注意事项提交源码时把build目录删掉只留app/src和 Gradle 配置文件。.idea目录也可以删那是 Android Studio 的本地配置。在项目根目录写一个README.md说明项目功能、运行环境、默认账号密码。这个 README 花十分钟写答辩印象分能加不少。我自己的习惯是每次改完一个功能就 commit 一次commit message 写清楚改了什么。这样万一改崩了还能回退答辩前也能从 commit 记录里快速回忆做了哪些功能。课设这东西功能不在多在于每个功能都能讲清楚为什么这么做。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?