Android航空订票系统课设实战:从SQLite数据库到业务逻辑全解析
简介这是一份基于Android平台的航空订票系统课程设计项目主要面向Android学习者、移动开发初学者以及需要完成同类课设的高校学生以完整客户端工程展示从界面搭建到业务逻辑落地的全过程。系统客户端覆盖航线信息录入、订票、退票、修改航班信息以及输出全部航线信息和客户信息等核心业务是理解Android数据存储、页面跳转、事件监听与界面更新的综合实践样例。压缩包共含126个文件大小约4.08MB文件类型以Java源码、XML布局与配置、PNG图标资源为主另有Gradle构建脚本、需求文档和综合课程设计报告工程目录划分清晰方便对照文档阅读和调试代码。目前已有294人学习下载适合课程设计、项目实训或移动开发入门阶段的参考学习也可作为毕业设计的选题基础。通过这份资源读者既能掌握Android客户端各功能模块的编码思路与数据传递方法也能依靠课程设计报告和需求说明完善自己的项目文档从而提炼出同类系统开发中的通用套路与设计思路。1. 基于 Android 的航空订票系统一份课设资源能挖出多少实战细节航空订票系统在 Android 课设里是出现频率最高的题目之一。它不像通讯录、计算器那样只考单一知识点而是把 Android 开发里最常见的三件事全占了多页面导航、SQLite 数据库、业务状态管理。编号 100010286 的资源是一份用 Java 写的 Android Studio 工程目标是还原一套完整的订票闭环——用户注册登录、航班查询、下单选座、订单管理全部串起来不是只有两个页面的静态 Demo。对正在赶课设的同学来说它的价值不只在于「能跑通」更在于你能在现有代码上改字段、加页面、换主题做出一个能讲清楚逻辑的自己的版本。对刚学完 Android 基础、在找练手项目的人来说它是一份很好的阅读样本Activity 之间怎么传参数、RecyclerView 和 Adapter 怎么配合、SQLite 的增删改查在一个真实业务里怎么落地这些在单篇教程里很难看到完整串联。下面按我拆项目的习惯从业务拆解、数据设计、环境运行、常见排错到答辩演示一条线讲完。中间会给出表结构和核心代码也会把那些不跑一遍根本发现不了的坑直接标出来。2. 系统拆解订票业务怎么映射成页面和数据库拿到课设资源别急着打开代码点 Run。我一般先做两件事把功能模块画出来把数据表结构理清楚。这两件事搞明白代码只是按图索骥。航空订票系统听着功能多拆开就三块用户体系、航班查询、订单管理外加一个可选的管理端做航班维护。2.1 用户端与管理端两类角色的页面划分用户端这条线的标准页面流转是这样的启动进登录页没账号就跳注册页登录成功进主页面主页面放航班查询条件——出发城市、到达城市、日期点查询跳航班列表列表项展示航班号、起降时间、价格和余票点某条航班进入订票页填写乘机人姓名、证件号和购票张数提交后生成订单订单列表页能看到全部历史订单未出票的可以取消。这个流程涉及的 Activity 大概五六个。老课设模板里如果带管理端会额外加一个 FlightManageActivity 做航班的增删改查。页面跳转全部用 Intent航班和用户信息用 Bundle 塞进 Intent 传递这是答辩必问的点。列表展示这块老模板多用 ListView新一点的用 RecyclerView两者课设都能过但 RecyclerView 的 ViewHolder 复用机制更好讲。如果你拿到的资源是 ListView 版建议自己改造成 RecyclerView改动不大答辩时能说出「为什么换、换来解决了什么」很加分。页面和 Activity 的对应关系大致如下页面核心职责关键组件LoginActivity登录校验、跳转注册页EditText、ButtonMainActivity查询条件录入城市、日期Spinner、DatePickerFlightListActivity航班列表展示、点击进入订票RecyclerView / ListView AdapterBookingActivity乘机人信息填写、提交订单EditText、RadioButtonOrderListActivity订单展示、取消操作RecyclerView / ListView Adapter页面间传参的代码很简单但很多新手会在这里翻车。查询页面把条件塞进 IntentIntent intent new Intent(this, FlightListActivity.class); intent.putExtra(fromCity, fromCity); intent.putExtra(toCity, toCity); intent.putExtra(flightDate, date); startActivity(intent);接收页面用 getIntent 取出来用注意 getStringExtra 和 putExtra 的类型必须一一对应put 的时候是 intget 的时候用 getStringExtra 就会拿到 null。这种类型不匹配不报编译错但运行时会空指针排查起来很费时间。还有一个容易被忽略的点查询条件里两个城市用 Spinner 联动时onItemSelected 回调在页面初始化时会自动触发一次导致还没操作就被填入默认值。我一般用一个 boolean 标志位跳过首次回调或者在初始化完成之后再调用 setSelection。2.2 SQLite 表结构航班、用户、订单三张表的字段怎么定数据层在课设级别基本就是 SQLite SQLiteOpenHelper这套资源也不例外。核心表三张用户表、航班表、订单表。第一次写课设的人容易犯一个错订单表里把航班所有信息冗余一遍或者只存一个航班 id展示时到处 join。课设数据量小join 也行但更常见的做法是订单表只存必要的业务字段展示时按需回查航班表。CREATE TABLE tb_user ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password TEXT NOT NULL, phone TEXT, create_time TEXT DEFAULT (datetime(now, localtime)) ); CREATE TABLE tb_flight ( id INTEGER PRIMARY KEY AUTOINCREMENT, flight_no TEXT NOT NULL UNIQUE, airline TEXT, from_city TEXT NOT NULL, to_city TEXT NOT NULL, depart_time TEXT NOT NULL, arrive_time TEXT NOT NULL, price REAL NOT NULL, total_seats INTEGER NOT NULL, remain_seats INTEGER NOT NULL, flight_date TEXT NOT NULL ); CREATE TABLE tb_order ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_no TEXT NOT NULL UNIQUE, user_id INTEGER NOT NULL, flight_id INTEGER NOT NULL, passenger_name TEXT NOT NULL, passenger_id_card TEXT NOT NULL, seat_count INTEGER NOT NULL, total_price REAL NOT NULL, status INTEGER DEFAULT 0, create_time TEXT DEFAULT (datetime(now, localtime)), FOREIGN KEY (user_id) REFERENCES tb_user(id), FOREIGN KEY (flight_id) REFERENCES tb_flight(id) );有几个字段值得单独说明。price 用 REAL课设够用但答辩时能说出「正式项目里价格用分存储、避免浮点累积误差」比一句「反正能运行」好很多。status 是订单业务的核心字段约定 0 未支付、1 已出票、2 已取消展示层再映射成文字不要用字符串存状态。订单号 order_no 不建议直接拿自增 id 展示我一般用时间戳拼随机数生成字符串视觉上更像真实订单号也避免泄露每日订单量这类信息。表的初始化由 SQLiteOpenHelper 管理课设资源的写法基本是这种结构public class DBHelper extends SQLiteOpenHelper { private static final String DB_NAME airline.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_TB_USER); db.execSQL(CREATE_TB_FLIGHT); db.execSQL(CREATE_TB_ORDER); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(DROP TABLE IF EXISTS tb_order); db.execSQL(DROP TABLE IF EXISTS tb_flight); db.execSQL(DROP TABLE IF EXISTS tb_user); onCreate(db); } }注意这里的 DATABASE_VERSION。很多课设改表结构只改 SQL 不改版本号导致老设备上表不更新运行时报字段缺失或表不存在。如果你拿到资源后修改了任何一张表的字段记得把版本号加一让 onUpgrade 真正执行这个问题在避坑章节还会展开。2.3 订票与退票状态流转余票扣减的关键逻辑订票是整套系统里最值得讲的业务逻辑。很多初学者写订票就是往订单表插一条记录完事余票不减或者减了不校验。一个合格的处理流程是先查航班余票是否足够再扣减余票然后插入订单三步放在同一个事务里。public boolean bookFlight(SQLiteDatabase db, long userId, long flightId, String name, String idCard, int count) { db.beginTransaction(); try { // 1. 查询当前余票单机课设场景不加锁逻辑上够用 Cursor c db.rawQuery( SELECT remain_seats FROM tb_flight WHERE id ?, new String[]{String.valueOf(flightId)}); int remain 0; if (c.moveToFirst()) { remain c.getInt(0); } c.close(); if (remain count) { return false; // 余票不足直接失败 } // 2. 先扣余票再插入订单顺序不能反 db.execSQL(UPDATE tb_flight SET remain_seats remain_seats - ? WHERE id ?, new Object[]{count, flightId}); String orderNo genOrderNo(); double unitPrice queryPrice(db, flightId); db.execSQL( INSERT INTO tb_order(order_no, user_id, flight_id, passenger_name, passenger_id_card, seat_count, total_price, status) VALUES(?,?,?,?,?,?,?,0), new Object[]{orderNo, userId, flightId, name, idCard, count, unitPrice * count}); db.setTransactionSuccessful(); return true; } finally { db.endTransaction(); } }扣减放在插入之前是因为余票是共享资源先扣再插即使插到一半异常事务回滚也能把余票恢复不会出现「订单没生成余票却被扣了」的状态。判断用remain count而不是remain 0因为一张订单可以买多张票这个条件才完整。事务一定要用 try-finally 包住保证 beginTransaction 之后不管成不成功都会 endTransaction否则数据库连接长时间占用后面所有操作卡死这也是「数据库偶尔动不了」的一个重要排查方向。退票是订票的逆操作但多了一个关键点必须先校验订单状态。订单已经取消过再退一次余票会被加两遍这种数据错乱在答辩现场会被一眼看穿。标准做法是先查订单 status只有等于 1已出票才允许取消然后在一个事务里把 status 改成 2同时把对应航班的余票加回去。「先查状态再改状态」这个顺序是我排查过好几个数据对不上问题后总结出来的。3. 在 Android Studio 里把项目跑起来环境对齐到编译成 APK资源到手第一步永远是环境对齐。Android 课设项目最典型的翻车方式就是代码没问题但电脑上的 Android Studio 和项目里配置的 Gradle、SDK 对不上一 sync 就一片红。这里先说版本匹配再给完整部署路径。如果你连 Android Studio 都还没装好先把 JDK、Android SDK、IDE 这三样装齐再开始不然后面每一步都会卡在环境上。3.1 前置检查JDK、compileSdk、Gradle 版本怎么对齐打开项目先看三个文件根目录的build.gradle、gradle/wrapper/gradle-wrapper.properties、app/build.gradle。它们分别决定 Gradle 插件版本、Gradle 运行时版本、编译用的 SDK 版本。首次同步时如果提示下载 Android SDK 平台等它下完再继续跳过的话后面所有构建都会报 SDK 缺失这种低级问题最浪费排队时间。老课设项目的配置一般落在这个范围compileSdk 28 到 34minSdk 21 到 26targetSdk 跟随 compileSdk。Gradle 插件版本和 Gradle 版本有固定对应关系插件 4.2.x 配 Gradle 6.7.1插件 7.x 配 Gradle 7.3插件 8.x 配 Gradle 8.x。资源如果是老模板可能是插件 3.6 或 4.1 配 Gradle 6.5 左右新版 Android Studio 打开会提示自动升级。点 OK 让 IDE 自己处理也行但升级完经常伴随依赖报错我更建议手动对齐。常用的版本组合如下直接对照着改AGP 插件版本Gradle 最低版本常用 compileSdk适用场景3.6.x6.528年代较早的老模板4.2.x6.7.130通用课设模板7.4.x7.533主流新版模板8.1.x8.034新项目推荐改版本的位置在gradle-wrapper.propertiesdistributionUrlhttps\://services.gradle.org/distributions/gradle-7.4-bin.zipdistributionUrl 指定 Gradle 运行时版本。如果下载慢优先考虑换镜像源而不是干等。改完在菜单里点 File → Sync Project with Gradle Files右下角进度条走完不报红就算对齐了。老年份项目 sync 报错时优先降 AGP 而不是升因为升 AGP 往往连带要求更高版本 JDK 和 SDK改动面更大。从老模板移植到新版环境或者把项目迁移给同学用最核心的就是把这一步对齐其他代码基本不用动。3.2 导入与编译从 Open 到生成 APK 的完整步骤环境检查完就可以导入了。File → Open 选中项目根目录注意一定要选到包含settings.gradle的那一层选错层 Gradle 识别不到模块打开后会一片空白。首次打开会触发 Gradle 下载和依赖拉取建议网络畅通时等它跑完别中途点 Cancel否则部分依赖缓存损坏后面还得 Clean 重来。编译我建议分两步走。第一步先 Build → Build Bundle(s) / APK(s) → Build APK(s)只产出安装包验证编译链路是否通。编译成功后 APK 默认在app/build/outputs/apk/debug/app-debug.apk。第二步再连接设备或模拟器直接 Run让 IDE 帮你安装并启动。课设迭代时每次修改后直接点 Run 更快需要交付安装包时才用 Build APK(s)。app 模块的依赖声明在app/build.gradle里课设资源里常见的是这几个版本可以按需调整dependencies { implementation androidx.appcompat:appcompat:1.6.1 implementation com.google.android.material:material:1.9.0 implementation androidx.recyclerview:recyclerview:1.3.0 }如果打开工程后依赖报红先确认仓库源里有没有你要的版本。androidx 的包一般都在 google() 仓库老项目如果只配了 jcenter()会显示找不到依赖把根 build.gradle 里的仓库源改成下面这样就解决了buildscript { repositories { google() mavenCentral() } }拿到 APK 手动部署到设备命令行方式更适合排查安装问题adb install -r app-debug.apk adb shell am start -n com.example.airline/.ui.LoginActivityinstall -r的-r表示覆盖安装。这里有个隐藏坑如果你改过数据库表结构覆盖安装会保留旧数据库文件新的建表逻辑不会执行运行时会报 no such table。这种时候要么卸载重装要么把 SQLiteOpenHelper 的版本号加一触发 onUpgrade。am start后面的组件名必须和 AndroidManifest.xml 里声明的一致写错了报 ActivityNotFound。3.3 模拟器与真机部署两套路径的差异和注意点模拟器适合快速跑通和截图真机适合现场演示。模拟器优势是环境干净SDK 自带 AVD 镜像和项目的 minSdk 一般不会冲突不用插线课设报告里的界面图基本都是模拟器截的。缺点是冷启动慢老机器跑 x86_64 镜像会卡。另外模拟器不走 USB 调试但 adb 命令照样能用adb devices会看到 emulator 开头的一行该有的日志一条不少。真机调试的注意点更常见。开发者选项入口不同品牌不一样一般是连续点版本号七次开启插上 USB 后手机会弹「允许 USB 调试吗」勾选始终允许adb 识别不到设备时先adb kill-server再adb start-server能解决八成识别不上的问题。还有一个演示技巧用adb shell am start直接指定启动页跳过登录直达要演示的功能页现场不用现输账号少了很多手滑风险。演示完记得清理手机的 adb 授权记录避免设备留在别人那里还能被连上。4. 避坑清单跑课设最常见的翻车现场这些坑是我自己做课设、也帮别人排查过不少「跑不起来」的问题后攒下来的踩坑记录每条按现象、原因、解决三步写你可以直接对着报错信息找。一共五条覆盖编译、运行和数据三个层面基本能把课设翻车的常见位置覆盖全。4.1 编译期翻车Gradle 版本冲突与 R 文件标红现象一项目一打开 Gradle sync 就报Could not find com.android.tools.build:gradle:7.2.2或者一堆依赖无法解析。原因大概率是 AGP 与 Gradle 运行时版本不匹配或者仓库源不可达。解决方法是先看gradle-wrapper.properties里的 distributionUrl把 Gradle 升到与 AGP 匹配的版本再检查根 build.gradle 的buildscript.repositories是否同时配置了 google() 和 mavenCentral()。老模板很多只写了 jcenter()jcenter 已经停止服务必须换掉这个坑极其常见。现象二编译报AAPT: error: resource string/app_name not found或者工程里所有 R 引用标红。这种情况多数不是真的缺资源而是上一次编译失败导致 R 类没有生成。解决方法是 Build → Clean Project清理后重新 Sync 再 Build。如果试完还是红检查资源文件名是不是用了大写或中文Android 资源文件只允许小写字母和下划线这条新手特别容易踩。注意Clean 之后第一次 Build 时间会明显变长那是在重新生成 R 类和资源索引属于正常现象别以为卡死了。4.2 运行期翻车白屏闪退与列表不刷新现象三App 启动直接闪退Logcat 里能看到android.database.sqlite.SQLiteException: no such table: tb_order。第一次遇到时真的觉得很玄学——建表语句明明写在 onCreate 里。原因是你之前装过旧版本 App设备上已经有旧结构的数据库文件覆盖安装时 onCreate 不会再次执行。解决方法是卸载重装或者把 SQLiteOpenHelper 的 DATABASE_VERSION 加一在 onUpgrade 里执行 drop 重建。这也是为什么每次改了表结构都要卸载干净再装而不是直接点 Run 覆盖。排查这类闪退我习惯先用 logcat 过滤异常标签日志里定位到具体异常类再动手比瞎猜快很多adb logcat -s AndroidRuntime:E SQLiteLog:E *:S-s后面跟的是日志 tag 过滤条件AndroidRuntime 管 App 崩溃堆栈SQLiteLog 管数据库错误两个 tag 一起看基本能覆盖八成课设闪退现场。最后的*:S表示静默其他 tag日志输出会干净很多。现象四页面跳转正常但 RecyclerView 或 ListView 列表是空的或者订完票返回列表数字没变。原因多半是查完数据库没有通知 Adapter 刷新或者在子线程里改了数据直接碰 UI。解决办法是数据变更后统一在主线程跑adapter.notifyDataSetChanged()或者用runOnUiThread包一下刷新动作。如果涉及跨页面数据比如订单列表更稳妥的方式是在 onResume 里重新查询并刷新这样从订票页返回时自动带上新数据。4.3 业务逻辑翻车余票负数与订单重复退票现象五订了几张票后航班余票变成负数或者同一张订单退票两次余票加了两遍。这两个问题本质都是操作顺序写错了。余票负数是因为没做余票校验直接执行了 UPDATE重复退票是因为没检查订单当前状态。解决方法是把订票逻辑改成 2.3 节的事务写法查余票 → 扣余票 → 插订单三步一个事务退票时先查 status 再决定是否执行同样用事务包住「改状态 加余票」两步。这个套路我在后来写库存扣减类小系统时也在用属于通用的数据一致性方案。还有一个容易被忽略的坑很多课设里取消订单是直接 DELETE 那条记录再把余票加回去。这样做的缺陷是订单在业务上凭空消失一旦余票加回那一步失败两边就对不上账而且答辩时「为什么订单删了」很难圆。规范做法是把取消做成UPDATE tb_order SET status 2保留记录只把余票加回去。这也是订单表必须加 status 字段的核心理由它不是装饰字段是数据一致性的兜底。5. 演示与验证把「能跑」变成「能答」课设答辩里代码是不是你写的问几句话就能试探出来。老师最爱问的是数据库现在有几条订单退票之后余票加回来了吗怎么证明加回来了如果没准备现场容易答得支离破碎。我每次做演示前都会强制走一遍闭环验证路线是固定的。第一步把 App 卸载干净重装走真实流程注册测试账号登录查今天的航班下一个两座的单。第二步验证余票变化不要只靠眼睛看列表里的余票数字直接把数据库文件拉出来看adb exec-out run-as com.example.airline databases/airline.db /tmp/airline.db sqlite3 /tmp/airline.db SELECT id, remain_seats FROM tb_flight;run-as 在 debug 包上可以直接读取应用私有目录不需要 root。这条命令能确认下单前后的 remain_seats 差值是不是正好等于下单张数。第三步执行退票再拉一次库确认余票回到原值、订单 status 变成 2。三步走完数据层面自洽现场被追问也有终端命令作为依据。另外一个小技巧答辩前准备一个专用演示账号里面只放三条精心构造的订单一条已支付、一条待支付、一条已取消别把平时调试留下的脏数据带上场。我吃过一次亏那次现场打开订单列表全是一边调试一边随手点的「测试1」「测试2」老师随口问了一句「这些都是你自己买的票吗」场面安静了好几秒。从那以后我每次交课设或演示 Android 项目都会强制走一遍「卸载重装 → 新建账号 → 下单 → 拉库验证余票 → 退票 → 再拉库」的完整流程验证完数据再调界面视觉效果这已经是习惯了。代码能跑只是起点数据能对上才叫真正做通了。希望这篇拆解能帮你把课设这条路走顺一点。本文还有配套的精品资源点击获取