
简介这是一份面向计算机专业本科生的Android毕业设计实战项目聚焦宠物管理场景帮助学习者系统掌握移动应用开发全流程。资源包含完整的App源码、数据库脚本pet.sql、服务端代码server目录、部署说明与开发环境搭建指南覆盖UI设计、SQLite本地存储、网络请求、JSON解析、用户认证及文件媒体处理等核心技能点。压缩包共1168个文件以513个Java源文件和524个XML布局/配置文件为主体辅以Gradle构建脚本、SQL数据库定义、Shell部署脚本及Markdown文档整体47.16MB结构规范、模块清晰便于分层学习与调试。已有881人学习下载适合初学者通过宠物百科、论坛交互、档案管理、证件办理四大功能模块理解真实项目中前后端协同、权限控制与用户体验设计的落地逻辑。1. 这不是又一个“宠物日记”App它用 Android 原生架构解决真实养宠场景中的数据断层问题很多毕业设计选“宠物管理App”最后却只做出带头像上传和简单备忘录的壳子——用户拍照存猫粮批次结果照片存在 app 私有目录里换手机就全丢打疫苗提醒设了但没和系统日历联动到期根本收不到通知连“驱虫记录”都得手动填日期而实际场景中用户更习惯对着药盒扫码确认。这个基于 Android 的毕业设计源码核心价值不在功能多而在把养宠动作喂食、驱虫、体检映射为可持久化、可触发、可跨应用协作的 Android 组件行为用 ContentProvider 封装宠物健康数据供其他应用查询用 JobIntentService 实现离线环境下的定时驱虫提醒用 FileProvider 安全共享疫苗证书 PDF 给社区医院 App。适合正在做 Android 课程设计、毕设或想补足「真实设备交互能力」的开发者——它不教你怎么写 RecyclerView而是告诉你当用户点击“生成体检报告”按钮时startActivityForResult()后真正该监听什么回调、如何判断ActivityResult是否来自系统打印服务、为什么getUriForFile()的 authority 必须和 manifest 里provider标签完全一致。2. 用 Android Studio 2023.2.1 SDK 34 构建最小可运行骨架从空项目到能存一只猫的数据模型2.1 为什么选 Room 而非 SQLiteOpenHelper毕业设计里它省掉 3 类硬编码错误在毕业设计中直接手写SQLiteDatabase的onCreate()和onUpgrade()是高危操作字段类型写错如把INTEGER写成INT、主键缺失、外键约束未声明都会导致SQLiteException: no such table却找不到源头。Room 通过注解强制编译期校验比如定义宠物实体类Entity(tableName pet_info) public class PetEntity { PrimaryKey(autoGenerate true) public long id; ColumnInfo(name pet_name) public String name; // 不允许 null ColumnInfo(name species) public String species; // cat or dog ColumnInfo(name birth_date) public long birthTimestamp; // 毫秒级时间戳避免 SimpleDateFormat 线程安全问题 Ignore public String avatarPath; // 仅用于 UI 层不存入数据库 }注意Ignore字段必须显式声明否则 Room 编译器会报错Cannot figure out how to save this field。这是 Room 强制你区分「数据模型」和「UI 模型」的设计约束恰恰是毕业设计最容易忽略的分层意识。2.2 创建 Database 类并验证迁移路径用fallbackToDestructiveMigration()保开发效率但必须知道它删库的边界Database( entities {PetEntity.class, HealthRecordEntity.class}, version 1, exportSchema false // 毕业设计无需导出 schema.json减小 apk 体积 ) public abstract class PetDatabase extends RoomDatabase { public abstract PetDao petDao(); public abstract HealthRecordDao healthRecordDao(); private static volatile PetDatabase INSTANCE; public static PetDatabase getDatabase(final Context context) { if (INSTANCE null) { synchronized (PetDatabase.class) { if (INSTANCE null) { INSTANCE Room.databaseBuilder( context.getApplicationContext(), PetDatabase.class, pet_management_db ) .fallbackToDestructiveMigration() // 开发阶段允许删库重建 .build(); } } } return INSTANCE; } }fallbackToDestructiveMigration()在版本号升级时自动删除旧表重建避免写Migration类——但必须清楚它只在version变更且无 Migration 对象时触发且会清空所有已有数据。毕业设计答辩前务必替换为显式 Migration例如从 v1 到 v2 增加体重字段static final Migration MIGRATION_1_2 new Migration(1, 2) { Override public void migrate(NonNull SupportSQLiteDatabase database) { database.execSQL(ALTER TABLE pet_info ADD COLUMN weight REAL DEFAULT 0.0); } }; // 然后在 databaseBuilder() 中添加 .addMigrations(MIGRATION_1_2)2.3 初始化数据库并插入首只测试猫用 Application 类确保单例生命周期在app/src/main/java/your/package/MyApplication.java中public class MyApplication extends Application { Override public void onCreate() { super.onCreate(); // 首次启动时预置测试数据避免空列表界面 new Thread(() - { PetDatabase db PetDatabase.getDatabase(this); PetDao dao db.petDao(); if (dao.getAllPets().size() 0) { PetEntity firstCat new PetEntity(); firstCat.name 橘子; firstCat.species cat; firstCat.birthTimestamp System.currentTimeMillis() - 365L * 24 * 3600 * 1000; // 1年前 dao.insert(firstCat); } }).start(); } }并在AndroidManifest.xml的application标签中声明application android:name.MyApplication ... 提示new Thread()启动异步初始化是安全的因为Application.onCreate()在主线程执行但 Room 的insert()是同步阻塞操作必须移出主线程否则 ANRApplication Not Responding风险极高——这是毕业设计答辩时评委常问的性能陷阱。3. 实现核心业务流从扫码录入驱虫药到生成 PDF 报告的完整 Android 原生链路3.1 用 ZXing 集成扫码功能不调用第三方 App所有逻辑在本 App 内闭环毕业设计常见误区是调用Intent.ACTION_VIEW打开微信扫码这导致流程中断、无法获取扫码结果。本方案集成 ZXing 核心库com.journeyapps:zxing-android-embedded:4.3.0在activity_scan.xml中嵌入ZXingScannerViewcom.journeyapps.barcodescanner.DecoratedBarcodeView android:idid/zxing_barcode_scanner android:layout_widthmatch_parent android:layout_height0dp app:layout_constraintTop_toTopOfparent app:layout_constraintBottom_toTopOfid/btn_flash app:zxing_focused_camera_previewtrue /在ScanActivity.java中处理扫码结果Override public void onResume() { super.onResume(); barcodeView.setResultHandler(this); // this 实现了 ZXingScannerView.ResultHandler barcodeView.startCamera(); } Override public void handleResult(Result rawResult) { String content rawResult.getText(); // 解析药品二维码格式为 DRUG|FLEA|2024-06-15|Frontline if (content.startsWith(DRUG|)) { String[] parts content.split(\\|); if (parts.length 4) { String type parts[1]; // FLEA or WORM String dueDate parts[2]; // 2024-06-15 String brand parts[3]; // 转换为毫秒时间戳存入 HealthRecordEntity long dueTimestamp parseDateToMillis(dueDate); HealthRecordEntity record new HealthRecordEntity(); record.petId getCurrentPetId(); // 从 Intent 或 ViewModel 获取当前宠物 ID record.type type; record.dueDate dueTimestamp; record.brand brand; record.status HealthRecordEntity.STATUS_PENDING; new InsertHealthRecordAsyncTask(healthRecordDao).execute(record); } } }parseDateToMillis()必须使用SimpleDateFormat并设置setLenient(false)否则2024-13-01会被错误解析为2025-01-01private long parseDateToMillis(String dateStr) { try { SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd, Locale.getDefault()); sdf.setLenient(false); // 关键禁止宽松解析 return sdf.parse(dateStr).getTime(); } catch (ParseException e) { return System.currentTimeMillis(); // 解析失败则设为当前时间 } }3.2 用 PdfDocument 生成本地 PDF 报告绕过网络请求纯 Android 原生实现毕业设计常因调用在线 PDF 生成 API如 wkhtmltopdf导致部署困难。本方案用 Android 原生PdfDocumentprivate void generatePdfReport(Context context, PetEntity pet, ListHealthRecordEntity records) { String fileName pet_report_ pet.id .pdf; File file new File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), fileName); PdfDocument document new PdfDocument(); PdfDocument.Page page document.startPage( new PdfDocument.PageInfo.Builder(595, 842, 1).create() // A4 尺寸 ); Canvas canvas page.getCanvas(); Paint paint new Paint(); paint.setTextSize(16f); paint.setColor(Color.BLACK); // 标题 canvas.drawText(【宠物健康管理报告】, 100, 100, paint); // 宠物信息 canvas.drawText(姓名 pet.name, 100, 150, paint); canvas.drawText(种类 pet.species, 100, 180, paint); canvas.drawText(出生日期 formatTimestamp(pet.birthTimestamp), 100, 210, paint); // 驱虫记录表格简化版 float y 260; for (HealthRecordEntity r : records) { String statusText r.status HealthRecordEntity.STATUS_DONE ? ✅ 已完成 : ⚠️ 待处理; canvas.drawText(r.brand ( r.type ) - statusText, 100, y, paint); y 40; } document.finishPage(page); try (FileOutputStream fos new FileOutputStream(file)) { document.writeTo(fos); Toast.makeText(context, 报告已保存至 file.getAbsolutePath(), Toast.LENGTH_LONG).show(); } catch (IOException e) { e.printStackTrace(); } document.close(); }关键参数说明PdfDocument.PageInfo.Builder(595, 842, 1)中595和842是 A4 纸的像素宽高72dpi 下不是 dpfile必须存于getExternalFilesDir()而非Environment.getExternalStorageDirectory()否则 Android 10 会因分区存储策略拒绝写入。3.3 用 FileProvider 安全共享 PDF解决 Android 7.0 的 StrictMode 限制在res/xml/file_paths.xml中声明路径?xml version1.0 encodingutf-8? paths xmlns:androidhttp://schemas.android.com/apk/res/android external-files-path nameexternal_files_path/ path./ /paths在AndroidManifest.xml中注册 providerprovider android:nameandroidx.core.content.FileProvider android:authorities${applicationId}.fileprovider android:exportedfalse android:grantUriPermissionstrue meta-data android:nameandroid.support.FILE_PROVIDER_PATHS android:resourcexml/file_paths / /provider分享 PDF 的代码File pdfFile new File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), pet_report_1.pdf); Uri contentUri FileProvider.getUriForFile( context, context.getPackageName() .fileprovider, pdfFile ); Intent shareIntent new Intent(Intent.ACTION_SEND); shareIntent.setType(application/pdf); shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); // 必须授予临时读取权限 context.grantUriPermission( android.intent.action.SEND, contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION ); startActivity(Intent.createChooser(shareIntent, 分享报告));注意grantUriPermission()的toPackage参数不能写*必须指定目标包名如com.whatsapp但Intent.createChooser()会动态选择因此这里用android.intent.action.SEND作为占位符——这是 Android 兼容性方案实测在主流分享面板中有效。4. 配置 Android 12 通知与后台任务让驱虫提醒在锁屏状态下可靠触发4.1 用 WorkManager 替代 AlarmManager适配 Android 12 的后台执行限制AlarmManager 在 Android 12 上对非前台应用的精确闹钟setExactAndAllowWhileIdle有严格限制而驱虫提醒必须准时。WorkManager 是 Google 推荐的替代方案它自动适配不同 Android 版本的后台策略// 创建周期性工作请求每天检查一次驱虫到期状态 PeriodicWorkRequest checkDueWork new PeriodicWorkRequest.Builder( CheckDueWorker.class, 15, TimeUnit.MINUTES // 最小间隔 15 分钟满足系统要求 ) .setConstraints( new Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() ) .build(); WorkManager.getInstance(context).enqueueUniquePeriodicWork( check_health_due, ExistingPeriodicWorkPolicy.KEEP, // 避免重复注册 checkDueWork );CheckDueWorker.java中查询即将到期的记录public class CheckDueWorker extends CoroutineWorker { public CheckDueWorker(NonNull Context context, NonNull WorkerParameters params) { super(context, params); } NonNull Override public Result doWork() { PetDatabase db PetDatabase.getDatabase(getApplicationContext()); ListHealthRecordEntity dueSoon db.healthRecordDao() .getDueSoon(System.currentTimeMillis() 24L * 3600 * 1000); // 24 小时内到期 if (!dueSoon.isEmpty()) { sendNotification(dueSoon); } return Result.success(); } private void sendNotification(ListHealthRecordEntity records) { NotificationCompat.Builder builder new NotificationCompat.Builder( getApplicationContext(), health_reminder ) .setSmallIcon(R.drawable.ic_pet) .setContentTitle(驱虫提醒) .setContentText(有 records.size() 项驱虫计划即将到期) .setPriority(NotificationCompat.PRIORITY_HIGH) .setAutoCancel(true) .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); NotificationManagerCompat manager NotificationManagerCompat.from(getApplicationContext()); manager.notify(1001, builder.build()); } }4.2 在 AndroidManifest.xml 中声明 Notification ChannelAndroid 8.0 强制要求!-- 必须在 application 内 -- meta-data android:nameandroid.app.background_activity android:valuefalse / !-- 在 application 外manifest 内 -- uses-permission android:nameandroid.permission.POST_NOTIFICATIONS /并在Application.onCreate()中创建 channelif (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { NotificationChannel channel new NotificationChannel( health_reminder, 健康提醒, NotificationManager.IMPORTANCE_HIGH ); channel.setDescription(驱虫、疫苗等健康事项提醒); NotificationManager manager getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); }4.3 处理 Android 12 的 PendingIntent 语义变更FLAG_IMMUTABLE 是硬性要求在CheckDueWorker发送通知时若需点击跳转到详情页PendingIntent 必须显式声明不可变Intent intent new Intent(getApplicationContext(), HealthDetailActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent PendingIntent.getActivity( getApplicationContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE // Android 12 强制要求 ); builder.setContentIntent(pendingIntent);提示FLAG_IMMUTABLE表示 PendingIntent 内容不可被接收方修改这是 Android 安全模型升级的关键点。若漏写应用在 Android 12 设备上将无法触发通知点击跳转。5. 毕业答辩高频问题应对三个必须现场演示的验证技巧5.1 验证数据库是否真在外部存储写入用 Device File Explorer 直接定位 db 文件在 Android Studio 中打开View → Tool Windows → Device File Explorer路径导航至/data/data/your.package.name/databases/pet_management_db右键pet_management_db→Save As…导出到本地用 DB Browser for SQLite 打开执行 SQLSELECT p.pet_name, h.brand, h.dueDate FROM pet_info p JOIN health_record h ON p.id h.pet_id WHERE h.status pending;若返回结果为空说明insert()未成功或status字段值不匹配如存了PENDING但查询用pending。这是答辩时最直观证明数据持久化的手段。5.2 验证 FileProvider 共享路径是否生效用 adb shell 检查 URI 解析在终端执行adb shell run-as your.package.name ls /data/data/your.package.name/files/ # 查看是否有生成的 PDF 文件 exit # 然后模拟 URI 解析 adb shell am start -a android.intent.action.VIEW \ -d content://your.package.name.fileprovider/external_files_path/pet_report_1.pdf \ -t application/pdf若报错java.lang.SecurityException: Permission Denial说明grantUriPermission()未正确调用或authority字符串不匹配。5.3 验证 WorkManager 是否真在后台运行用 adb 触发立即执行避免等待 15 分钟周期用 adb 强制触发adb shell cmd jobscheduler run -u 0 -e your.package.name com.yourpackage.CheckDueWorker然后观察 Logcat 过滤CheckDueWorker应看到doWork()日志及sendNotification()调用。若无日志检查WorkManager.initialize()是否被多次调用会导致配置覆盖。关键技巧在CheckDueWorker.doWork()开头加入Log.d(WORKER, Start checking at System.currentTimeMillis());答辩时用 Logcat 实时展示后台任务执行痕迹——比口头解释“已配置”更有说服力。本文还有配套的精品资源点击获取