在我们的代码中,十几个业务线的下方都依赖一个公用的base库,base库的东西很多,包含Push、Share、Util、Web、SSO、Pay和部分共用的业务逻辑。我们打算拆分Base,每个模块弄成一个代码仓库,方便模块复用,解开部分耦合。
代码拆分出去比较简单,本来模块基本上都是按照package区分的,拿出去即可,但资源就不好拆分了。如果代码拆出去,用到的资源也拿出去,可能会导致多个库用了名称相同的资源,由于资源的合并机制,同样的名字最后只有一份资源会被打到APK中,如果有一个库更改了资源内容,可能就会导致APP运行时失败。比如A库和B库都复制了一份c.layout,B改了自己的c.layout,添加了一个控件,在代码中会用findViewById去获取控件,自己编译能通过,但A的资源优先级更高,最终APK中只保留了A的c.layout,运行时B就会找不到添加的控件,代码崩溃。关于资源合并的优先级,请看 官方文档 。官方文档是个宝啊,谁看谁知道。
资源拆分有两种方案:
资源复制多份并且修改资源名称,比如加前缀。
这种做法要改动的源代码太多,并且资源冗余,会增加APK大小。
资源保留一份沉在下层。
保留一份资源,合情合理,关键是把资源放在哪里?都放在Base库中,代码肯定可以跑的通,但肯定不是合理的。资源应该谁用谁负责,和代码在一个仓库里,如果多个库用,就应该放在这多个库更上层的公共依赖。
我们决定采用第二种方案,但Base拆库的过程是同步进行的,代码拆出去再建立依赖关系,在没有依赖关系的前提下,根本不知道Base中的资源最后能放到哪个仓库里。所以我们分库的时候,首先只拆分代码,拆完打成aar,所有仓库都能编译通过就可以了。最后,资源拆分的重担就落到了我的肩上。
资源的使用情况有多种:
没有库使用
应用大了,这种问题在所难免,直接删掉吧
一个库使用
直接放在那一个库就可以了
多个库使用
这种情况比较复杂,我说过,如果多个库用,就应该放在这多个库更高层的公共依赖。什么意思呢,比如A,B都依赖C,C依赖D,这时候你应该放在哪里?C里面比较合适,C、D都是A和B的公共依赖,C在更上层。
但还有一个问题,A依赖B,B依赖D,C依赖D,D依赖Base,如果资源在A和C里都用到了,这时候应该把资源放到哪里?A和C都间接依赖D,但如果B以后不依赖D了,那A也不会依赖到D,资源放在D里也不好。所以我们要修正上面说的公共依赖为公共直接依赖。像刚才这种情况没有公共直接依赖的,那就暂时放到Base里面吧,方正放哪里也不合适,Base最后内容很少改动很小,如果后面嫌更改Base比较烦,那就复制一份改名到上层也可以,这就是所谓的历史遗留问题吧。
各个代码仓库最终打成AAR,在build.gradle中配置依赖关系,由于Gradle支持依赖传递,这种依赖都是间接依赖,需要自己去找直接依赖。这个依赖主要是找代码之间的依赖,资源调用代码的依赖可以暂时不用考虑。
你会发现,在Android开发中,可以借鉴Java Web开发的很多东西,后端帮我们踩坑这么多年,已经积累了丰富的经验。
找到了一个库, Tattletale ,可以查找Jar包之间的直接依赖关系。我们把所有的Jar包放在一起,用这个工具扫一下就可以了,所有的Jar包肯定也包含第三方库的代码,这些Jar包都在主工程的 build/intermediates/exploded-aar
中,遍历.jar结尾的都放到一个文件夹就可以了。
Tattletale提供的结果是HTML页面,需要我们自己解析,用了 BeautifulSoup 库。之前写Pyhton很多都是按照Java的思路来写,去查对应的语法,没有感觉到Python的简洁,最近系统的看了一下Python的一些知识,我靠,写起来真是痛快,豪爽豪爽。
AAR的依赖关系是一个有向无环图,我们要在图中找到两个结点的更上公共依赖,怎么搞?我想到了拓扑排序,不学算法好多年,竟然还能想到这个东东。拓扑排序可以使得依赖关系有序,拓扑序列中,后面的不会依赖前面。比如,一个资源在多个库中使用,那就可以找到这几个库在拓扑序列中哪个更靠后,从这个库开始逐个检查是否同时被这几个库依赖,找到就可以停止遍历了,这个库就是更上层的公共依赖,遍历完还找不到,那就放Base里吧。
把依赖绘制成图,看起来比较直观。发现了 Graphviz ,非常简单的语句描述,自动绘制成图,简单易用。
聊了那么多,怎么查资源被哪些库所使用的呢?
由于我们分库后,有几十个代码仓库,使用aar依赖,使用AS是不方便的。有同事之前搞ROM开发,了解到Android源代码是用 repo 工具管理的,就果断的引用到我们的项目里。repo在分库的时候用处蛮大的,可以同时操纵多个代码仓库,平常开发的话,就是全局搜索源代码比较方便,期待AS能够早日支持搜索AAR关联的源代码。