资讯动态

安卓图书管理系统开发:SQLite数据层与RecyclerView界面完整实践

发布时间:2026/9/25 1:35:16 来源:尧图企业网站定制
简介安卓图书管理系统是一份基于Android Studio开发的完整项目源码面向正在学习Android原生开发与SQLite数据库操作的开发者可帮助读者理解从建表、增删改查到界面交互的完整流程。资源为zip压缩包大小约23.26MB平台记录文件总数为0且未列出具体文件类型。目前已有4060人学习下载。系统利用SQLiteOpenHelper与SQLiteDatabase实现图书数据持久化代码覆盖添加、删除、修改、查询等核心功能并涉及ListView列表展示、EditText表单录入等常用UI组件用法同时可参考二维码扫码入库等扩展思路学习如何集成第三方库提升录入效率。对希望通过一个完整实战项目掌握Android数据库编程、数据持久化与界面协作方式的初学者和中高级开发者均有参考价值。1. 安卓图书管理系统在Android Studio里落地难点从来不在界面很多人拿到“安卓图书管理系统”的第一反应是赶紧画界面其实这类项目最容易翻车的全是数据层。Android Studio版本的意思是整个系统就是一个原生Android App用SQLite存数据用RecyclerView列图书用Dialog做增改不依赖任何服务器。它解决的是图书信息登记、借还状态维护、按书名或作者检索这些真实业务适合课程设计、毕业设计也适合第一次完整跑通一个App的开发新手。别被“系统”两个字吓住拆开就是几张表、一个列表、一堆按钮。真在写这个需求的人要的是能打开、能演示、能讲清楚我按这个目标往下拆。2. 搭建Android Studio工程骨架版本、SDK与项目结构一次配好2.1 Android Studio、JDK与SDK选型别用最新用最稳的组合Android Studio版本我建议在Dolphin2021.3.1到Electric Eel2022.1.1之间选一个正式版。这些版本都有中文设置入口Android Studio怎么设置中文这个问题对它们来说只是在Settings里切一下语言的事网上教程也最多。Arctic Fox和Bumblebee这些老版本打开新项目会遇到Gradle插件不兼容太新的版本又会因为默认启用AndroidX让老代码报错。选版本记住一个标准同学或老师能打开你的项目比版本新更重要。JDK用Android Studio内置的Embedded JDK 11就够不要在系统里单独装高版本否则会出现Gradle要求一个JDK版本、项目编译用的又是另一个的黑匣子问题。SDK分两块compileSdk决定编译时能用哪些APIminSdk决定能装到哪些旧手机。图书管理系统没有花哨系统APIcompileSdk 31、minSdk 21是最省心的组合覆盖Android 5.0到12模拟器和老旧真机都能跑。2.2 Gradle配置最小可用build.gradle新建工程时AS默认生成的build.gradle里堆了一堆依赖很多课程设计根本用不上。我一般会把app模块的build.gradle精简成下面这样android { compileSdk 31 defaultConfig { applicationId com.example.bookmanager minSdk 21 targetSdk 31 versionCode 1 versionName 1.0 } } dependencies { implementation androidx.appcompat:appcompat:1.4.2 implementation com.google.android.material:material:1.6.1 implementation androidx.recyclerview:recyclerview:1.2.1 }applicationId决定应用在手机上的唯一标识后面用adb导出数据库时靠它定位应用目录。targetSdk设为31而不是追最新是为了避开Android 13之后的运行时权限和分区存储弹窗那些和图书管理系统没关系却会让演示现场翻车。RecyclerView单独引依赖是因为Material包会连带引入RecyclerView但版本可能被其他依赖覆盖单独声明才能保证行为一致。注意targetSdk不建议超过31除非你愿意处理分区存储和通知权限适配这些和图书业务无关却会消耗大量演示前夜的时间。2.3 包结构与layout文件先铺好骨架写代码才不迷路工程里的Java包不要全堆在MainActivity一个文件里按职责分成三层entity放Book实体类db放DBHelper和BookDaoui放Adapter和Activity。layout目录下预备这三个文件activity_main.xml放RecyclerView和顶部搜索栏item_book.xml放单本图书的书名、作者、价格和借出状态dialog_book_edit.xml放新增和编辑图书的输入框。骨架铺好后MainActivity只需要做四件事初始化RecyclerView、加载列表、监听搜索框、监听按钮弹出编辑框。数据层和界面层彻底分开写DAO时不用关心控件写Adapter时不用关心SQL这是项目能快速写完的关键。还有个建议第一个layout先只放控件不写样式跑通数据流之后再调美化免得一开始就耗在dp、颜色和阴影这些事上。3. SQLite数据库层设计图书借还系统最核心的一个表3.1 三张表的结构与为什么不用外键图书管理系统的数据量撑死几百本完全不需要MySQL或服务器SQLite单文件搞定。表结构建议三张book存图书信息reader存读者borrow存借阅记录。建表SQL这样写CREATE TABLE book ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, author TEXT, price REAL, borrowed INTEGER DEFAULT 0, publish TEXT ); CREATE TABLE reader ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, phone TEXT ); CREATE TABLE borrow ( id INTEGER PRIMARY KEY AUTOINCREMENT, book_id INTEGER, reader_id INTEGER, borrow_time INTEGER );这里故意没有写FOREIGN KEY。原因很实际SQLite默认不强制外键约束要手动执行PRAGMA foreign_keysON才生效而课程设计答辩时老师更常问“删掉一本被借出的书会怎样”而不是“你有没有用外键”。用应用层判断借出状态比依赖数据库约束更直观。borrowed字段用INTEGER当布尔值用0代表在架1代表已借出后面所有借还逻辑都依赖这个字段。publish字段在book表里预留配合onUpgrade演示数据库升级。3.2 DatabaseHelper版本升级的写法决定你以后有没有后悔药SQLiteOpenHelper是系统提供的数据库管理类重写它的onCreate和onUpgrade两个方法就够了。代码这样写public class DBHelper extends SQLiteOpenHelper { private static final String DB_NAME library.db; private static final int DB_VERSION 2; public DBHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE TABLE book ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, author TEXT, price REAL, borrowed INTEGER DEFAULT 0, publish TEXT)); db.execSQL(CREATE TABLE reader (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, phone TEXT)); db.execSQL(CREATE TABLE borrow (id INTEGER PRIMARY KEY AUTOINCREMENT, book_id INTEGER, reader_id INTEGER, borrow_time INTEGER)); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion 2) { db.execSQL(ALTER TABLE book ADD COLUMN publish TEXT); } } }onCreate在数据库文件第一次创建时执行onUpgrade在DB_VERSION变大时触发。这里演示了一个关键习惯升级用ALTER TABLE加字段而不是DROP TABLE重建。很多人的项目一开始没想好字段后期加“出版社”时图省事直接删表重建用户之前录的书全没了。数据库升级没有后悔药从第一版就按增量迁移写后面每次加字段都只加一个if版本判断。注意onCreate里的建表SQL和onUpgrade里的ALTER要保证最终表结构一致否则新装用户和升级用户看到的表会不一样。3.3 BookDao用ContentValues代替拼接SQLDAO层就是封装增删改查的地方图书管理系统最常用的是insert和update核心就两个方法public class BookDao { private final SQLiteDatabase db; public BookDao(Context context) { db new DBHelper(context).getWritableDatabase(); } public long insert(Book book) { ContentValues values new ContentValues(); values.put(title, book.getTitle()); values.put(author, book.getAuthor()); values.put(price, book.getPrice()); values.put(borrowed, book.getBorrowed() ? 1 : 0); values.put(publish, book.getPublish()); return db.insert(book, null, values); } public int update(Book book) { ContentValues values new ContentValues(); values.put(title, book.getTitle()); values.put(author, book.getAuthor()); values.put(price, book.getPrice()); values.put(publish, book.getPublish()); return db.update(book, values, id?, new String[]{String.valueOf(book.getId())}); } }insert返回新行的id如果返回-1说明写入失败。update返回受影响的行数更新单本图书时返回值应该是1。这里建议不要写db.execSQL(UPDATE book SET title title )这种代码单引号、转义符问题防不胜防拼接字符串到SQL里还有注入风险。ContentValues本质是一个键值对集合系统会做类型适配和转义错误率低很多。Book实体类就是几个字段加getter/setter的纯Java类不需要注解不需要继承。4. 图书增删改查的完整实现从DAO到界面的一条完整链路4.1 RecyclerView列表Adapter只做一件事RecyclerView是列表容器真正的逻辑在Adapter里。图书列表的Adapter不关心数据从哪来只负责把Book对象映射到item布局上public class BookAdapter extends RecyclerView.AdapterBookAdapter.VH { private final ListBook list; private OnBookClickListener clickListener; interface OnBookClickListener { void onItemClick(Book book); } static class VH extends RecyclerView.ViewHolder { TextView title, status; VH(View itemView) { super(itemView); title itemView.findViewById(R.id.tv_title); status itemView.findViewById(R.id.tv_status); } } Override public VH onCreateViewHolder(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(VH holder, int position) { Book book list.get(position); holder.title.setText(book.getTitle()); holder.status.setText(book.getBorrowed() ? 已借出 : 在架); holder.itemView.setOnClickListener(v - { if (clickListener ! null) clickListener.onItemClick(book); }); } Override public int getItemCount() { return list.size(); } }这个Adapter的正确用法是用一个final的List引用接收数据外部刷新时不要new一个新List赋给Adapter而是list.clear()再加新数据最后notifyDataSetChanged()。ViewHolder只放两个展示字段就够了价格、出版社这些可以后续做详情页再看。itemView的点击事件把Book对象回调给外部Adapter不直接操作数据库这样职责只有一条改起来不牵连。4.2 新增与编辑共用一个Dialog一个表单两套逻辑交互边界很简单点按钮新增点列表条目编辑。两种情况共用同一个表单布局区别只在保存时走insert还是update。核心写法如下private void showEditDialog(Nullable Book book) { View view getLayoutInflater().inflate(R.layout.dialog_book_edit, null); EditText etTitle view.findViewById(R.id.et_title); EditText etAuthor view.findViewById(R.id.et_author); if (book ! null) { etTitle.setText(book.getTitle()); etAuthor.setText(book.getAuthor()); } AlertDialog dialog new AlertDialog.Builder(this) .setTitle(book null ? 新增图书 : 编辑图书) .setView(view) .setPositiveButton(保存, null) .setNegativeButton(取消, null) .create(); dialog.show(); dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v - { String title etTitle.getText().toString().trim(); if (TextUtils.isEmpty(title)) { Toast.makeText(this, 书名不能为空, Toast.LENGTH_SHORT).show(); return; } if (book null) { dao.insert(new Book(title, etAuthor.getText().toString().trim())); } else { book.setTitle(title); book.setAuthor(etAuthor.getText().toString().trim()); dao.update(book); } refreshList(); dialog.dismiss(); }); }这段代码有两个容易翻车的细节。一是setPositiveButton第一个参数传了null如果直接传监听器AlertDialog会在按钮点击后无条件dismiss校验失败时对话框关了但数据没写。现在改成show()之后再通过getButton挂监听就能在书名校验失败时让对话框留在原地用户填的内容也不会丢。二是book参数用Nullable标注null表示新增非null表示编辑表单回显和标题都通过它区分。refreshList()是公共刷新方法内部从DAO重新查询数据后clearaddAllnotifyDataSetChanged。4.3 借出与归还UPDATE加条件再用事务包住两步借书逻辑不能只改borrowed字段还要往borrow表插一条记录。两步操作必须用事务保证原子性否则会出现“状态改成已借出但借阅记录没了”这种不一致。public boolean borrowBook(int bookId, int readerId) { db.beginTransaction(); try { int rows db.update(book, new ContentValues() {{ put(borrowed, 1); }}, id? AND borrowed0, new String[]{String.valueOf(bookId)}); if (rows ! 1) { return false; // 图书不存在或已被借出 } ContentValues cv new ContentValues(); cv.put(book_id, bookId); cv.put(reader_id, readerId); cv.put(borrow_time, System.currentTimeMillis()); db.insert(borrow, null, cv); db.setTransactionSuccessful(); return true; } finally { db.endTransaction(); } }UPDATE语句里的“AND borrowed0”是并发安全的关键即使两个管理员同时操作同一本书数据库层面也只会让一个借出成功另一个rows为0直接返回false。beginTransaction之后所有SQL都先写入事务缓冲只有setTransactionSuccessful()才会真正提交异常或提前return都不会破坏数据一致性。还书逻辑反过来做把borrowed改回0并在borrow表里更新return_time结构完全一样。4.4 模糊搜索LIKE通配符要放在参数里搜索框的SQL是所有功能里最容易写错的。正确写法是把%通配符拼进查询参数而不是拼进SQL语句public ListBook search(String keyword) { ListBook result new ArrayList(); String like % keyword %; Cursor cursor db.rawQuery( SELECT * FROM book WHERE title LIKE ? OR author LIKE ?, new String[]{like, like}); while (cursor.moveToNext()) { result.add(new Book( cursor.getInt(cursor.getColumnIndexOrThrow(id)), cursor.getString(cursor.getColumnIndexOrThrow(title)))); } cursor.close(); return result; }rawQuery的第二个参数就是用来替换SQL里?的把“% keyword %”直接拼进SQL是新手最爱踩的坑。问题在于keyword里如果含单引号比如《Harrys Adventure》SQL多了一个引号轻则查询失效重则直接崩溃。cursor.getColumnIndexOrThrow在列名写错时会立刻抛异常而不是返回-1后getString吐一个莫名其妙的结果。列名写明白后面维护才不痛苦这也是推荐getColumnIndexOrThrow而不是getColumnIndex的原因。5. 踩坑记录图书管理系统最常见的5个翻车现场5.1 列表数据更新了界面却不刷新现象往数据库里insert成功也调用了refreshList()但RecyclerView还是显示旧数据。原因最常见的是Adapter里的List引用和刷新时用的List不是同一个对象。如果你在Adapter构造里写了一句list new ArrayList(传入数据)外部再往原来的list里add是进不了Adapter的。另一个常见原因是直接在子线程改了list没有切回主线程通知Adapter。解决规范固定为MainActivity持有唯一的List对象每次刷新先list.clear()再加DAO查出来的新数据最后adapter.notifyDataSetChanged()。不要把“重新查数据”和“更新Adapter”拆到两个方法里异步执行课程设计这种单机项目不需要异步加载主线程直接查几百条记录毫秒级完成。5.2 保存按钮点了没反应Dialog却关了现象添加图书的Dialog里书名留空点保存Toast提示“书名不能为空”但对话框也一起关了填了一半的内容全没了。原因AlertDialog.Builder的setPositiveButton传入的OnClickListener执行完之后AlertDialog会自动dismiss这个行为不受你return影响。解决按4.2的写法setPositiveButton传入nullshow()之后用getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener重新挂监听。这样校验失败return时对话框不会关闭用户在表单里输入的内容也不会丢。取消按钮不用特殊处理默认关闭即可。5.3 工程换台电脑就打不开SDK路径与JDK版本不一致现象在宿舍电脑编译好好的换到实验室电脑打开同一份代码Gradle Sync直接报SDK location not found或者一堆Java版本错误。这就是“移植android studio项目”时最常见的崩溃场景。原因AS项目里的local.properties记录了旧电脑的SDK绝对路径这个文件本来就不该提交另外两台电脑安装的Android Studio版本不同内置JDK版本也可能不同。解决打开新电脑的Android Studio先去SDK Manager下载compileSdk对应版本的SDK Platform再打开项目让AS重新生成local.properties。JDK不要选系统安装的版本在Settings的Gradle配置里把Gradle JDK设为Embedded JDK。还有一条血泪经验项目里的build.gradle不要手动去升Gradle版本AS提示用什么版本就让它用什么版本手动改版本号常常引发连锁报错。5.4 首次打开AS卡死在SDK组件检测现象新装的Android Studio第一次新建项目时一直卡在“Unable to access Android SDK add-on list”或进度条转圈这个报错在中文搜索里出现频率极高。原因本机SDK组件不全AS默认要去官方仓库拉取在线清单网络路径不通时不会自动跳过就一直卡在检测界面。解决两个办法配合。先打开SDK Manager设置把网络连接方式改为直连模式并勾选强制使用HTTP协议再在SDK Update Sites里把仓库地址替换为国内可用的镜像地址然后重启AS。镜像地址在阿里云开源镜像站能查到完整路径列表选择对应SDK版本那一行复制进去即可。装了三台电脑的AS都是这个操作解决的没换系统没重装。5.5 覆盖安装App后数据库里的书全没了现象给表加了publish字段DB_VERSION从1改成2直接Run到手机上旧数据全部消失。原因SQLiteOpenHelper的onUpgrade没有被正确触发或者onUpgrade里执行的还是DROP TABLE再CREATE的旧逻辑。很多初学项目为了方便onUpgrade直接写“删除全部表再重建”这对开发期图快可以一旦有真实数据就是灾难。解决从写第一行数据库代码起就按增量迁移来。onUpgrade里判断oldVersion小于2时执行ALTER TABLE加字段小于3时执行另一个ALTER。测试升级要验证路径用adb install -r覆盖安装而不是卸载后装数据才能保留。这个习惯能让你的项目在演示“升级不丢数据”时成为加分项。6. 验收检查清单与adb导出验证确认这个系统真的能用6.1 演示前必过的8个检查项代码写完不等于能交。我整理了一个演示前检查清单每项过一遍基本覆盖答辩老师爱问的场景检查项操作通过标准新增添加《计算机网络》作者谢希仁列表出现新条目重启App后仍在编辑把刚才那本书价格改成59.00列表和详情同步更新搜索输入“计算”返回含关键字的书名清空关键词恢复全量借出选择读者借出该书状态变为“已借出”重复借出被拦截归还归还该书状态变回“在架”可再次借出删除删除一本未借出的书列表移除数据库同步删除升级改DB_VERSION加1后覆盖安装旧数据还在新字段可用断网飞行模式打开App所有功能正常数据在本地第三项容易被遗漏搜索框用TextWatcher监听时文字清空要主动执行一次全量加载很多实现只在文字变化时搜索清空后列表就卡在筛选结果里。6.2 用adb导出数据库亲眼确认数据落盘界面上的列表可能来自内存缓存数据库是否真的写成功了需要导出验证。这个App是debug签名安装可以直接用run-as命令访问应用私有目录adb -s emulator-5554 shell run-as com.example.bookmanager \ cat databases/library.db library_backup.db执行成功后本地会多出一个library_backup.db文件用任意SQLite工具打开执行SELECT * FROM book能看到界面录入的书说明数据真的落盘了。run-as只能用于debug包你自己Run出来的就是debug包这个验证方式对课程设计完全够用。导出后用命令行再查一遍sqlite3 library_backup.db SELECT id, title, borrowed FROM book;输出里borrowed为0的在架为1的已借出这几行数据比任何界面截图都有说服力。我自己的习惯是每完成一个功能模块就导出一次库看一眼新增字段、顺手清一遍测试数据别等答辩前夜一次性导入几百条假数据那种数据经不住老师点两下就露马脚。这个项目的核心就一句话先用SQLite把数据流打通再回头美化界面。我最早做这个方向时先调包、调动画、调卡片阴影结果数据层全是洞一演示借书就崩。后来按“表结构优先、DAO先行、界面后补”的顺序重做两天就收工。这个顺序也许能让你的开发少走几段弯路希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑