修改 Gradle 插件(Plugins)的下载地址(repositories)
Gradle 也可以用下面的方式声明使用的插件:
1 | // build.gradle |
其实是从 Gradle 官方的插件仓库 https://plugins.gradle.org/m2/ 下载的。
但是,众所周知的原因,某些地区会连不上,导致下载不到需要的插件,例如出现如下错误:
1 | * What went wrong: |
又或者,插件是不对外的,存在某个私有仓库的,该如何修改或者添加额外的私有仓库地址呢?
Gradle 也可以用下面的方式声明使用的插件:
1 | // build.gradle |
其实是从 Gradle 官方的插件仓库 https://plugins.gradle.org/m2/ 下载的。
但是,众所周知的原因,某些地区会连不上,导致下载不到需要的插件,例如出现如下错误:
1 | * What went wrong: |
又或者,插件是不对外的,存在某个私有仓库的,该如何修改或者添加额外的私有仓库地址呢?
书接上文,上回提到 B 站Android团队为了解决组件化后协作上的问题,已经采用了大仓(monorepo)
的方案来组织代码。
国内实践大仓的团队少之又少,更别提 Android 的大仓了,几乎没有来自其它团队的可借鉴经验。在这条路上,我们可以算作先行者。本文粗陋,文中所列思路不可能适用所有团队,仅给同样想实践Android 大仓的人些许启发。
首先回顾一下 Android 项目的组织方式。自从13年开始官方逐渐迁移到 Android Studio 做为 IDE 后,Android 项目的开发和编译就绑在 Gradle 上了。
一个标准的 Gradle 项目
结构如下所示:
1 | MyApp/ |
通常,会有多个Gradle Module存在:
1 | MyApp/ |
其中 settings.gradle
会注册所有的 Module
1 | include ':app', ':lib1', ':lib2' |
随业务的扩张,Module 数量会越来越多。遵循多数人实践过的组件化的思路,按业务分仓库存放便理所当然:
1 | android group/ |
每个仓库都是一个标准 Gradle 项目
,通过 publishing
插件将module 都上传 aar(或者jar)到 maven私服(如nexus)上,再在 MyApp/build.gradle
中以 maven 组件的形式依赖它们,最终打包成apk:
1 | repositories { |
此时的代码组织方式便是上文中所述的多仓库形态(可能许多团队正处于当前阶段)。
那么,如何既能快速搭建出适用于 Android 的大仓,又能不影响当前的团队协作流程,还要尽量避免迁移带来的开发效率降低?
早在2012年,B 站 Android APP 便已上线。当时开发者不过一人,而如今,业务线众多、隶属不同团队的Android 端开发人员数以百计。从单兵作战到百花争鸣,代码库的组织管理也随之经过数次的改革、演进。
2014年底,Android 端的常驻开发人员一只手也数的过来。业务发展迅速,为追求效率,方便管理,所有代码都在一个仓库中,甚至包括第三方的、开源的代码(个别用 git submodule 管理)。Clone下来导入 Eclipse 就可以开干。
到大约15年中旬,开始使用 Android Studio,得益于 Gradle 的项目管理理念,分出了多个 library module。外部依赖使用 maven。也是这一期间开始搭建了内网 maven 服务。
这期间代码库组织结构是:单仓库 + 个别 git submodule 。
这种组织方式好处显而易见:
但是,约莫到16年中,业务发展,新团队纷纷成立,招聘要求降低人员迅速膨胀。这种小而美的代码库已经不适用了,主要有以下缺点:
博客目前用的是Hexo,没有后端,为静态博客,评论一般用的第三方系统,如常用的 Disqus。但众所周知的原因,在“火星”上无法正常访问Disqus。
隆重推荐一个通过api实现评论的项目:DisqusJS
这里说一下它的配置技巧。
这里以很多人用的 Next 主题为例,其它类似。
修改_config.yml 中的disqus配置为:
1 | # for DisqusJS, https://github.com/SukkaW/DisqusJS |
具体含义见DisqusJS
修改 layout/_partials/head/custom-head.swig:
1 | {% if theme.disqus.enable %} |
在同时使用 Terminal 和 Android Studio 开发和编译 Android 项目时,跑 gradle 任务经常有一个提示,类似:
Starting a Gradle Daemon, 2 incompatible Daemons could not be reused, use --status for details
这表示 Gradle 又开启了一个新的 Daemon,已经存在的没有被重用。
执行gradlew --status
可以看到已经有3个Daemon 存活,对于我这台13年末产的老爷机来说是个严重的负担
1 | $ ./gradlew --status |
Gradle 官方给的解释是:
-Xmx1024m
而另一个 -Xmx2048m
;BUSY
状态,比如Android Studio 和 Terminal 同时跑 Gradle 任务。针对上述原因,可以得到以下方案:
对于同一个项目来说,使用同一个 JDK 执行 Gradle即可;如果是多个项目则另外需要指定使用同一个版本的gradle、并设置同样的 JVM 参数。
插件名:shrinker
项目地址: https://github.com/yrom/shrinker(其实很早之前就已经发布到github上了,不过无人问津→_→)
插件效果:与removeUnusedCode
同用可以起到最佳效果
这里有一个简单的测试项目,大部分类来自于依赖的support库,结果如下:
选项 | methods | fields | classes |
---|---|---|---|
原始项目 | 22164 | 14367 | 2563 |
应用shrinker 插件 | 21979 | 7805 | 2392 |
应用shrinker 并开启 removeUnusedCode | 11335 | 3302 | 1274 |
如果应用于依赖众多的大型项目则效果惊人。
ps. 其实已经在 b站的APP 上使用很久了,插件稳定、可靠且无副作用。
不论组件化或者说模块化,都有个核心思想:拆分,拆成一个又一个独立的Library。
举个例子
现一个 APP,它为了实践组件/模块化,拆分出了 common-ui ,business-a, business-b… 依赖关系如下图所示:
R 文件生成的大致流程如下图:
其中processReleaseResources
实际是调用的 aapt
工具来给每个依赖的Library都生成一个最终确定的R.java
。
可想而知,第一个问题: 拆分的Android Library越多,R 文件越多!
然而,Library 的 R 文件只会在最终编译成 APK 时确定字段常量值,输出 aar 时只有一个R.txt用于记录声明的资源。
假设 common-ui 声明了15个公共drawable资源,则生成的 R 文件中将有 15个相关的用于记录的字段,而且每个依赖于它的上层的library 生成的R都会有这15个同名的字段,如下图:
由此可得,第二个问题: 越底层的依赖所声明资源越多,最终生成的 R 文件越庞大 ! 因为这些字段没有得到有效内联,最终生成的DEX字段数就会严重超标。
为了解决组件/模块化进程中出现的上述两个问题,shrinker
应运而生。
虽然很可惜没有在Kotlin 刚发布的时候跟进学习,现在也还不晚~
官方很早在 Github 上发布了一个名为 android kotlin guides 的网站供 Android 开发者学习使用,但似乎没有中文版(至今没找到╮( ̄▽ ̄)╭)。
于是乎,自己动手丰衣足食。也就几个页面,将它们都翻译了一下~
链接:https://yrom.github.io/kotlin-guides/
项目:https://github.com/yrom/kotlin-guides/
又试着将一个小工具用kotlin 重写了一遍,用以实践kotlin:https://github.com/Bilibili/xpref
That’s ALL.
又闲来无事到植物园瞎拍了几张,也不枉这秋高气爽ヽ(゚∀゚*)ノ