Android健康APP开发实战:SQLite数据建模与真机调试闭环
2026/9/16 13:18:37 网站建设 项目流程

简介:本资源是一份面向高校移动应用开发课程学习者的Android实践项目完整交付包,聚焦体重管理类APP的设计与实现,适用于Java安卓基础扎实、正开展课程设计或毕业设计的学生。项目基于Eclipse+Android SDK开发环境,采用Java语言编写,涵盖Dalvik虚拟机机制、Activity生命周期管理、UI组件布局(XML)、数据持久化及APK打包全流程,具有典型教学示范价值。压缩包共459个文件,含30个核心Java源码、96个XML界面与配置文件、210个编译后Class字节码、46个PNG图标资源,以及APK安装包、答辩PPT、课程报告等交付物,整体18.31MB,结构完整、模块清晰,便于逐层理解项目架构与运行逻辑。目前已有350人学习下载,读者可直接导入Eclipse工程调试运行,获取从需求分析、编码实现到成果汇报的全链路参考方案,尤其适合掌握Android基础后进行实战巩固与课程作业复用。

1. 这不是又一个“记体重”的Demo,而是一套可落地的Android健康类APP开发闭环

很多初学者拿到“体重管理APP”课程资源时,第一反应是:不就是个EditText加个TextView?但实际拆开这个基于Eclipse时代的Android项目(含完整app-debug.apkgradlew.bat、多级.ap_资源索引文件及taskArtifacts.bin等构建缓存),你会发现它完整复现了2015–2017年主流健康类App的典型架构逻辑:本地SQLite存储体重/日期/体脂率三元组、按周/月聚合统计图表雏形、BMI计算与分级提示、以及关键的——数据持久化边界处理。它没用Room或LiveData,却用纯Java+SQLiteOpenHelper+SimpleCursorAdapter实现了零OOM的列表滚动;没接入云同步,但通过/android/data/com.mi.health/files/log/这类真实路径模拟了厂商健康生态的数据落盘习惯。适合正在从Java转向Kotlin、熟悉Android四大组件但缺乏完整项目交付经验的开发者,尤其适合作为理解Android SDK + Java SE + AVD三件套协同工作原理的“最小可运行教学锚点”。


2. 从Eclipse工程到APK:构建链路还原与关键文件作用解析

这个RAR包表面是课程作业,实则是Android 4.4–5.1时代典型构建流程的快照。它没有使用Gradle Wrapper的现代方式,而是保留了gradlew.bat与大量.bin缓存文件,说明项目曾经历从Ant向Gradle迁移的过渡阶段。我们需先厘清这些文件在真实开发中的角色,再逆向还原其构建逻辑。

2.1 构建产物文件族的定位与验证方法

项目中出现的resources-debug.ap_resources-debug.ir.ap_fileHashes.bin等并非冗余文件,而是Android Gradle Plugin 2.x版本(对应Android Studio 2.3–3.0)的中间产物。其中:

  • resources-debug.ap_是AAPT2编译后的资源归档(Android Package Archive),包含已编译的resources.arscres/目录结构,可通过aapt dump resources app-debug.apk比对验证;
  • resources-debug.ir.ap_是增量编译(Incremental Resource)产物,用于加速R.java生成,当修改strings.xml后仅重编此文件;
  • taskArtifacts.binfileSnapshots.bin属于Gradle Build Cache机制,记录任务输入输出哈希,执行./gradlew --build-cache assembleDebug可复现该结构。

提示:若在Android Studio中打开此项目报错“Cannot resolve symbol R”,优先删除build/目录并执行./gradlew clean assembleDebug,而非盲目刷新Project。

2.1.1 验证APK是否含调试符号与可调试性

使用adb install -r app-debug.apk安装后,执行以下命令确认调试能力:

# 检查APK是否启用debuggable标志 aapt dump badging app-debug.apk | grep "application-debuggable" # 输出应为: application-debuggable: true # 查看进程是否可被JDWP调试 adb shell ps | grep "com.weightmanager" # 正常应返回类似: u0_a123 12345 123 12345678 123456 ep_poll 00000000 S com.weightmanager

ps输出中无com.weightmanager进程,说明AndroidManifest.xml<application android:debuggable="true">未生效,需检查build.gradledebug { debuggable true }配置是否被覆盖。

2.2 Eclipse工程结构映射到现代Android Studio的关键转换点

该项目原始开发环境为Eclipse + ADT插件,需手动配置Android SDK LocationAVD ManagerJava Build Path。迁移到Android Studio时,核心转换不在代码,而在构建配置语义对齐

Eclipse ADT配置项Android Studio等效操作必须校验点
project.propertiesapp/build.gradlecompileSdkVersiontargetSdkVersion需匹配android-23(对应Android 6.0)否则RuntimePermissions相关代码会编译失败
libs/下jar包引用app/build.gradleimplementation files('libs/commons-lang3-3.4.jar')检查libs/内是否有android-support-v4.jar,若有则需替换为androidx.core:core:1.0.0
res/values/ids.xml定义ID现代Gradle默认禁用<item type="id">,需在build.gradle中添加android.useNewResourceProcessing=true否则R.id.xxx无法生成
2.2.1localClassSetAnalysis.bin揭示的依赖冲突隐患

该文件是Gradle 3.3+引入的类集分析缓存,用于检测重复类(如同时引入support-v4androidx)。若项目运行时报java.lang.NoClassDefFoundError: android.support.v4.app.Fragment,说明存在隐式依赖冲突。此时应执行:

./gradlew app:dependencies --configuration compileClasspath | grep -E "(support|androidx)"

输出中若同时出现com.android.support:support-v4:23.4.0androidx.core:core:1.0.0,证明存在二进制不兼容。解决方案是全局替换:

  1. gradle.properties中添加android.useAndroidX=trueandroid.enableJetifier=true
  2. 执行Refactor > Migrate to AndroidX(Android Studio菜单);
  3. 手动修正MainActivity.javaimport android.support.v7.app.AppCompatActivity;import androidx.appcompat.app.AppCompatActivity;

3. 核心功能实现:SQLite体重数据模型与BMI计算逻辑深度拆解

本APP的业务重心不在UI炫技,而在数据准确性本地计算鲁棒性。其DBHelper.javaBMIUtil.java两个类构成了整个应用的骨架,所有界面交互最终都服务于这两者的调用闭环。

3.1 体重数据表设计与事务安全写入

DBHelper继承自SQLiteOpenHelper,创建表语句如下(摘录自onCreate()):

// 注意:字段名严格匹配课程报告中"体重管理数据库ER图" String CREATE_TABLE = "CREATE TABLE weight_record (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "weight REAL NOT NULL, " // 单位:kg,精度保留1位小数 + "date TEXT NOT NULL, " // 格式:yyyy-MM-dd,非timestamp + "body_fat REAL DEFAULT 0.0, " // 体脂率%,范围0–100,允许为0表示未录入 + "bmi REAL DEFAULT 0.0, " // 实时计算值,非存储字段但设为DEFAULT便于查询 + "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP" + ")"; db.execSQL(CREATE_TABLE);

注意:date字段使用TEXT而非INTEGER存储时间戳,是为兼容Eclipse ADT时代SimpleDateFormat直接格式化需求,避免时区转换错误。现代开发应改用INTEGER并配合Calendar.getInstance().getTimeInMillis()

3.1.1 插入体重记录的原子性保障

课程代码中insertWeight()方法采用显式事务,这是关键设计亮点:

public long insertWeight(double weight, String date, double bodyFat) { SQLiteDatabase db = this.getWritableDatabase(); db.beginTransaction(); // 开启事务 try { ContentValues values = new ContentValues(); values.put("weight", weight); values.put("date", date); values.put("body_fat", bodyFat); // 关键:BMI在插入时实时计算并存入,避免每次查询都计算 double bmi = calculateBMI(weight); // 调用BMIUtil values.put("bmi", bmi); long result = db.insert("weight_record", null, values); db.setTransactionSuccessful(); // 仅当无异常才标记成功 return result; } finally { db.endTransaction(); // 无论成功失败都结束事务 } }

参数说明:

  • weight:必须为正数且≤300(防误触输入999kg),代码中应补充if (weight <= 0 || weight > 300) throw new IllegalArgumentException("Invalid weight");
  • date:需校验格式合法性,建议增加if (!date.matches("\\d{4}-\\d{2}-\\d{2}")) throw new IllegalArgumentException("Date format error");
  • body_fat:课程未强制校验,但生产环境应限制0 ≤ body_fat ≤ 100

3.2 BMI计算与健康等级映射的医学合规性

BMIUtil.java中的calculateBMI(double weight)看似简单,但隐含身高硬编码逻辑:

// 课程报告明确说明:用户身高固定为170cm(男性)或160cm(女性) // 代码中通过SharedPreferences读取gender后决定height public static double calculateBMI(double weight, String gender) { double height = "male".equals(gender) ? 1.70 : 1.60; // 单位:米 return Math.round((weight / (height * height)) * 10.0) / 10.0; // 保留1位小数 }
3.2.1 BMI分级标准与UI反馈策略

根据WHO标准,课程实现的分级映射表如下(摘录自getBMICategory()):

BMI范围分级名称TextView背景色(res/color/bmi_level.xml)建议文案
BMI < 18.5体重过轻#FF6B6B(红色系)“建议增加营养摄入”
18.5 ≤ BMI < 24正常范围#4ECDC4(青绿色)“保持当前健康习惯”
24 ≤ BMI < 28超重#FFE66D(黄色)“建议控制饮食并增加运动”
BMI ≥ 28肥胖#FF6B6B(同过轻,强化警示)“请咨询医生制定减重计划”

提示:课程PPT中强调,该颜色方案通过ColorStateList实现动态切换,而非硬编码setTextColor()。在activity_main.xml中,TextViewandroid:textColor="@color/bmi_level"即绑定此逻辑。


4. 真机调试与数据验证:ADB命令驱动的端到端测试流程

课程资源虽提供app-debug.apk,但真正掌握其价值需在真机上完成数据写入→查询→导出→验证全链路。以下是一套可直接复用的ADB操作序列,覆盖从安装到数据核验的每个环节。

4.1 安装与基础连通性验证

# 1. 启用开发者选项:设置→关于手机→连续点击"版本号"7次 # 2. 开启USB调试:设置→开发者选项→USB调试(打钩) # 3. 连接手机并授权调试(首次连接弹窗点"允许") adb devices -l # 应返回设备序列号及"device"状态 # 4. 安装APK(-r参数表示覆盖安装) adb install -r app-debug.apk # 5. 启动主Activity(注意包名与Activity名需从AndroidManifest.xml提取) adb shell am start -n "com.weightmanager/.MainActivity"
4.1.1 检查应用是否获得存储权限(Android 6.0+关键步骤)

即使targetSdkVersion=23,课程代码未实现运行时权限请求,因此需手动授予权限:

# 查询当前权限状态 adb shell pm list permissions com.weightmanager -g # 强制授予WRITE_EXTERNAL_STORAGE(否则数据无法写入/sdcard/) adb shell pm grant com.weightmanager android.permission.WRITE_EXTERNAL_STORAGE # 验证:进入应用后点击"保存记录",再执行下一步

4.2 SQLite数据库内容实时提取与校验

APP数据存储于私有目录,需通过run-as命令访问:

# 1. 进入应用私有数据库目录(路径由getDatabasePath()决定) adb shell run-as com.weightmanager ls -l databases/ # 2. 导出数据库文件到电脑(假设当前目录为项目根目录) adb shell run-as com.weightmanager cp /data/data/com.weightmanager/databases/weight.db /sdcard/weight.db adb pull /sdcard/weight.db ./weight.db # 3. 使用sqlite3命令行工具查询(macOS/Linux)或DB Browser for SQLite(Windows) sqlite3 weight.db "SELECT * FROM weight_record ORDER BY created_at DESC LIMIT 5;"

预期输出示例:

1|72.5|2023-10-01|18.2|25.1|2023-10-01 14:22:33 2|71.8|2023-09-24|17.9|24.9|2023-09-24 09:15:41 ...

提示:若run-as报错Security exception,说明设备已Root或SELinux处于enforcing模式。此时改用adb backup -f backup.ab com.weightmanager导出,再用dd if=backup.ab bs=24 skip=1 | zlib-flate -uncompress | tar -xvf -解包获取数据库。

4.3 模拟用户操作并捕获关键事件日志

课程答辩PPT强调“用户行为可追溯”,因此需验证日志是否写入指定路径:

# 1. 查看应用日志(过滤com.weightmanager标签) adb logcat -s "WeightManager" # 2. 模拟一次体重录入(触发onSaveClick) adb shell input tap 500 1200 # 坐标需根据屏幕分辨率调整,此处为示例 adb shell input text "70.5" adb shell input keyevent 66 # 回车确认 # 3. 检查/data/data/com.weightmanager/files/下是否有log文件 adb shell run-as com.weightmanager ls -l files/ # 正常应返回: -rw-rw---- u0_a123 u0_a123 1234 2023-10-01 14:30 weight_log_20231001.txt

5. 进阶技巧:将课程项目升级为可发布APK的5个关键改造点

课程资源的价值不仅在于复现,更在于作为跳板进行生产级改造。以下是基于当前代码结构,无需重写核心逻辑即可实施的5项关键升级,每项均附可直接粘贴的代码片段与验证指令。

5.1 为SQLite添加数据加密(SQLCipher集成)

原项目数据明文存储,存在隐私风险。使用SQLCipher可实现透明加密:

// app/build.gradle dependencies块中添加 implementation 'net.zetetic:android-database-sqlcipher:4.5.0' implementation 'androidx.sqlite:sqlite:2.2.0'
// DBHelper.java中修改getWritableDatabase() @Override public SQLiteDatabase getWritableDatabase() { // 密码从Keystore读取,此处简化为硬编码(生产环境必须用AndroidKeyStore) String password = "weight2023!"; return SQLiteDatabase.openOrCreateDatabase( getDatabasePath("weight.db"), new SQLiteDatabaseHook() { public void preKey(SQLiteDatabase database) {} public void postKey(SQLiteDatabase database) { database.rawExecSQL("PRAGMA cipher_page_size = 1024;"); database.rawExecSQL("PRAGMA kdf_iter = 64000;"); } }, password, null ); }

验证指令:

# 解密后查看数据(需安装sqlcipher命令行工具) sqlcipher weight.db sqlite> PRAGMA key = 'weight2023!'; sqlite> SELECT * FROM weight_record;

5.2 实现周/月统计图表(MPAndroidChart轻量集成)

课程报告提到“支持趋势分析”,但未实现图表。添加MPAndroidChart仅需3步:

// app/build.gradle implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
<!-- activity_main.xml中添加 --> <com.github.mikephil.charting.charts.LineChart android:id="@+id/chart" android:layout_width="match_parent" android:layout_height="250dp" />
// MainActivity.java onCreate()末尾添加 LineChart chart = findViewById(R.id.chart); ArrayList<Entry> entries = new ArrayList<>(); Cursor cursor = dbHelper.getWeeklyWeightData(); // 自定义方法,返回近7天数据 while (cursor.moveToNext()) { float weight = cursor.getFloat(cursor.getColumnIndex("weight")); int day = cursor.getInt(cursor.getColumnIndex("day_of_week")); // 1=周一...7=周日 entries.add(new Entry(day, weight)); } LineDataSet dataSet = new LineDataSet(entries, "本周体重"); dataSet.setDrawValues(false); chart.setData(new LineData(dataSet)); chart.invalidate(); // 刷新图表

提示:getWeeklyWeightData()需在DBHelper中编写,使用SELECT weight, strftime('%w', date) as day_of_week FROM ... WHERE date >= date('now', '-6 days')

5.3 添加数据导出CSV功能(适配Android 11+分区存储)

课程未考虑数据导出,但用户有备份需求。适配Scoped Storage需修改:

// ExportUtil.java public static void exportToCSV(Context context) { File exportDir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS); if (!exportDir.exists()) exportDir.mkdirs(); File file = new File(exportDir, "weight_export_" + System.currentTimeMillis() + ".csv"); try (FileWriter writer = new FileWriter(file)) { writer.append("日期,体重(kg),体脂率(%),BMI\n"); Cursor cursor = dbHelper.getAllRecords(); while (cursor.moveToNext()) { String date = cursor.getString(cursor.getColumnIndex("date")); double weight = cursor.getDouble(cursor.getColumnIndex("weight")); double fat = cursor.getDouble(cursor.getColumnIndex("body_fat")); double bmi = cursor.getDouble(cursor.getColumnIndex("bmi")); writer.append(String.format("%s,%.1f,%.1f,%.1f\n", date, weight, fat, bmi)); } Toast.makeText(context, "导出完成:" + file.getAbsolutePath(), Toast.LENGTH_LONG).show(); } catch (IOException e) { Log.e("Export", "CSV write failed", e); } }

验证路径:

adb shell ls -l /sdcard/Android/data/com.weightmanager/files/Documents/ # 应看到weight_export_*.csv文件

5.4 优化启动速度:Application类预加载数据库

当前MainActivity中每次onCreate()都新建DBHelper,导致冷启动延迟。改为Application单例:

// WeightApplication.java public class WeightApplication extends Application { private static DBHelper dbHelper; @Override public void onCreate() { super.onCreate(); dbHelper = new DBHelper(this); // 预热:执行一次轻量查询,触发数据库初始化 dbHelper.getReadableDatabase().rawQuery("SELECT COUNT(*) FROM sqlite_master", null).close(); } public static DBHelper getDBHelper() { return dbHelper; } }
<!-- AndroidManifest.xml <application>标签添加 --> <application android:name=".WeightApplication" ... >

验证效果:

adb shell am force-stop com.weightmanager adb shell am start -W com.weightmanager/.MainActivity # 查看Logcat中"WaitTime"值,优化后应降低150ms以上

5.5 添加基础崩溃监控(ACRA轻量集成)

课程未处理异常,生产环境必须捕获Crash:

implementation 'ch.acra:acra-core:5.8.4' implementation 'ch.acra:acra-http:5.8.4'
// MainActivity.java onCreate()首行添加 CoreConfigurationBuilder builder = new CoreConfigurationBuilder(this) .setBuildConfigClass(BuildConfig.class) .setReportFormat(StringFormat.KEY_VALUE_LIST); ACRA.init(this, builder);
<!-- AndroidManifest.xml <application>内添加 --> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>

验证方法:
MainActivity中添加测试按钮,点击时执行throw new RuntimeException("Test crash");,观察Logcat是否输出ACRA is enabled及上报URL。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询