OCR模块识别
古法编程之下的一些AI应用模块
前言
这是之前的一个同事,独立写的一个模块,时间跨度非常久,从2024年11月30号开始,然后2025年1月15日他被裁员了。我之前没有想明白为什么在过年前夕这个节点,还要去裁员。后来他写的这部份代码交接给我去维护(重构),我才知道,这个人写的代码,放任何公司都足以让他被裁一万遍的。他被裁员,真的是不冤枉。
因此,这个模块的开发,比平时任何一个独立模块的开发,都还要艰辛,维护起来心很累。
因为之前的开发,真就是那种接一个需求就立刻写一点点代码的人,他没有对整个结构的拓扑图,没有完善的工程能力,你能想像得到吗?
最离谱的事情是:他有一个Dialog,想去回调一个数据给Activity使用,他的做法是
Activity的view-binding做成public的形式,然后让Dialog去调用
(parent as SpecialActivity).binding.tvInfo = "Callback"写的东西一塌糊涂,然后MVP、MVVM也不会使用,搞出来了一大堆的内存泄漏,而且线程框架不会使用(RxJava这些东西都没有).
世界真的是有趣,刷新了我对Android开发下限的认知。
流程图梳理
我花了一天的时间,读了一遍他留下来的那些代码,并且与iOS以及产品那边的同事去对了一下,弄清楚了这个业务的开发逻辑到底是怎么样的,从代码与业务的逻辑反向推演出它的基本流程图如下:
如上,此图为OCR识别Android部分的整体流程结构,具体可以再划分成为两个部分,一部分是基于MVVM的Android通用业务结构。此部分主要是通过Google与Android官方提供的思想,再去做部分删减,MVVM的核心思路,可以如下所示:
因此,按照OCR模式的业务需求,可以得到它的MVVM结构如下建立:

RxJava
原来的OCR模块完全使用Java + MVP + java.util.concurrent去实现的
使用Concurrent包的问题,数据链路极其不清晰,无法正确定位数据在每一个流程中具体做了什么操作,如果后期需要做额外判断,不方便加入。
如:在本次开发的过程中,中期需要加入一个ONNX是否纯文本算法的判断,按照原有的方式,最简单的方式只能是使用同步的方式加入判断,但衍生的问题是:这个算法本身也是需要耗时的,尽管AI算法组那边明确说这个算法的耗时很小,但IO操作的行为,都尽可能放进IO线程去运算,否则日积月累的IO操作放进UI线程运行,这会造成页面卡顿。
因此,使用了RxJava之后,这个进行就变成了,先用Single把图片判断一下,产生一个布尔值,然后图片信息加上布尔值,使用flatMap进入下一个运算中,如果Boolean值为true,即纯文本,那就只请求高精准解析,否则请求全部解析。
接口化的原因
统一使用无实现代码的接口去描述
Android开发普适的流程可以公开
核心代码不予展示
interface ExternalAPI {
// 判断纯文本
fun isPureText(bitmap: Bitmap): Boolean
// 访问所有API
fun fetchAllByBaiduAPI(bitmap: Bitmap): Single<Result>
// 访问高精准文本解析API
fun fetchOnlyGeneralByBaiduAPI(bitmap: Bitmap): Single<Result>
}
fun mainProcessing(bitmap: Bitmap) {
Single.create {
try {
it.onSuccess(ExternalAPI.isPureText(bitmap))
} catch (e: Exception) {
it.onError(e)
}
}.flatMap {
if (it) ExternalAPI.fetchOnlyGeneralByBaiduAPI(bitmap)
else ExternalAPI.fetchAllByBaiduAPI(bitmap)
}
// 其他后续行为
.otherBehavior()
}
当然,这里使用Kotlin Coroutines也是可以的,但受限于原有代码完全使用Java开发,其中涉及了View层,如果后期有ViewModel层的代码需要回到View层,中间可能要加入适配器去转化Kotlin Coroutines 与 Java的支持,这是额外的开发成本。
(语言之间的互相适配,Google与Jetbrains官方做到了直接调用,但是Coroutines与Java之间的适配,官方做得不够完善,还需要开发团队补充此部分的适配工具)
因此这里只去重改Presenter + Java-Concurrent-Package的内容为MVVM + RxJava
数据解析
原有网络层数据开发,使用新创建的Okhttp3发起HTTP请求,以execute + Body的形式直接获取API返回的数据,然后以JsonObject的方式存入SharedPreference中记录,并在之后的链路中直接获取,直接读取。
这个方案存在多个潜在问题:
- 每次需要使用额外数据的时候,后续开发极其维护过程中需要时刻翻看API文档。
如果使用Retrofit + GSON,对数据进行反序列化解析成为一个对象,实现装箱操作,在后续开发过程中,就无须去进行这个行为了。
在这一次的重构中,实现了:使用项目中统一规范定义的Okhttp3 + Retrofit2 + RxJava解析器,重构了网络层的内容。
- 数据之前的传递形式:放在了SharedPreference做持久化存储,然后其他地方再实际访问持久化存储去获取数据。(定位问题的时候,不方便定位)
思路:将实际的TotalUIModel,序列化传递或者直接Json化以String的形式传递
- JsonObject无序列化与反序列化行为,直接以JSONObject的方式去获取内容
这里是用一下,以JSOBObject的形式解析一下。这种写法,维护的时候,极难去定位到,具体是使用到什么样的一个参数。而且在JSONObject内嵌很深的时候,需要非常大的精力去判断出具体的层级在哪那里,可维护性极差。
之前的时候,这种代码在整个OCR模块随处可见,完全拆除这一块内容,需要极大的工作量。(需要验证会不会影响之前的业务逻辑)
网络层的后续优化点
当前的OCR-Request网络层的内容,属于iOS端与Android端对Baidu-OCR的API直接访问,使用类似于RxJava(Android)或者RxSwift(iOS)等线程框架+流式编程拼装数据。
如果此处,由后端新增一个接口,APP移动端只需要上传一张Bitmap或者File的图片给到服务器,经服务器接口内完成对Baidu-OCR的API访问以及数据拼装,然后以统一的形式返回给到APP端。
这样能和在数据层提升两端的逻辑一致性,目前党Android和iOS的请求逻辑存在一些出入的时候,需要两端频繁讨论方案。
逻辑的处理放在移动端,这并不是一个标准的开发模式。
2025.03.13记录
离3月底的上线还剩下几天的时间,这帮产品开始急急忙忙去这里反馈问题那里反馈问题的,他们现在开始紧张整个流程卡顿的问题了。
于是Push着后端的人,一定要修复这个问题。整个问题直接反馈到经理层去会议讨论,才把这个事情拍板决定下来,等于说我用RxJava东拼西凑出来的整个流程,是推翻了(当然从一开始就不应该这样去做的)
这个事情我在2月初接手之前写的那堆杂乱无章的代码时,整理出的这个业务流程,一眼看下来就不合理的。
不仅是之前的Android代码是极其不合理的,其实从整一个技术架构都是不合理的。

事实上,还可以在服务器端处理这张图片大小压缩问题的,不过,不是火烧眉毛的技术困境,他们才不会在乎的。