Android花卉识别源码解析:图片压缩、OkHttp上传与接口调用全链路

Android花卉识别源码解析:图片压缩、OkHttp上传与接口调用全链路 简介这是一款花卉识别Android应用的完整源码附带项目说明文档识别功能基于植物研究所与百度识图合作的“看图识花”服务。项目覆盖Android客户端完整工程结构从界面布局、图片选取到网络请求、识别结果展示均有实现适合计算机、电子信息等专业学生作为课程设计、期末大作业或毕业设计参考也适合学习Android网络请求与第三方API接入的开发者研读。压缩包共34个文件除Java源码和XML布局配置外还包含PNG图片资源、TTF字体、工程配置与项目说明文件、android-support-v4.jar依赖库及README说明整体仅1.51MB。源码中src目录存放核心逻辑res目录包含多分辨率适配图片、菜单和动画资源assets与fonts目录提供静态资源可直接导入Android工程运行调试。目前已有236人学习可帮助读者快速上手花卉识别场景并了解百度识图接口的调用方式与完整实现思路。1. 看图识花源码背后是完整识别链路不是模型代码拿到标题时不少人会先入为主以为这套花卉识别 Android 源码里装着 TensorFlow Lite 或者 PyTorch Mobile 之类的本地模型。实际拆开看项目说明会发现识别的判断来自研究所与百度识图合作的“看图识花”接口Android 端只负责拍照、压缩、上传、解析和展示真正的分类推理发生在服务端。这个分工决定了源码的学习价值不在算法而在一条完整的“取景 → 传输 → 解析 → 兜底”链路上。对 5 年以上 Android 工程师来说识别逻辑本身没有太多黑魔法真正值得抠的是图片压缩策略、HTTP 连接超时设置、JSON 解析容错和状态码排查。对刚接触 Android 图像类应用的人来说这个项目又是少见的“接口真实可用、返回结构完整”的练手样本。整篇文章会顺着源码里的实际调用链讲清楚为什么这样设计、参数在哪里改、跑不起来时看什么。2. 花卉识别 App 的工程骨架图库选图、Bitmap 压缩与 HTTP 上传2.1 先从项目说明里确认识别链路一般的项目说明文档会给出数据流向这个看图识花项目也不意外。常见做法是这样一条链路用户在 MainActivity 里点击“拍照”或“从相册选择”拿到图片 URI 后先做压缩再以 Base64 或表单形式 POST 到百度识图的识别接口服务端返回 JSON 后通过 Gson 解析成 PlantResult 对象最后在 RecyclerView 或 ScrollView 里展示植物名称、相似度和百科链接。activity_main.xml ├── Button触发拍照/选图 ├── ImageView预览待识别的花卉照片 └── TextView ListView展示识别结果 MainActivity.java ├── 权限申请CAMERA / READ_EXTERNAL_STORAGE ├── 拍照回调onActivityResult 接收缩略图 ├── 上传逻辑Bitmap 压缩 - MultipartBody 上传 └── 结果回调onResponse 里解析 JSON这个结构在绝大多数 Android 图像识别项目里都能复用。把“看图识花”换成 OCR、人脸检测或者商品识别代码改动的只是请求地址和返回字段名骨架完全不需要动。所以源码真正的入门价值是让你先理解“客户端只做搬运工”这件事。2.2 AndroidManifest 配置权限、明文传输与 FileProviderAndroid 9 开始默认禁止明文 HTTP 流量而公网上的识别接口很多还停留在 http 协议。项目说明里通常会直接允许明文传输或者用 NetworkSecurityConfig 对指定域名放开。开发阶段我一般选择后者配置精确不至于把整个应用的安全策略压到最低。manifest xmlns:androidhttp://schemas.android.com/apk/res/android packagecom.example.plantrecognize uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.CAMERA / uses-permission android:nameandroid.permission.READ_EXTERNAL_STORAGE android:maxSdkVersion32 / application android:label看图识花 android:networkSecurityConfigxml/network_security_config ... /application /manifestnetwork_security_config.xml 里只对识别接口的域名放开明文network-security-config domain-config cleartextTrafficPermittedtrue domain includeSubdomainstrueaip.baidubce.com/domain /domain-config /network-security-config注意 READ_EXTERNAL_STORAGE 在 Android 13 以上已经被细粒度媒体权限替代如果项目说明的 targetSdk 还停在 30 以下升级后必须补 READ_MEDIA_IMAGES 权限。另一个常见的坑是拍照返回的缩略图来自相机应用弱光下可能只有几百 KB但分辨率却可能达到 4000×3000后续压缩时必须统一处理。2.3 相册选图返回的 content:// 要落盘再上传从系统相册选图时Intent 返回的 data.getData() 经常是 content:// 开头的内容提供器 URI而不是真实文件路径。很多人在这一步直接把 URI 字符串拼进请求参数结果服务端收到一个无法读取的路径返回 400 或空结果。正确做法是先通过 ContentResolver 打开 InputStream复制到一个临时文件再对这个临时文件做压缩。public File uriToFile(Uri uri) { InputStream inputStream null; FileOutputStream outputStream null; try { inputStream getContentResolver().openInputStream(uri); File temp new File(getCacheDir(), source_ System.currentTimeMillis() .jpg); outputStream new FileOutputStream(temp); byte[] buffer new byte[8192]; int len; while ((len inputStream.read(buffer)) ! -1) { outputStream.write(buffer, 0, len); } return temp; } catch (IOException e) { Log.e(TAG, uri转文件失败, e); return null; } finally { closeQuietly(inputStream); closeQuietly(outputStream); } }这段代码解决的是“content:// 协议源与识别接口不兼容”的问题先把图片从第三方内容提供器搬到自建应用目录后续无论是压缩、上传还是再次读取都拿到稳定的文件引用。参数上缓存目录 getCacheDir() 比 getFilesDir() 更合适系统空间不足时自动清理不需要手动维护。2.4 上传图片的 OkHttp 写法与超时参数看图识花项目最可能选择的网络库是 OkHttp 3.14 或 4.x 版本配合 Retrofit 或裸调用。下面这段是核心上传逻辑public void upload(File imageFile, ResultCallback callback) { RequestBody fileBody RequestBody.create(MediaType.parse(image/jpeg), imageFile); MultipartBody requestBody new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart(image, imageFile.getName(), fileBody) .addFormDataPart(top_num, 5) .build(); Request request new Request.Builder() .url(API_URL) .post(requestBody) .build(); client.newCall(request).enqueue(new Callback() { Override public void onFailure(Call call, IOException e) { callback.onError(网络请求失败: e.getMessage()); } Override public void onResponse(Call call, Response response) throws IOException { String json response.body().string(); callback.onSuccess(json); } }); }这里的 top_num 参数代表返回候选花卉的数量调成 5 比默认的 1 个更实用。用户在页面上能看到“相似度排名”而不是只给一个孤零零的名称。OkHttpClient 的构建建议单独抽出来设置连接超时 10 秒、读取超时 30 秒——识别接口在后端还要跑模型推理30 秒的读取超时并不过分。字段建议值说明connectTimeout10s国内移动网络下握手通常 2-3 秒readTimeout30s服务器推理网络延迟预留writeTimeout15s压缩后图片通常 200-500KBtop_num5返回前 5 个候选结果image 质量85%保证花萼纹理可辨识体积可控3. Android Studio 导入看图识花源码的三个关键配置与跑通步骤3.1 Gradle 与 SDK 版本对齐这类带项目说明的源码包最常见的问题是 Gradle 版本和本地 Android Studio 不匹配。解压后先不要急着 Build打开根目录的 build.gradle 确认 Gradle 插件版本。老项目经常停留在大版本 3 或 4 的阶段而 Android Studio 最新稳定版的 AGP 已经到 8.x强行打开会报 “Minimum supported Gradle version is 8.x” 的错误。常见做法是手动升级 AGP 到当前 Studio 支持的版本同时把 com.android.tools.build:gradle 与 gradle-wrapper.properties 里的 distributionUrl 对应起来。下面是一个能在 Android Studio Koala 及更高版本直接跑通的配置// 根目录 build.gradle buildscript { repositories { google() mavenCentral() maven { url https://maven.aliyun.com/repository/public } } dependencies { classpath com.android.tools.build:gradle:8.1.4 } } // gradle-wrapper.properties distributionUrlhttps\://services.gradle.org/distributions/gradle-8.0-bin.zip使用阿里云镜像的主要原因是国内拉取 Maven Central 依赖不稳定尤其在小带宽环境下一卡就是十几分钟。把 google() 和 mavenCentral() 保留在仓库列表里同时加上阿里云镜像前置依赖解析速度会明显提升。升级 AGP 后如果项目里还残留 compile 关键字需要统一替换成 implementationprovided 改为 compileOnly。3.2 真机联调时 USB 调试与日志过滤模拟器上跑这个项目会遇到一个硬伤模拟器的相机拍不了真实花卉而且 GPU 渲染出来的图片噪点明显。推荐直接用真机调试开启开发者选项里的 USB 调试后用 adb 执行反向端口映射和进程启动adb devices adb shell am start -n com.example.plantrecognize/.MainActivity adb logcat -s PlantRecog:D第三条命令的 PlantRecog 是代码里自定义的日志 Tag。看图识花项目如果沿用源码里的日志命名直接过滤这个 Tag 就能看到上传开始、HTTP 状态码、返回 JSON 等关键节点。一个实用的排查步骤是先用 postman 或 curl 单独调一次接口确认 API Key 有效再回到 App 里点击识别。很多 401/403 问题根本不涉及 Android 代码纯粹是 Key 配错或额度耗尽。3.3 图片压缩参数对识别率的影响看图识花接口对图片尺寸和体积有隐性限制超出会直接返回参数错误。源码里一般会提供压缩工具类我见过的典型实现是限制最长边为 1080pxJPEG 质量为 85。下面这段代码可以直接替换进项目里public static File compress(File source, int maxWidth, int quality) throws IOException { BitmapFactory.Options options new BitmapFactory.Options(); options.inJustDecodeBounds true; BitmapFactory.decodeFile(source.getAbsolutePath(), options); int sampleSize 1; while (options.outWidth / sampleSize / 2 maxWidth || options.outHeight / sampleSize / 2 maxWidth) { sampleSize * 2; } Bitmap bitmap BitmapFactory.decodeFile(source.getAbsolutePath(), new BitmapFactory.Options().apply { inSampleSize sampleSize }); File out new File(source.getParent(), compressed_ System.currentTimeMillis() .jpg); FileOutputStream fos new FileOutputStream(out); bitmap.compress(Bitmap.CompressFormat.JPEG, quality, fos); fos.flush(); fos.close(); return out; }inSampleSize 采用 2 的幂次缩放避免 decode 阶段内存峰值过高。quality 参数不是越大越好95 和 85 视觉差异极低但文件体积可能从 300KB 涨到 700KB多出来的体积对识别结果几乎没有帮助反而拖慢上传。另一个容易被忽略的点压缩后如果 Bitmap 不回收连续识别 20 张以上就会出现内存抖动建议在 compress 后立即调用 bitmap.recycle()。3.4 跑通识别流程后的第一个验证点按上述配置把源码跑起来后建议先拍一张叶片干净、单朵花占画面 60% 以上的照片做冒烟测试。此时日志里应该出现类似 result.code 0 的记录后面的植物名称字段不为空。如果返回为空首先检查图片是不是被压缩到长边小于 400px太小的图会让特征提取直接崩溃。日志关键词含义可能原因result.code 0识别成功正常error_code 17日配额耗尽换 API Keyerror_code 4请求超限降低并发频率null result未识别出植物图片过小/背景复杂4. 看图识花识别结果不准确的 4 个调优点与兜底逻辑4.1 API Key 与鉴权字段的存放位置源码里的 API Key 一般会直接硬编码在 Constants 类中这是教学项目的常态但不代表生产环境应该这么做。至少应该把它挪到 BuildConfig 字段里或者用一个 properties 文件在构建时注入。识别接口的调用量直接和账号余额挂钩Key 一旦被反编译拿走损失是实打实的。buildTypes { debug { buildConfigField String, API_KEY, \your_debug_key\ } release { buildConfigField String, API_KEY, \your_release_key\ } }代码里通过 BuildConfig.API_KEY 引用既保证 debug 和 release 可以配置不同的 Key也避免了源码仓库里明文暴露。另一个细节是 token 过期刷新百度识图接口的 access_token 有效期通常是 30 天项目说明若没有提到缓存逻辑需要自己实现一个本地 token 缓存避免每次启动都重新鉴权。4.2 回报 top_num 结果后展示相似度列表大部分教学源码只会取识别结果的第一名但看图识花接口返回的是数组形式。如果在 MainActivity 里只拿 result[0].name等于放弃了接口里最有价值的置信度参考。把返回列表完整解析并展示出来才能直观看出“最像玫瑰但也有 30% 相似度指向月季”这类信息。public class PlantResult { private String log_id; private int result_num; private ListPlant result; public static class Plant { private String name; private double score; private String baikeUrl; private String baikeImageUrl; } }解析后按 score 降序排列第一条结果保留为“最佳答案”其余放进列表下方。置信度低于 0.5 的候选结果建议置灰处理因为 0.5 以下基本等于模型也没把握展示出来只会让用户更困惑。4.3 拍照取景与图片裁剪对识别率的影响源码能做的调优到了极致识别准确率的瓶颈大概率在取景阶段。看图识花接口对前景/背景区分很敏感如果拍摄时花朵只占画面 20%背景又包含大量绿色叶片模型很容易把整体场景误判为绿植类别。项目说明如果只按常规相机逻辑实现那这里就是必须手动补的“拍摄引导”。一个不需要大改的做法是在拍照后进入裁剪界面强制用户把花朵区域裁到画面 60% 以上再发送。系统自带的裁剪 Intent 可以临时用更好的方案是集成 uCrop 之类的开源库。裁剪后的图片传输到服务端特征是清晰的单目标识别准确率通常能提升 10 到 15 个百分点。4.4 识别失败与空结果的兜底方案即使做了所有调优仍然可能遇到接口返回空数组的情况——反光、逆光、花瓣太密集都会导致模型失手。此时 App 端不能直接展示一个空白页面正确的做法是准备二级确认机制。识别为空时自动降低质量参数重新压缩例如从 quality85 下调到 70再重试一次两次都失败就明确提示用户“当前光线或角度不理想请近景重拍”。private void retryWithLowerQuality(File original) { try { Bitmap src BitmapFactory.decodeFile(original.getAbsolutePath()); ByteArrayOutputStream baos new ByteArrayOutputStream(); src.compress(Bitmap.CompressFormat.JPEG, 70, baos); File retryFile new File(getCacheDir(), retry_ System.currentTimeMillis() .jpg); FileOutputStream fos new FileOutputStream(retryFile); fos.write(baos.toByteArray()); fos.close(); upload(retryFile, this.callback); } catch (IOException e) { Log.e(TAG, 重试压缩失败, e); } }这里的降质量策略不是盲目压缩体积而是通过降低细节保留量逼迫模型聚焦大的形状特征。对部分特定花卉反而有奇效。如果是接口本身超时则提示“请稍后重试”不要把底层异常直接抛给用户。5. 用日志与可信度分级验证花卉识别结果源码跑通只是第一步真正判断识别结果靠不靠谱需要在 App 里加上可信度分级。接口返回的 score 字段已经在 0 到 1 之间可以直接映射成“高置信 / 中置信 / 低置信”三档。高置信的结果名称用绿色标记中置信用黄色低置信直接灰显并附带“仅供参考”的提示。private int getLevelColor(double score) { if (score 0.8) { return getColor(R.color.high_conf); } else if (score 0.5) { return getColor(R.color.mid_conf); } else { return getColor(R.color.low_conf); } }分级标准推荐放在项目说明末尾的“验收标准”章节里让其他人也能看懂评分逻辑。实际使用中0.8 以上的结果基本可以放心展示0.5 到 0.8 之间存在明显的种属分辨难度例如“蔷薇科”下特征相近的品种0.5 以下建议直接引导重拍。日志埋点的位置要有讲究。上传开始前记录一次图片大小和耗时onResponse 后记录一次返回耗时和 result_num。这样在真机上反复实验不同压缩参数时可以直接用 logcat 的输出做对比不需要在界面里打桩。adb logcat -s PlantRecog:D | grep upload\|recognize输出会包含图片尺寸、请求耗时、返回数量三组数据配合时间戳就能算出每张图的平均处理时间。如果单张请求耗时超过 10 秒先检查移动网络环境再用 postman 测试同一张图的接口纯净耗时定位慢在网络还是后端推理。最后把耗时、相似度和选中页签一起带出能让后续调优直接对着采样说话。本文还有配套的精品资源点击获取