心脏彩超主要检查什么| 有福是什么意思| 打蛋白针有什么作用| 拖是什么意思| 焦亚硫酸钠是什么| 婴儿什么时候可以睡枕头| 什么花没有叶子| 前列腺在什么地方| 内热是什么原因引起的| 肾阴阳两虚吃什么| 去火喝什么茶| 伏特加是什么意思| 吸顶灯什么牌子的好| 立flag什么意思| 睡觉腿麻是什么原因引起| hpv感染有什么症状| 保肝降酶药首选什么药| 喉咙长期有痰是什么原因| 杀破狼是什么意思| 喝酒后头疼吃什么药| 血小板是什么意思| 忧郁的意思是什么| 乌龟喜欢吃什么食物| 什么是心率| 长脸适合什么发型| 腺样体是什么意思| 高血压吃什么好降压快| 核能是什么| 男孩适合学什么专业| 心超是检查什么的| 肾虚是什么原因造成的| suki是什么意思| 外阴湿疹用什么药| 男性尿路感染有什么症状| 过敏有什么症状表现| 什么是疱疹怎么得的| 骨骼肌是什么| 血漏是什么病| 支原体感染有什么症状| 吃什么可以瘦肚子| 橡皮擦是什么材料做的| 扑感敏又叫什么名字| 提拔是什么意思| 褥疮用什么药最好| 就是什么意思| 莅临什么意思| 新生儿c反应蛋白高说明什么| 小狗肚子里有虫子吃什么药| 戴银镯子变黑是什么原因| 春天什么花会开| 什么程度才需要做胃镜| 梦见塌方是什么预兆| 单三是什么| 脑白质脱髓鞘是什么意思| 高考450分能上什么学校| 什么然泪下| 人中龙凤是什么意思| 福祉是什么意思| 大惊小怪是什么意思| 女生两个月没来月经是什么原因| ochirly是什么品牌| 温州冬至吃什么| rbc是什么意思| 弯弯是什么意思| sanyo是什么牌子| 桔子树用什么肥料最好| 合疗和医保有什么区别| 血脂稠吃什么药| 嘌呤高会引起什么症状| 殖民地是什么意思| 炎帝叫什么| 基因突变什么意思| pyq是什么意思| 暗代表什么生肖| 4月26日什么星座| 高铁为什么会晚点| 肚脐眼周围痛什么原因| 喝什么茶| 朱元璋长什么样| 每天放很多屁是什么原因| 态度是什么| 星辰大海是什么意思| 全身是宝的动物是什么生肖| 新生儿干呕是什么原因| 红米是什么米| 乐哉是什么意思| dna是什么意思| 山药与什么食物相克| 美国为什么要打伊拉克| 见人说人话见鬼说鬼话是什么意思| 肝血不足吃什么补最快| 什么时候吃苹果最好| 蝙蝠为什么倒挂着睡觉| 20岁长白头发是什么原因造成的| 蟋蟀吃什么东西| 肾阳虚喝什么泡水最好| 门第是什么意思| 猪八戒的老婆叫什么| 吃大虾不能吃什么| 黄斑前膜是什么病| 中秋节送什么水果好| 防微杜渐的意思是什么| 小孩眼屎多是什么原因引起的| 二郎神是什么动物| 大便一粒一粒的是什么原因| 舌头发白有齿痕是什么原因| 牛欢喜是什么| 什么呢| 司令是什么级别| 魂牵梦萦的意思是什么| 13朵玫瑰代表什么意思| 什么中生什么| 陈皮泡水喝有什么好处| 革兰氏阳性菌是什么病| 矫正视力是指什么| 代言是什么意思| 2000年属什么的| 无毒不丈夫是什么意思| 什么是闰年什么是平年| 什么的口罩| 肾炎吃什么食物好| 秋收冬藏是什么生肖| 送老师什么礼物| 刘备是一个什么样的人| 炖肉什么时候放盐| 艮是什么意思| 胃黏膜受损吃什么药| 世界上最深的湖泊是什么| 六味地黄丸有什么副作用吗| 身体不出汗是什么原因| 紧急避孕药什么时候吃有效| 尿路感染什么症状| 排卵试纸强阳说明什么| 肛裂涂什么药膏能愈合| 秋天有什么植物| 1978年属什么生肖| 一什么村庄| 喷字去掉口念什么| 什么是神经性皮炎| 湿疹什么原因引起的| 衣服发黄是什么原因| 鼻梁有痣代表什么| 鸡呜狗盗是什么生肖| 菊花茶和枸杞一起泡水有什么好处| 风湿热是什么病| 孕妇梦见水是什么意思| 入盆是什么意思| 姨妈期间可以吃什么水果| 水分是什么意思| 清明节吃什么| 普洱茶什么牌子好| 广东菜心是什么菜| 学考是什么| 补气养血吃什么中成药| 南瓜长什么样子的图片| 黄梅时节是什么季节| 成双成对是什么意思| 2002年什么年| 普瑞巴林是什么药| 男人嘴角有痣代表什么| 逆钟向转位是什么意思| 四肢肿胀是什么原因引起的| 激素六项是查什么的| 新陈代谢是什么意思| 法身是什么意思| 愿闻其详什么意思| 足字旁的字有什么| 为什么家里有蚂蚁| 什么食物增加血管弹性| 秦始皇叫什么| 为什么早射| 路演是什么意思| 下一年是什么生肖| 农历9月28日是什么星座| 小孩割包皮挂什么科室| 大堤是什么意思| 官杀旺是什么意思| 一什么尾巴| 女人生气容易得什么病| 维生素b2有什么功效| 喝酒上脸是什么原因| smile是什么牌子| 心电图伪差是什么意思| 什么食物含铁量最高| 执迷不悟是什么生肖| 为什么叫五七干校| 心率低有什么危害| 拉雪橇的狗是什么狗| 系统性红斑狼疮不能吃什么| 支原体弱阳性是什么意思| 胳膊脱臼什么症状| 碧玺是什么意思| dell是什么牌子的电脑| 梦到黄鳝是什么意思| 闪婚是什么意思| 大条是什么意思| 脖子粗大是什么原因| jeans是什么意思| 上不下要读什么| 内热是什么意思| 弓箭是什么时候发明的| 阴道镜活检是什么意思| 艾滋病有什么特征| 为什么游戏| 尿酸高会引起什么疾病| 安徽有什么特色美食| 右眼皮跳是什么预兆女| 家里出现蚂蚁预示什么| pv是什么材质| 心肌酶高是什么意思| 北极贝长什么样| 新生儿不睡觉是什么原因| 什么是理学| 急性咽喉炎吃什么药好得快| 黄桃不能和什么一起吃| 一个木一个号念什么| 芒果什么时候成熟| 牙套什么年龄戴合适| 逸五行属性是什么| 自闭是什么意思| 大生化能查出什么病来| 视网膜脱落有什么症状| 什么是微商| 木槿花什么时候开花| 为什么晚上不能扫地| 什么汤降火| 三天不打上房揭瓦的下一句是什么| 跳大神是什么意思| 89年属什么生肖| 米线和米粉有什么区别| 躯体是什么意思| 开飞机什么意思| 疝气吃什么药效果好| 沙拉是什么意思| 走马观花的走是什么意思| 粟是什么| 聚什么会什么| 什么是干眼症| 发膜什么牌子效果最好| 前列腺增大有什么危害| 遇难呈祥是什么生肖| 子宫内膜单纯性增生是什么意思| 材料化学属于什么类| 隐性基因是什么意思| 女人切除子宫有什么影响| 做病理意味着什么| 做梦梦见兔子是什么意思| 无的放矢是什么意思| 户主有什么权利| 什么是汛期| 刘备字什么| bv中间型是什么意思| 太爷爷的爸爸叫什么| 什么时辰出生最好| 化骨龙是什么意思| 狗为什么喜欢吃骨头| 总胆红素升高是什么原因| 彩色多普勒超声检查是什么| 船舷是什么意思| 无创什么时候出结果| cc是什么单位| 做梦结婚是什么征兆| 下午六点多是什么时辰| linen是什么面料成分| 百度
Sitemap
Android Developers

Articles on modern tools and resources to help you build experiences that people love, faster and easier, across every Android device.

Press enter or click to view image in full size
Illustrated by Virginia Poltrack

How and why we modularized Plaid and what’s to come

This article dives deeper into the modularization portion of Restitching Plaid.

In this post I’ll cover how we refactored Plaid away from a monolithic universal application to a modularized app bundle. These are some of the benefits we achieved:

  • more than 60% reduction in install size
  • greatly increased code hygiene
  • potential for dynamic delivery, shipping code on demand

During all of this we did not make changes to the user experience.

A first glance at Plaid

Navigating Plaid

Plaid is an application with a delightful UI. Its home screen displays a stream of news items from several sources.
News items can be accessed in more detail, leading to separate screens.
The app also contains “search” functionality and an “about” screen. Based on these existing features we selected several for modularization.

The news sources, (Designer News and Dribbble), became their own dynamic feature module. The about and search features also were modularized into dynamic features.

Dynamic features allow code to be shipped without directly including it in the base apk. In consecutive steps this enables feature downloads on demand.

What’s in the box — Plaid’s construction

Like most Android apps, Plaid started out as a single monolithic module built as a universal apk. The install size was just under 7 MB. Much of this data however was never actually used at runtime.

Code structure

From a code point of view Plaid had clear boundary definitions through packages. But as it happens with a lot of codebases these boundaries were sometimes crossed and dependencies snuck in. Modularization forces us to be much stricter with these boundaries, improving the separation.

Native libraries

The biggest chunk of unused data originates in Bypass, a library we use to render markdown in Plaid. It includes native libraries for multiple CPU architectures which all end up in the universal apk taking up around 4MB. App bundles enable delivering only the library needed for the device architecture, reducing the required size to around 1MB.

Drawable resources

Many apps use rasterized assets. These are density dependent and commonly account for a huge chunk of an app’s file size. Apps can massively benefit from configuration apks, where each display density is put in a separate apk, allowing for a device tailored installation, also drastically reducing download and size.

Plaid relies heavily on vector drawables to display graphical assets. Since these are density agnostic and save a lot of file size already the data savings here were not too impactful for us.

Stitching everything together

During the modularization task, we initially replaced ./gradlew assemble with ./gradlew bundle. Instead of producing an Android PacKage (apk), Gradle would now produce an Android App Bundle (aab). An Android App Bundle is required for using the dynamic-feature Gradle plugin, which we’ll cover later on.

Android App Bundles

Instead of a single apk, AABs generate a number of smaller configuration apks. These apks can then be tailored to the user’s device, saving data during delivery and on disk. App bundles are also a prerequisite for dynamic feature modules.

Configuration apks are generated by Google Play after the Android App Bundle is uploaded. With app bundles being an open spec and Open Source tooling available, other app stores can implement this delivery mechanism too. In order for the Google Play Store to generate and sign the apks the app also has to be enrolled to App Signing by Google Play.

Benefits

What did this change of packaging do for us?

Plaid is now more than 60 % smaller on device, which equals about 4 MB of data.

This means that each user has some more space for other apps.
Also download time has improved due to decreased file size.

Not a single line of code had to be touched to achieve this drastic improvement.

Approaching modularization

The overall approach we chose for modularizing is this:

  1. Move all code and resources into a core module.
  2. Identify modularizable features.
  3. Move related code and resources into feature modules.
Press enter or click to view image in full size
green: dynamic features | dark grey: application module | light grey: libraries

The above graph shows the current state of Plaid’s modularization:

  • :bypass and external shared dependencies are included in core
  • :app depends on :core
  • dynamic feature modules depend on :app

Application module

The :app module basically is the already existing com.android.application, which is needed to create our app bundle and keep shipping Plaid to our users. Most code used to run Plaid doesn’t have to be in this module and can be moved elsewhere.

Plaid’s core module

To get started with our refactoring, we moved all code and resources into a com.android.library module. After further refactoring, our :core module only contains code and resources which are shared between feature modules. This allows for a much cleaner separation of dependencies.

External dependencies

A forked third party dependency is included in core via the :bypass module. Additionally, all other gradle dependencies were moved from :app to :core, using gradle’s api dependency keyword.

Gradle dependency declaration: api vs implementation

By utilizing api instead of implementation dependencies can be shared transparently throughout the app. While using api makes our dependencies easily maintainable because they are declared in a single file instead of spreading them across multiple build.gradle files this can slow down builds.

So instead of our initial approach we reverted to implementation, which requires us to be more explicit about the dependency declaration but tends to make our incremental builds faster.

Dynamic feature modules

Above I mentioned the features we identified that can be refactored into com.android.dynamic-feature modules. These are:

:about
:designernews
:dribbble
:search

Introducing com.android.dynamic-feature

A dynamic feature module is essentially a gradle module which can be downloaded independently from the base application module. It can hold code and resources and include dependencies, just like any other gradle module. While we’re not yet making use of dynamic delivery in Plaid we hope to in the future to further shrink the initial download size.

The great feature shuffle

After moving everything to :core, we flagged the “about” screen to be the feature with the least inter-dependencies, so we refactored it into a new :about module. This includes Activities, Views, code which is only used by this one feature. Also resources such as drawables, strings and transitions were moved to the new module.

We repeated these steps for each feature module, sometimes requiring dependencies to be broken up.

In the end, :core contained mostly shared code and the home feed functionality. Since the home feed is only displayed within the application module, we moved related code and resources back to :app.

A closer look at the feature structure

Compiled code can be structured in packages. Moving code into feature aligned packages is highly recommended before breaking it up into different compilation units. Luckily we didn’t have to restructure since Plaid already was well feature aligned.

Press enter or click to view image in full size
feature and core modules with their respective architectural layers

As I mentioned, much of the functionality of Plaid is provided through news sources. Each of these consists of remote and local data source, domain and UI layers.

Data sources are displayed in both the home feed and, in detail screens, within the feature module itself. The domain layer was unified in a single package. This had to be broken in two pieces: a part which can be shared throughout the app and another one that is only used within a feature.

Reusable parts were kept inside of the :core library, everything else went to their respective feature modules. The data layer and most of the domain layer is shared with at least one other module and were kept in core as well.

Package changes

We also made changes to package names to reflect the new module structure.
Code only relevant only to the :dribbble feature was moved from io.plaidapp to io.plaidapp.dribbble. The same was applied for each feature within their respective new module names.

This means that many imports had to be changed.

Modularizing resources caused some issues as we had to use the fully qualified name to disambiguate the generated R class. For example, importing a feature local layout’s views results in a call to R.id.library_image while using a drawable from :core in the same file resulted in calls to

io.plaidapp.core.R.drawable.avatar_placeholder

We mitigated this using Kotlin’s import aliasing feature allowing us to import core’s R file like this:

import io.plaidapp.core.R as coreR

That allowed to shorten the call site to

coreR.drawable.avatar_placeholder

This makes reading the code much more concise and resilient than having to go through the full package name every time.

Preparing the resource move

Resources, unlike code, don’t have a package structure. This makes it trickier to align them by feature. But by following some conventions in your code, this is not impossible either.

Within Plaid, files are prefixed to reflect where they are being used. For example, resources which are only used in :dribbble are prefixed with dribbble_.

Further, files that contain resources for multiple modules, such as styles.xml are structurally grouped by module and each of the attributes prefixed as well.

To give an example: Within a monolithic app, strings.xml holds most strings used throughout.
In a modularized app, each feature module holds on to its own strings.
It’s easier to break up the file when the strings are grouped by feature before modularizing.

Adhering to a convention like this makes moving the resources to the right place faster and easier. It also helps to avoid compile errors and runtime crashes.

Challenges along the way

To make a major refactoring task like this more manageable it’s important to have good communication within the team. Communicating planned changes and making them step by step helped us to keep merge conflicts and blocking changes to a minimum.

Good intentions

The dependency graph from earlier in this post shows, that dynamic feature modules know about the app module. The app module on the other hand can’t easily access code from dynamic feature modules. But they contain code which has to be executed at some point.

Without the app knowing enough about feature modules to access their code, there is no way to launch activities via their class name in the Intent(ACTION_VIEW, ActivityName::class.java) way.
There are multiple other ways to launch activities though. We decided to explicitly specify the component name.

To do this we created an AddressableActivity interface within core.

Using this approach, we created a function that unifies activity launch intent creation:

In its simplest implementation an AddressableActivity only needs an explicit class name as a String. Throughout Plaid, each Activity is launched through this mechanism. Some contain intent extras which also have to be passed through to the activity from various components of the app.

You can see how we did this in the whole file here:

Styling issues

Instead of a single AndroidManifest for the whole app, there are now separate AndroidManifests for each of the dynamic feature modules.
These manifests mainly contain information relevant to their component instantiation and some information concerning their delivery type, reflected by the dist: tag.
This means activities and services have to be declared inside the feature module that also holds the relevant code for this component.

We encountered an issue with modularizing our styles; we extracted styles only used by one feature out into their relevant module, but often they built upon :core styles using implicit inheritance.

Parts of Plaid’s style hierarchy

These styles are used to provide corresponding activities with themes through the module’s AndroidManifest.

Once we finished moving them, we encountered compile time issues like this:

* What went wrong:Execution failed for task ‘:app:processDebugResources’.
> Android resource linking failed
~/plaid/app/build/intermediates/merged_manifests/debug/AndroidManifest.xml:177: AAPT:
error: resource style/Plaid.Translucent.About (aka io.plaidapp:style/Plaid.Translucent.About) not found.
error: failed processing manifest.

The manifest merger tries to merge manifests from all the feature modules into the app’s module. That fails due to the feature module’s styles.xml files not being available to the app module at this point.

We worked around this by creating an empty declaration for each style within :core’s styles.xml like this:

<! — Placeholders. Implementations in feature modules. →<style name=”Plaid.Translucent.About” />
<style name=”Plaid.Translucent.DesignerNewsStory” />
<style name=”Plaid.Translucent.DesignerNewsLogin” />
<style name=”Plaid.Translucent.PostDesignerNewsStory” />
<style name=”Plaid.Translucent.Dribbble” />
<style name=”Plaid.Translucent.Dribbble.Shot” />
<style name=”Plaid.Translucent.Search” />

Now the manifest merger picks up the styles during merging, even though the actual implementation of the style is being introduced through the feature module’s styles.

Another way to avoid this is to keep style declarations in the core module. But this only works if all resources referenced are in the core module as well. That’s why we decided to go with the above approach.

Instrumentation test of dynamic features

Along the modularization we found that instrumentation tests currently can’t reside within the dynamic feature module but have to be included within the application module. We’ll expand on this in an upcoming blog post on our testing efforts.

What is yet to come?

Dynamic code loading

We make use of dynamic delivery through app bundles, but don’t yet download these after initial installation through the Play Core Library. This would for example allow us to mark news sources that are not enabled by default (Product Hunt) to only be installed once the user enables this source.

Adding further news sources

Throughout the modularization process, we kept in mind the possibility of adding further news sources. The work to cleanly separate modules and the possibility of delivering them on demand makes this more compelling.

Finish modularization

We made a lot of progress to modularize Plaid. But there’s still work to do. Product Hunt is a news source which we haven’t put into a dynamic feature module at this point. Also some of the functionality of already extracted feature modules can be evicted from core and integrated into the respective features directly.

So, why did we decide to modularize Plaid?

Going through this process, Plaid is now a heavily modularized app. All without making changes to the user experience. We did reap several benefits in our day to day development from this effort:

Install size

Plaid is now on average more than 60 % smaller on a user’s device.
This makes installation faster and saves on precious network allowance.

Compile time

A clean debug build without caches now takes 32 instead of 48 seconds.*
All the while increasing from ~50 to over 250 tasks.

This time saving is mainly due to increased parallel builds and compilation avoidance thanks to modularization.

Further, changes in single modules don’t require recompilation of every single module and make consecutive compilation a lot faster.

*For reference, these are the commits I built for before and after timing.

Maintainability

We have detangled all sorts of dependencies throughout the process, which makes the code a lot cleaner. Also, side effects have become rarer. Each of our feature modules can be worked on separately with few interactions between them. The main benefit here is that we have to resolve a lot less merge conflicts.

In conclusion

We’ve made the app more than 60% smaller, improved on code structure and modularized Plaid into dynamic feature modules, which add potential for on demand delivery.

Throughout the process we always maintained the app in a state that could be shipped to our users. You can switch your app to emit an Android App Bundle today and save install size straight away. Modularization can take some time but is a worthwhile effort (see above benefits), especially with dynamic delivery in mind.

Go check out Plaid’s source code to see the full extent of our changes and happy modularizing!

--

--

Android Developers
Android Developers

Published in Android Developers

Articles on modern tools and resources to help you build experiences that people love, faster and easier, across every Android device.

Ben Weiss
Ben Weiss

Written by Ben Weiss

#Android Developer Relations @ Google

Responses (23)

睾丸癌是由什么引起的 精神什么满 身体上有小红点是什么病 四川人为什么喜欢吃辣 属猪五行属什么
白细胞0是什么意思 轻微脑震荡吃什么药 半月板是什么意思 口干口苦吃什么药最好 过敏嘴唇肿是什么原因
哀莫大于心死什么意思 安全总监是什么级别 药流之后需要注意什么 门第什么意思 脐下三寸是什么地方
salsa什么意思 猫吐是什么原因 沉网和浮网有什么区别 肺痈是什么意思 肉桂茶适合什么人喝
头晕呕吐挂什么科hcv9jop4ns9r.cn 鸡炖什么好吃hcv9jop3ns1r.cn 烟雾病是什么原因引起的hcv8jop3ns6r.cn 厘米为什么叫公分dayuxmw.com 小腿肌肉抽筋是什么原因引起的hcv8jop3ns2r.cn
经常肚子疼是什么原因hcv9jop7ns5r.cn 凤辇是什么意思shenchushe.com 做包皮手术挂什么科hcv8jop6ns6r.cn 梦见修坟墓是什么预兆onlinewuye.com 手指关节疼痛是什么原因hcv8jop5ns4r.cn
小猫感冒吃什么药hcv9jop5ns0r.cn 什么人不能喝大麦茶hanqikai.com 金价下跌意味着什么yanzhenzixun.com m是什么单位hcv9jop7ns2r.cn 女性下面流水什么原因hcv8jop9ns2r.cn
水晶粉是什么原料做的hcv8jop9ns9r.cn 吃什么健脾胃除湿气hcv9jop0ns4r.cn 扁的桃子叫什么名字hcv9jop5ns0r.cn 大油边是什么hcv9jop0ns7r.cn 艺人是什么意思hcv8jop9ns6r.cn
百度