资讯详情 基于Android Studio与SQLite的人员管理系统开发实战
📅 2026/10/14 19:08:56
简介这份资源是面向Android初学者的课程设计项目基于Android Studio开发完整实现了人员管理系统的用户登录与增删改查功能。包内为85.15MB的RAR压缩包共3291个文件涵盖39个java源码、525个xml布局与配置、11个可安装apk、91个dex字节码以及webp图片、json配置和gradle构建文件等既可直接查看核心代码也可导入工程学习完整项目结构。已有1425人学习下载。内容围绕登录界面设计、数据校验、SQLiteOpenHelper建表、CRUD操作、RecyclerView列表展示、Material Design界面优化与Gradle构建部署展开并附带编译产物与调试文件便于对照实际运行效果理解Android开发全流程。对于需要完成人员管理类课程设计或入门Android数据存储与界面开发的学习者这是一份可运行、可参考、可二次改造的实用资料。1. 基于Android Studio的人员管理系统为什么说它比你想的更简单把“人员管理系统”做成一个Android应用很多人的第一反应是要搭服务器、要搞网络交互、要上数据库中间件。但你拿到的这份课程设计资源里核心只有两样东西——Android Studio和一个本地SQLite数据库文件。人员数据的增删改查、关键字搜索、部门筛选、导出备份全都跑在手机本地不需要外网不需要后端甚至不需要一个真实的服务器接口。这种两层架构的好处很直白逻辑清晰、演示稳定、答辩时几乎不会因为网络问题翻车。适合刚学完安卓四大组件、想拿一个完整项目练手或者课程设计需要快速落地成型的同学。这篇文章会从数据库层拆到界面层再到参数设置和坑点全程按我拆项目时实际会动的代码来写。2. SQLite数据层先从人员表结构和DAO层搭起2.1 数据库表结构字段别贪多够用还要能扩展人员管理系统最忌讳一上来就把表设计得很复杂比如“员工绩效表”“考勤表”“薪资表”全塞进一张表。我经手的模拟项目X里最初版本只落四张表但真正跑通后维护成本最高的反而是字段最杂的那张。这次的人员管理系统我一般只保留一张主表字段控制在六个以内字段名类型约束说明idINTEGERPRIMARY KEY AUTOINCREMENT自增主键nameTEXTNOT NULL姓名phoneTEXTNOT NULL手机号departmentTEXT默认“未分配”部门positionTEXT可空岗位remarkTEXT可空备注为什么不做多张表因为课程设计阶段的检索需求大多是“按名字搜”“按部门列”一张宽表配合SQL的WHERE条件就足够覆盖。真到了要扩展SQLite的ALTER TABLE也可以后续动态加列。表结构设计的第一原则是当前功能能跑通未来改起来不难。2.2 SQLiteOpenHelper实现版本号不要乱动DAO层第一步是写一个继承SQLiteOpenHelper的帮助类。常见的坑是很多人在onCreate里建表然后每次改表结构就卸载App重装这样连错误现场都找不到。正确的做法是把数据库版本号当成一个设计参数每次表结构变动就升版本号让onUpgrade兜底。public class DatabaseHelper extends SQLiteOpenHelper { private static final String DB_NAME person_manager.db; private static final int DB_VERSION 1; public DatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } Override public void onCreate(SQLiteDatabase db) { String sql CREATE TABLE IF NOT EXISTS person ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, phone TEXT NOT NULL, department TEXT DEFAULT 未分配, position TEXT, remark TEXT); db.execSQL(sql); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 低版本直接重建实际项目可在这里做迁移 db.execSQL(DROP TABLE IF EXISTS person); onCreate(db); } }这段代码里DB_VERSION 1是初始版本后面如果要加字段不要改这个数而是改成2并重写onUpgrade里的ALTER逻辑。onCreate只在数据库文件第一次生成时执行你的所有建表语句都必须放在这里。getWritableDatabase和getReadableDatabase的调用则统一放在DAO层不在Activity里直接执行SQL。2.3 DAO层实现五个基础方法加一个模糊搜索DAO是连接SQLite和业务逻辑的桥我习惯把增删改查全部封装成独立方法。下面这个PersonDao实现了项目中最核心的五个操作插入人员、按ID删除、全量更新、查询全部、按姓名或手机号模糊搜索。public class PersonDao { private final DatabaseHelper helper; public PersonDao(Context context) { helper new DatabaseHelper(context); } public long addPerson(Person person) { SQLiteDatabase db helper.getWritableDatabase(); ContentValues cv new ContentValues(); cv.put(name, person.getName()); cv.put(phone, person.getPhone()); cv.put(department, person.getDepartment()); cv.put(position, person.getPosition()); cv.put(remark, person.getRemark()); long id db.insert(person, null, cv); db.close(); return id; } public int deleteById(int id) { SQLiteDatabase db helper.getWritableDatabase(); int rows db.delete(person, id?, new String[]{String.valueOf(id)}); db.close(); return rows; } public int updatePerson(Person person) { SQLiteDatabase db helper.getWritableDatabase(); ContentValues cv new ContentValues(); cv.put(name, person.getName()); cv.put(phone, person.getPhone()); cv.put(department, person.getDepartment()); cv.put(position, person.getPosition()); cv.put(remark, person.getRemark()); int rows db.update(person, cv, id?, new String[]{String.valueOf(person.getId())}); db.close(); return rows; } public ListPerson queryAll() { SQLiteDatabase db helper.getReadableDatabase(); ListPerson list new ArrayList(); Cursor cursor db.rawQuery(SELECT * FROM person ORDER BY id DESC, null); while (cursor.moveToNext()) { Person p new Person(); p.setId(cursor.getInt(cursor.getColumnIndexOrThrow(id))); p.setName(cursor.getString(cursor.getColumnIndexOrThrow(name))); p.setPhone(cursor.getString(cursor.getColumnIndexOrThrow(phone))); p.setDepartment(cursor.getString(cursor.getColumnIndexOrThrow(department))); p.setPosition(cursor.getString(cursor.getColumnIndexOrThrow(position))); p.setRemark(cursor.getString(cursor.getColumnIndexOrThrow(remark))); list.add(p); } cursor.close(); db.close(); return list; } public ListPerson searchPersons(String keyword) { SQLiteDatabase db helper.getReadableDatabase(); ListPerson list new ArrayList(); String sql SELECT * FROM person WHERE name LIKE ? OR phone LIKE ? ORDER BY id DESC; String like % keyword %; Cursor cursor db.rawQuery(sql, new String[]{like, like}); while (cursor.moveToNext()) { // 与queryAll中相同的读取逻辑 } cursor.close(); db.close(); return list; } }逻辑说明addPerson使用ContentValues传参避免了字符串拼接SQL注入deleteById和updatePerson的WHERE条件用?占位符这是Android SQLite操作的安全习惯。searchPersons里LIKE ?配合%keyword%实现模糊匹配同时覆盖姓名和手机号两个维度。每个方法结束后一定要closeSQLiteDatabase的连接数很有限不关连接在多次操作后会直接报“数据库被锁定”的运行时异常。2.4 数据库文件去哪了路径和调试入口模拟器或真机上这个库文件实际存储在/data/data/你的包名/databases/person_manager.db。调试阶段可以开启Android Studio的Device File Explorer直接拉出来看也可以用命令行sqlite3工具打开检查表结构。实际项目中这个目录需要root权限才能访问但Android Studio的调试器不受限制。这是在开发期验证数据是否真的写进去了的最快手段不用依赖打印日志。3. UI交互层RecyclerView列表与Activity间数据传递3.1 列表展示RecyclerView比ListView更合适旧式写法多用ListView配ArrayAdapter但到了Android Studio的新版本模板RecyclerView已经是市场主流。人员列表这种动态刷新频繁、删除后需要局部更新的场景RecyclerView配合自定义Adapter的体验好很多。public class PersonAdapter extends RecyclerView.AdapterPersonAdapter.PersonViewHolder { private ListPerson dataset new ArrayList(); NonNull Override public PersonViewHolder onCreateViewHolder(NonNull ViewGroup parent, int viewType) { View view LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_person, parent, false); return new PersonViewHolder(view); } Override public void onBindViewHolder(NonNull PersonViewHolder holder, int position) { Person person dataset.get(position); holder.nameText.setText(person.getName()); holder.phoneText.setText(person.getPhone()); holder.deptText.setText(person.getDepartment()); holder.itemView.setOnClickListener(v - { if (listener ! null) { listener.onItemClick(person); } }); } Override public int getItemCount() { return dataset.size(); } public void setData(ListPerson newData) { dataset.clear(); dataset.addAll(newData); notifyDataSetChanged(); } static class PersonViewHolder extends RecyclerView.ViewHolder { TextView nameText, phoneText, deptText; PersonViewHolder(View itemView) { super(itemView); nameText itemView.findViewById(R.id.person_name); phoneText itemView.findViewById(R.id.person_phone); deptText itemView.findViewById(R.id.person_dept); } } }这里的setData方法先清理再整体填充配合notifyDataSetChanged是最不容易出错的刷新方式。局部刷新notifyItemInserted虽然效率高但新人阶段很容易因为位置计算错误导致界面闪现脏数据。列表点击回调onItemClick在Activity里统一处理跳转不要在ViewHolder内部直接startActivity。3.2 新增与编辑同一个Activity复用两种模式新增人员界面和编辑人员界面在课程设计里往往是同一个布局文件区别只在Intent里多传了一个人员ID。常规做法是在跳转时用Intent.putExtra(person_id, id)新增时传null或-1。编辑器里做判断如果传了ID就调用dao的queryAll或单独queryById回填表单没有就是空白表单。// 在MainActivity中启动编辑页 Intent intent new Intent(this, EditActivity.class); intent.putExtra(person_id, person.getId()); startActivity(intent); // 在EditActivity的onCreate中 int id getIntent().getIntExtra(person_id, -1); if (id ! -1) { dao new PersonDao(this); Person person dao.findPersonById(id); // 回填姓名、电话、部门等输入框 }表单保存时先校验必填项再判断id是否为-1决定调用addPerson还是updatePerson。这种“一个页面处理新增和编辑”的模式在答辩时要多说一句设计理由减少代码重复新增和编辑的界面结构天然一致。3.3 搜索栏实时搜索与防抖人员管理系统的搜索框建议放在列表Activity顶部用EditText监听文本变化每变化一次就调一次dao.searchPersons。性能上SQLite对本地小数据量查询毫秒级完成不需要做防抖但如果后续数据量上万要考虑加一个Handler延时300毫秒再查。搜索后拿返回的List直接调adapter.setData列表就会自动收窄。注意SearchView在这里不是必要的普通EditText加两个属性就够了。4. 五个低频却致命的坑版本升级、校验与列表刷新边界4.1 现象改了表结构但新字段一直报“no such column”原因SQLiteOpenHelper的onUpgrade没有触发因为数据库文件已经存在版本号也没升级。很多人只会建表不会改表遇到这种情况的第一反应是卸载App但答辩时不能这么干。 解决把DB_VERSION从1改成2在onUpgrade里写ALTER TABLE语句Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion 2) { db.execSQL(ALTER TABLE person ADD COLUMN age INTEGER DEFAULT 0); } if (oldVersion 3) { db.execSQL(ALTER TABLE person ADD COLUMN email TEXT); } }每一次版本迁移单独写一个if分支不要合并成一条命令这样历史版本升级路径才可控。4.2 现象输入了汉字姓名点击搜索后结果为空原因EditText拿到的字符串包含不可见空格常见于复制粘贴或输入法自动补全。LIKE查询时前面的空格被当成关键字的一部分。 解决所有搜索关键字统一做trim处理代码写成String keyword editText.getText().toString().trim()。保存数据时姓名和手机号同样做trim。这条在真机演示时特别容易翻车务必处理。4.3 现象删除最后一条数据后列表少了一行但再次删除又报数组越界原因删除后直接调notifyDataSetChanged还好但如果你用了notifyItemRemoved(position)而这个position是基于删除前的旧数据集计算的在连续删除时会拿到越界的索引。 解决删除后重新查一次数据库再调adapter.setData(newList)。不要手动维护列表里的Item位置。正确姿势是数据库删除成功 → 重新queryAll → setData刷新。4.4 现象EditText只判断isEmpty输入全角空格仍然通过验证原因中英文空格、全角空格在isEmpty判断下都认为有内容但保存后看起来像没填一样。 解决校验用TextUtils.isEmpty(phone)结合phone.trim().length() 0。再进一步手机号字段用正则校验11位数字且以1开头我会写一个简单校验方法public static boolean isValidPhone(String phone) { String regex ^1[3-9]\\d{9}$; return Pattern.matches(regex, phone); }格式不对时在输入框setError直接提示不要在保存时才弹Toast。4.5 现象数据库“locked”异常多出现在快速滑动列表时原因每次查询都new一个DAO或者每次都拿getWritableDatabaseSQLite单写多读但同一时刻写操作冲突会抛异常。常见于主线程频繁开数据库连接同时还有一个异步任务在写。 解决项目内统一一个DAO实例查询用getReadableDatabase写操作用getWritableDatabase用完后立刻close。如果后续需要优化可以线程加锁但课程设计阶段做到“所有数据库访问走同一DAO”就足够了。5. 导出与扩展让人员管理系统的演示更完整这个章节落到实处是“版本升级与扩展能力”。本地数据库也有生命周期一块SQLite数据库文件可以导出换了设备也能恢复这在答辩中是一个充分体现工程意识的加分项。5.1 导出CSV文件一行代码备份人员数据SQLite的备份最简单的方式不是拷贝db文件而是把内容导出成CSV方便用Excel打开。工具类里写一个export方法遍历DAO查出的List写入文本文件public static boolean exportCsv(Context context, ListPerson list, String filePath) { File file new File(filePath); try (FileOutputStream fos new FileOutputStream(file); OutputStreamWriter osw new OutputStreamWriter(fos, StandardCharsets.UTF_8)) { osw.write(\uFEFF); // 写入BOM防止Excel打开中文乱码 osw.write(姓名,手机号,部门,岗位,备注\n); for (Person p : list) { osw.write(String.format(%s,%s,%s,%s,%s\n, p.getName(), p.getPhone(), p.getDepartment(), p.getPosition(), p.getRemark())); } osw.flush(); return true; } catch (IOException e) { e.printStackTrace(); return false; } }那段\uFEFF在很多教程里是隐形的没有它Excel双击打开CSV时中文会全变乱码这是我实际踩过的坑。文件路径在Android 10以上要注意分区存储规则简单做法是写到getExternalFilesDir(null)下面不会触发权限问题。5.2 数据恢复的思路导入CSV解析文本然后逐条插入person表相比直接拷贝db文件更安全因为拷贝db文件常因为版本不一致或数据库被占用而失败。如果确实要拷贝先调用SQLiteDatabase的close方法再从/data/data路径读取。这个功能在课程设计的“系统扩展”部分提一下即可不需要做得太重。5.3 按部门折叠展示一个查询搞定分组效果扩展能力里效果最明显的改动不是加第三个Activity而是把已有的查询加一个GROUP BY部门的分组列表。RecyclerView里用getItemViewType区分“部门头”和“人员行”两种视图Adapter内部的viewType逻辑Override public int getItemViewType(int position) { return dataList.get(position).isHeader() ? TYPE_HEADER : TYPE_ITEM; }这个实现思路的价值在于说明你对RecyclerView的Adapter机制有深度理解而不仅仅是套用模板。写到这里想起一个习惯从那以后我每次改表结构都会先在升级脚本里跑一遍从旧到新版本的全路径再回来动数据访问代码。早期做某图像处理Demo时直接改了字段忘了升版本号害得导师演示时当场报错这份尴尬让我养成了“先管数据库版本、再写查询逻辑”的习惯。希望帮到你。本文还有配套的精品资源点击获取