返回 Android
Android
11 分钟阅读

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以及产品那边的同事去对了一下,弄清楚了这个业务的开发逻辑到底是怎么样的,从代码与业务的逻辑反向推演出它的基本流程图如下: 流程图 1 如上,此图为OCR识别Android部分的整体流程结构,具体可以再划分成为两个部分,一部分是基于MVVM的Android通用业务结构。此部分主要是通过Google与Android官方提供的思想,再去做部分删减,MVVM的核心思路,可以如下所示: 流程图 2 因此,按照OCR模式的业务需求,可以得到它的MVVM结构如下建立: 流程图 3

RxJava

原来的OCR模块完全使用Java + MVP + java.util.concurrent去实现的

使用Concurrent包的问题,数据链路极其不清晰,无法正确定位数据在每一个流程中具体做了什么操作,如果后期需要做额外判断,不方便加入。

如:在本次开发的过程中,中期需要加入一个ONNX是否纯文本算法的判断,按照原有的方式,最简单的方式只能是使用同步的方式加入判断,但衍生的问题是:这个算法本身也是需要耗时的,尽管AI算法组那边明确说这个算法的耗时很小,但IO操作的行为,都尽可能放进IO线程去运算,否则日积月累的IO操作放进UI线程运行,这会造成页面卡顿。

因此,使用了RxJava之后,这个进行就变成了,先用Single把图片判断一下,产生一个布尔值,然后图片信息加上布尔值,使用flatMap进入下一个运算中,如果Boolean值为true,即纯文本,那就只请求高精准解析,否则请求全部解析。

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内嵌很深的时候,需要非常大的精力去判断出具体的层级在哪那里,可维护性极差。

流程图 4 之前的时候,这种代码在整个OCR模块随处可见,完全拆除这一块内容,需要极大的工作量。(需要验证会不会影响之前的业务逻辑)

网络层的后续优化点

当前的OCR-Request网络层的内容,属于iOS端与Android端对Baidu-OCR的API直接访问,使用类似于RxJava(Android)或者RxSwift(iOS)等线程框架+流式编程拼装数据。 流程图 5 如果此处,由后端新增一个接口,APP移动端只需要上传一张Bitmap或者File的图片给到服务器,经服务器接口内完成对Baidu-OCR的API访问以及数据拼装,然后以统一的形式返回给到APP端。

这样能和在数据层提升两端的逻辑一致性,目前党Android和iOS的请求逻辑存在一些出入的时候,需要两端频繁讨论方案。

逻辑的处理放在移动端,这并不是一个标准的开发模式。

2025.03.13记录

离3月底的上线还剩下几天的时间,这帮产品开始急急忙忙去这里反馈问题那里反馈问题的,他们现在开始紧张整个流程卡顿的问题了。

于是Push着后端的人,一定要修复这个问题。整个问题直接反馈到经理层去会议讨论,才把这个事情拍板决定下来,等于说我用RxJava东拼西凑出来的整个流程,是推翻了(当然从一开始就不应该这样去做的)

这个事情我在2月初接手之前写的那堆杂乱无章的代码时,整理出的这个业务流程,一眼看下来就不合理的。

不仅是之前的Android代码是极其不合理的,其实从整一个技术架构都是不合理的。

流程图 6

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