从大厂APP看Android组件化实践最终篇
Posted 初一十五啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从大厂APP看Android组件化实践最终篇相关的知识,希望对你有一定的参考价值。
前言
关于组件化的介绍,今天就完结了。
后续等组件化更新完,更新:
- Shadow插件化
- ASM其他
- Systrace使用与解析
- 字节码相关
关注公众号:初一十五a
解锁 《Android十三大板块文档》,让学习更贴近未来实战。已形成PDF版
内容如下:
1.2022最新Android11位大厂面试专题,128道附答案
2.音视频大合集,从初中高到面试应有尽有
3.Android车载应用大合集,从零开始一起学
4.性能优化大合集,告别优化烦恼
5.Framework大合集,从里到外分析的明明白白
6.Flutter大合集,进阶Flutter高级工程师
7.compose大合集,拥抱新技术
8.Jetpack大合集,全家桶一次吃个够
9.架构大合集,轻松应对工作需求
10.Android基础篇大合集,根基稳固高楼平地起
11.Flutter番外篇:Flutter面试+Flutter项目实战+Flutter电子书
12.高级Android组件化强化实战
13.十二模块之补充部分:其他Android十一大知识体系
整理不易,关注一下吧。开始进入正题,ღ( ´・ᴗ・` ) 🤔
从有赞微商城看组件化架构实践
组件化改造概述:
- 提出了 5 个调整方向:抽象基础模块、公共服务去中心化、业务模块服务化、抽象基础组件、单/多模块打包
- 介绍了基于 3 个基础组件依赖和 1 个 Gradle 插件的落地方案
一丶有赞移动应用如何给页面安上“任意门”
“任意门”:一行配置实现页面跳转重定向。
1.1.背景 & 痛点 & 价值
动态路由组件,处理的是 App 中最最常见的一种行为的问题,那就是:跳转。
随着 App 技术栈的扩展,从原本最最简单的原生到原生的跳转,扩展到目前同一个 App 中包含原生页面、H5 页面、Weex 页面、Flutter 页面之间的跳转。
随之而来的问题就是:随着 App 的版本迭代,很多原本原生实现的页面,需要通过新的 H5 或者 Weex 页面进行升级/降级。而这些原本都是硬编码的跳转逻辑,可能需要随着版本不停改动。总结下来,现有的,各个技术栈隔离的页面跳转逻辑面临的直接问题有:
- 跳转逻辑跟着版本走,无法统一进行改动
- 跨技术栈跳转的实现成本比较高,必须在桥接模块中进行特殊适配
- 在一些 H5 需要使用专门 WebView 页面打开的场景下,很难去适配,也必须通过各个 Web 跳转的拦截做特殊处理
为了解决以上硬编码以及灵活性差的问题,我们决定梳理现有的各技术栈跳转逻辑,将这些跳转整合,能够满足动态性、可配置的需求。
得益于项目中原有的路由跳转组件,各种页面之间的页面都可以通过 URL 的方式进行路由,于是我们基于 URL 跳转,开发了一套动态路由组件,它完成的工作有 :
- 承担 App 内所有跳转逻辑
- 通过配置中心组件,支持获取/配置路由替换规则
- 匹配所有的路由跳转规则,命中规则的,替换成新的目标路由地址
- 将实际跳转目标地址传递给路由组件执行实际的跳转行为
1.2.实现方案
路由拦截+替换
微商城客户端目前已经有一套稳固的组件化实现方案,组件之间的页面跳转通过路由的方式进行解耦,这是一种比较常见的方式。
在微商城项目中,负责实现的路由组件为 ZanURLRouter ,它的职责很简单:
- 启动时注册路由和页面
- 找寻正确的页面进行跳转
在不影响外部接口的前提下,我们在目标路由解析这一步,引入了动态路由
对于移动端的路由重定向,实际上就是将一个路由转换为另一个路由,如:
youzan://orderlist?type=1&status=2
转换为:
wsc://orderlist/v2?type=1&status=2
跳转规则配置
路由的拦截和替换中的一个关键节点就是“配置”,我们需要一个路由规则列表来记录和下发匹配规则。为了方便下发路由规则表,我们将这份配置表存放在有赞移动配置中心,根据客户端的版本进行区分,动态地下发给不同版本的客户端。
一条路由规则,分为一个 Key 和对应的 Value,Key 为匹配方式,使用正则表达式进行匹配,Value 为替换方式,使用 JSON 格式定义。
实际代码实现中,我们将“路由规则”和“路由替换行为”分别抽象成实体类和接口方法。
抽象实体类
关于替换路由跳转的规则,我们可以这样配置:
Key: ^youzan://orderlist\\?type=(\\d+)&status=(\\d+)$
Value:
"template": "wsc://orderlist/v2?type=$1&status=$2"
即:一条匹配规则 + 一条替换模板。我们将之抽象为一个实体类, Rule :
class Rule
// url 匹配规则(正则表达式)
String pattern;
// url 匹配规则(正则表达式)
String template;
抽象接口
有了规则配置之后,就需要对动态路由的行为进行抽象,核心就是初始化规则、匹配规则和替换路由三个方法:
// 注册替换规则
fun initWithPattern(Rule rule)
// 校验是否命中已经注册的路由配置的 pattern 正则
fun testWithRoute(String routeUrl): Boolean
// 获取替换后的跳转地址
fun appliedWithRoute(String routeUrl): String
动态路由器会在应用启动阶段拉取正确的规则表,解析并记录下来:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dHTAWo77-1667814487712)(https://upload-images.jianshu.io/upload_images/23851953-4fc9f25bfd9546b3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
在 ZanURLRouter
解析目标路由的时候,对每一个规则进行匹配测试,命中则应用匹配的规则,返回替换后的路
由,再继续接下来的工作。
路由替换
实体类、接口类都抽象完成之后,就是动态路由的核心实现了,这里依赖到一个的核心工具就是:正则表达式。这里用到正则的场景有两个: - 正则验证是否命中规则 - 正则替换url文本
在 android 和 ios 开发中,字符串正则相关的 API 都是自带的,开箱即用:
/* ------------ Android ------------ */
// 正则匹配校验方法
Pattern.matcher(String text)
// 正则匹配校验方法
Regex.replace(String input, String replacement)
/* ------------ iOS ------------ */
(NSString *)stringByReplacingMatchesInString:(NSString *)string options:
(NSMatchingOptions)options range:(NSRange)range withTemplate:(NSString *)templ;
疑难问题:参数处理
大部分情况下,跳转本身都是带参数的,那么动态替换跳转的 URL 之后,参数的获取就成了一个问题,尤其是原生和其他页面页面的跳转。
我们主要以 Android 为例,Android 原生跳转都是通过一个关键类:Intent 来实现参数的存取。这里需要注意的是,由于 Intent 传值存在多种复杂的数据接口,包括 Parcelable 这种复杂参数的场景,由于降级之后都是以 URL 的形式传值,所以我们目前约定动态路由的参数只支持基本数据类型,复杂参数类型的需要接入方来做兼容。
参数处理我们分两个典型的场景来讨论: - 原生跳转 H5 参数传递 - H5 跳转原生的参数传递.
原生跳转H5
这里的方式主要是将 Intent 中的基本数值类型参数取出来,拼接成带参数的 URL 来实现将 Intent 里面的参数传递给H5,主要实现代码如下:
fun appendBundleParams(strBuilder: StringBuilder, bundle: Bundle)
val ketSet = bundle.keySet()
for (key in ketSet)
bundle[key]?.let value ->
when (value)
is Bundle -> appendBundleParams(strBuilder, value)
is String, is Int, is Long, is Double, is Float, is Char, is Boolean, is Short ->
if (strBuilder.isNotEmpty())
strBuilder.append("&")
strBuilder.append("$key=$value")
else ->
// do nothing
H5跳转原生
同理的,H5 跳转原生做的就是将 URL 中携带的参数塞到 Intent 中来进行。
这里比较关键的一个问题是:Intent 的取值都是带类型的,而 URL 的参数都是字符串。我们目前解决方案也很简单,就是封装 Intent 的取值方法,由于目前有赞 Android 主要使用 Kotlin 来开发,可以使用 Kotlin 的扩展函数特性来实现(Java 可以使用工具类的方式):
fun Intent.getIntFromRouter(key: String, defaultValue: Int): Int
val extras = this.extras;
if (extras == null || !this.hasExtra(key))
return defaultValue
return extras.get(key) as? Int ?: (this.getStringExtra(key)?.toInt() ?: defaultValue)
碰到的坑:UrlEncode
在匹配和替换 URL 规则的场景中,我们经常会碰到这么一种情况,URL 是被 UrlEncode 过的。由于字符串的正则匹配和正则替换是不会判断字符串是否被 UrlEncode 过,所以这里的逻辑需要由路由组件来实现。
UrlEncode 字符串的正则匹配逻辑实现比较简单,即直接将字符串 Decode 之后进行匹配。
比较复杂的是 UrlEncode 字符串的正则替换,有些情况下,路由中的url是必须进行 UrlEncode 的,如果直接Decode 进行替换,那么可能会导致实际跳转的目标 URL 被错误地截断,导致无法跳转,所以这里的替换必须保留UrlEncode 的字符。
我们的解决思路是:记录 URLEncode 前后被 encode 字符的下标,然后再手动实现 replace 方法去挨个替换字符串中的字符,核心代码如下:
private fun getEncodeCharMap(url: String, encodeUrl: String): Map<Int, IntRange>
if (Uri.decode(encodeUrl) != url)
return mapOf()
val urlChars = url.toCharArray()
val urlEncodeChars = encodeUrl.toCharArray()
var i = 0
var j = 0
val encodeMap = mutableMapOf<Int, IntRange>()
while (i < urlChars.size && j < urlEncodeChars.size)
// text: [www:] => [www%23]
// length: [0123] => [012345]
if (urlChars[i] != urlEncodeChars[j])
val s = Uri.encode(urlChars[i].toString())
val range = IntRange(j, j + s.length - 1)
encodeMap[i] = range
j += s.length
else
j++
i++
return encodeMap
1.3.实际应用案例
应用中心
微商城App应用中心,应该是应用动态路由的最佳场景,应用中心存在大量跳转的场景。
先来说下使用动态路由的背景,应用中心中应用列表都是由服务端统一下发的,后端为每个应用配置的跳转地址是统一的,而 Android 和 iOS 本地路由配置的 URL 是不一致的,如果直接下发配置的话,会存在有一端无法跳转的问题。以店铺管理应用跳转为例:
- iOS中店铺管理的路由 URL:wsc://shop/management
- Android 中的路由URL:wsc://team/management
- 服务端下发的URL:wsc://team/management
那么解决同一套配置跳转不同 URL 的这个问题,就交给动态路由来完成了,我只需要在iOS的动态路由添加一个规则,将 wsc://shop/management 动态替换成 wsc://team/management 就可以搞定!
订单项目
在微商城客户端的订单模块重构项目中,考虑到订单是使用频次很高的核心场景之一,且代码历史较久,所以新的模块上线后与旧订单列表模块共存,直到灰度完全结束。
由于微商城已经是组件化拆分,业务组件之间的跳转使用路由完成,我们在设计灰度方案时,利用动态路由来实时进行目标路由的映射:
1.4.总结
“上线只是开始”,随着业务迭代,历史业务也越来越多,为了保证不同平台版本的用户能够平滑过渡到新的功能上去,动态路由组件扮演了一个客户端的 URL 重定向服务的角色,避免因服务下线、功能更新、平台差异、项目重构等原因导致的功能不可用。
动态路由组件,核心就是非常简单的正则匹配和正则替换,而这个非常简单和核心代码逻辑,实现了业务场景下非常重要的路由重定向。这整套解决方案,也是有赞移动端在应用组件化、动态化的一个重要组成部分,我们也希望这个技术方案能够抛砖引玉,启发更多优秀的移动端动态化解决思路。
二丶有赞微商城-Android组件化方案
2.1.概述
目前有赞移动端的主要工作内容是在“有赞微商城”和“有赞零售”两条公司主要的业务线,随着有赞 Saas 业务的增长,客户端也不断迭代,支持越来越多的功能。 在这个业务快速增长的情况下,移动端技术的整体架构也是一直在不断调整,来保证开发效率和业务的快速迭代。
这篇文章,主要是介绍有赞微商城 Android组件化的一些思路和实现。
现状
客户端的架构,从一开始的“All IN ONE” 模式(即所有代码都在 App 中),逐渐演变到目前的一个单 Project 多Module 结构:
痛点
新的项目架构,也带了了新的问题: - 日益复杂的 Common 模块,逻辑复杂,依赖不清晰,不敢随便改动Common 代码,造成大量冗余代码和无法维护的业务逻辑 - 随着业务模块的增多,打包速度一发不可收拾;从倒杯水的时间到下楼吃个饭的时间,大大减慢了开发节奏 - 由于业务模块跟项目中的上层(App 壳)和下层(Common模块)耦合 - 业务模块增多,由于业务模块没有自己的生命周期,无法实现模块之间的隔离,整体模块控制比较混乱
需要解决的问题
- Common模块轻量化,需要将 Common 层的业务向上抽离,通用的底层和基础组件、公用 UI 组件抽成单独的依赖
- 移动端业务服务化,解耦现有业务,抽象出业务接口,业务模块只向外暴露自己的接口,并实现跨模块之间的调用
- 能够配置单模块或者多模块打包,不用每次调试都全量打包,费时费力,又影响开发的节奏
- 业务模块的依赖和发布管理
2.2.架构调整
我们之前虽然有在做整个工程模块化的开发,但是目前的模块化框架可以说是不够彻底的:
- 模块只是项目结构的概念(一个模块一个 Module),在逻辑层并没有模块这个概念
- 模块本身并没有生命周期控制
- 公用服务中心化,公用逻辑部分全部都在 Common 模块中
- 模块对外暴露的服务不可知,都是直接依赖模块内部的代码逻辑
- 模块无法单独打包,针对模块的代码改动,只能全量打包之后才能看到效果
- 为了解决以上的问题,我们需要对现有的架构进行调整。
模块化的抽象
将模块的功能抽象出一些基础类,组成了模块化支持组件,它提供的功能有:
- 抽象出模块本身作为某一类业务的容器,即所有业务模块需要实现自己的模块类,继承自我们的BaseModule,并在 App 壳工程中进行注册
- 模块对象跟 Activity 一样,拥有生命周期的概念,需要在生命周期的不同阶段处理自己相应的逻辑(注册服务、初始化数据等)
- 模块可以注册的对外暴露的服务的实现,在注册模块的时候,模块携带的服务也会被注册到 App 的服务中心
公共业务去中心化
跟很多客户端的同学聊过,很多 APP 发展到一定阶段之后,必然会诞生一个所谓的 Common 模块。它就像一个大储物柜,每个人都把一些其他人可能用到的东西一股脑儿塞进去。 这么个塞法,会有两个问题:
- 冗余:比如一些工具类,很多时候,当你找不到需要的工具类的时候,你可能会塞一个新的进去
- 维护成本高:所有公用的业务逻辑的实现都在 Common 中,对一些公用业务逻辑的影响面无法掌控
Common 里面都有什么?
- 工具类
- 公用的 UI 组件
- 多个业务模块都公用的业务类
- 基础组件的封装类(图片库、网络库、Webview)
- 封装的一些基类(BaseActivity,BaseFragment 什么的)
解决的思路
- 将公用的业务模块向上抽离到业务模块中(所谓业务模块的服务化)
- 将基础组件抽象到一个独立的组件中
- 将一些基础类下沉到不包含业务逻辑的底层核心库中
业务模块服务化
服务化”这个词,在服务端的开发中经常被提到,简单来说,就是根据业务划分为多个模块,模块之间的交互以互相提供服务的方式来完成。 而客户端随着业务模块的增多,也必然存在业务模块之间存在业务依赖的情况,而Android 端依赖的方式有:
- A 模块直接依赖 B 模块,直接调用 B 模块的代码逻辑
- 将 A 和 B 模块中的公用部分放到 Common 模块中,通过调用 Common 模块的代码实现依赖
业务模块服务依赖的实现
- 后端的服务化是借助于 Dubbo 来构建的 RPC 服务,依赖某个服务,只需要依赖其对外暴露的 API 模块(只包含接口和数据结构的 Maven 依赖),不需要依赖其具体实现,具体服务调用的实现由框架来实现
- 客服端的依赖也可以参考这样的方式来实现模块之间的依赖,例如商品模块,可以提供一个 API 层,用来对外暴露数据结构和服务
API 层实现方式
对外暴露服务的方式有很多种:
- 协议的方式:例如"app://order/detail/get?id=100",数据可以用 JSON 来进行传递,请求本地服务就像调用一个 Http 服务一样,根据请求协议来获取数据,然后解析数据进行操作
- 接口的方式:像后端使用 Dubbo 服务那样,订单模块对外提供一个独立的 Maven 依赖,里面包含了数据接口和对外提供的服务接口,适用方依赖之后直接调用
接口的方式实现 API
协议的方式的问题:如果服务提供的地方更改了之后,需要手动去查询所有调用到的地方,进行更改,而且没有版本管理,而且数据解析都需要手动进行转换,改动的成本比较高,也有一定稳定性风险。 接口的方式的问题:需要额外提供一个依赖(单独把 API 层打包成一个 aar 包),使用方需要添加 Mave 依赖,所以引入依赖和发布的成本比较高。
我们最终选择了接口的方式,这种方式的稳定性和版本控制做的更好,对于改动来说,编译过程自动会帮你校验改动的影响面,而引入依赖和发布成本高的问题,完全可以交给构建工具(Gradle Plugin)来解决。
业务实现层
业务实现层需要做的,就是实现自己模块本身的业务逻辑,并实现自己提供的 API 接口,暴露对外的服务。
基础组件抽象
现有的基础组件实现
项目中现在有很多的基础组件都是统一在 Common 里面进行封装的,例如:账号库、网络库、图片加载、Web 容器库等等,这也带来了一些问题:
- Common 太重
- 业务模块跟基础组件强耦合,在开发一些跨团队的组件过程中,如果碰到使用的基础库不同的时候,需要比较多的时间来做封装
- 升级基础组件或替换依赖的成本比较高,一些 API 的更改需要改动每个调用的地方
实现思路
- 将常用的基础组件整理,抽象成单独的一个抽象层,里面定义了一系列基础组件接口(图片加载、Web 容器、JsBridge 调用、账号等等)
- 把统一实现的组件放到另一个依赖里面,可以在 App 中进行具体实现的注册,而业务模块本身,可以只依赖抽象
依赖结构
单/多模块打包
随着业务量和业务复杂度的增长,还有多个三方组件的引入,客户端工程代码量也变得越来越庞大,直接造成的一个问题是:打包慢!一个简单的场景:当你开发了一个商品模块内部的功能之后,你需要打整个 App 的包才能进行测试,而打一个包的时间可能是 5~10 分钟,如果一天打包 10 次,也是比较酸爽。我们的组件也需要支持单模块或者选定的某些进行打包,其中的思路也是通过自定义 Gradle Plugin 在编译阶段,动态去更改 Module 实际依赖的Android Gradle 插件来实现的。 经测试,同一台电脑,完整打包(clean之后再安装)耗时 4 分钟,而单模块打包
(同样也是 clean 之后安装)耗时 1 分钟,整体打包时间降低了 70% 以上。
架构图
上面的一些改进点,总结成一张图,就是这样的:
2.3.实现方案
目前我们的方案提供 3 个基础组件依赖和 1 个 Gradle 插件:
- modular-core: 提供组件模块化生命周期和模块服务注册相关的模块化基础组件
- modular-support: 对项目中二方、三方包接口的抽象
- modular-support-impl:对项目中二方、三方包接口的默认抽象
- modular-plugin: 支持模块生成 API 层目录,生成 APP 运行环境,以及管理模块发布的 Gradle 插件
Modular-core
实现模块类
业务模块类需要继承 BaseModule:
public class ModuleA extends BaseModule
@Override
public void onInstalled()
registerBusinessService(ModuleAService.class, new CachedServiceFetcher()
@Override
public ModuleAService createService(@NotNull ModularManage manager)
if (service == null)
service = new ModuleAServiceImpl();
return service;
模块生命周期
模块有以下几个生命周期:
- onInstalled() -> 模块被注册的时候调用:Module 在 App 中被注册的时候
- onCreate() -> 模块第一次启动的时候调用:Module 所属的某个 Activity 第一次启动的时候
- onStart() -> 模块启动的时候调用:模块第一次启动之的时候
- onStop() -> 模块停止的时候调用:Activity 栈里面没有模块所属 Activity 的时候
模块生命周期的实现
其实组件内关于生命周期捕获和监听,都是借助于 Google 的 Android Architecture Components 中的 Lifecycle 库来实现的。
- 模块生命周期的捕获:首先需要将 Activity 的类注册到 Module 中,然后全局监听 Activity 的 Lifecycle.Event事件,就可以获取到模块内 Activity 的运行情况
- 模块生命周期的监听:BaseModule 本身继承了LifecycleOwner 接口,可以对其添加观察者,来实现对模块生命周期的监听
Modular-plugin
这里需要依赖对于 Android 的构建工具 Gralde 的扩展,它支持的高度可扩展特性,帮助我们在组件化开发中更加高效,不需要关系一些额外的工作,只需要关注开发的内容即可,对现有的代码逻辑基本没有侵入。
Gralde 的生命周期
这里必须要提一些的就是 Gradle 的生命周期,因为我们的很多扩展功能,都是在对 Gradle 执行的生命周期的各个阶段做一些改动来实现的,大概的生命周期如图:
单模块打包
Android 打包成 Apk 并运行的条件有:
- AndroidManifest.xml 的配置支持(application 标签的配置)
- 主 Activity 的配置
实现原理
- 自动生成模块自己的 Application 类
- 自动读取 Module 的 AndroidManifest 文件并修改成可以打包成 App 的配置
- 在打包的时候动态更改 SourceSet,使打包的时候使用生成的文件进行打包
- 在打包的时候动态更改支持的 Plugin 类(‘com.android.application’或是’com.android.library’)
修改模块 build.gradle 的配置
将以下配置添加到模块目录下的 build.gradle 文件中
modular
// 模块包名
packageName = "com.youzan.ebizcore.plugin.demoa"
app
// 单模块打包开关
asApp = true
// 运行的 App 的名称
appName = "Module A"
// 入口 Activity
launchActivity = "com.youzan.ebizcore.plugin.demoa.ModuleAActivity"
// 配置只在单模块打包时需要引入的依赖
requires
require "com.squareup.picasso:picasso:2.3.2"
生成单模块运行需要的环境
运行 modular 的 createApp Task,就会自动生成需要的类(以 module_a 为例)
自动生成的文件目录结构:
./module_a
--src
----main
------app # 自动生成 app 目录
--------java # 自动生成 Application 类
--------res # 自动生成资源
--------AndroidManifest.xml # 自动生成 Manifest 文件
执行单模块打包并安装的 Task
运行 modular 的 runAsApp Task,模块就会被单独达成一个 apk 包,并安装到你的手机上,如果模块有上下文依赖(比如登录)的话可以额外提供依赖,加到模块的 app 的 requires 中。 这里的打包执行是在 build 目录下生成了一个打包脚本,并调用 Gradle 的 API 执行脚本来实现打包安装的。
模块 API 管理
模块 API 层提供的接口和数据结构代码是可以直接在模块内部被引用到的,方便开发,但是在暴露给外部的模块时候的时候是需要打包成 aar 上传到 Maven 来提供的,Modular-Plugin 分别针对这两个步骤提供了两个 Task,方便开发者快速进行开发和发布。
在 build.gralde 中添加相关配置
modular
packageName = "com.youzan.ebizcore.plugin.demoa"
// 模块 API 支持相关参数
api
// 是否需要提供 API 支持的开关(会影响到是否可以运行自动生成代码的 Task)
hasApi = true
// 对外提供的 API Service 类名
apiService = "ModuleAService"
// API 层的依赖
requires
require "com.google.code.gson:gson:2.8.2"
生成 API 打包需要的文件
运行 modular 的 createApi Task,就会自动生成需要的类(以 module_b 为例)
./module_b
--src
----main
------service # 自动生成 service 目录,用来存放对外接口和数据对象
--------java # 自动生成 Application 类
--------AndroidManifest.xml # 自动生成 Manifest 文件,为了单独打成 aar 包
模块发布
发布功能内部使用了 ‘maven-publish’ 插件来进行依赖的上传,开发者只关心上报的配置就好
在 build.gralde 中添加发布配置
modular
// 模块发布需要的参数
publish
// 是否打开模块发布
active = true
// 上报地址,支持本地路径和远程 Mave 仓库地址
repo = "../release"
groupId = "com.youzan.ebizmobile.demo"
artifactId = "modular-a"
// 上报的业务模块 aar 包的版本号
moduleVersion = "0.1.4"
// 上报的 API 层 aar 包的版本号
apiVersion = "0.1.5"
// Maven 登录名和密码,可以从 local.properties 中取
userName = ""
执行发布的 Task
运行 modular 的 uploadModule Task,Module-Plugin 会执行打包上传的任务,执行顺序是这样的:
- 首先打包并上传 Module 的 API 模块(SourceSet 只包含 API 的类)
- 将 Module API 的代码从模块的 SourceSet 中去除,并添加刚才上报的 API 模块的 Maven 依赖到 Module 的 dependencies 中
2.4.Modular-support
基础组件抽象
以图片组件为例,一般业务模块中使用到的图片相关的功能有:图片加载、图片选择等,可以把这些功能抽象成接口
interface IImageLoadSupport
fun <IMAGE : ImageView> loadImage(imageView: IMAGE?, imgUrl: String)
fun <IMAGE : ImageView> loadImage(imageView: IMAGE?, @DrawableRes drawableId: Int)
fun <IMAGE : ImageView> loadImage(imageView: IMAGE?, imgUrl: String, callback:ImageLoadCallback<IMAGE>)
fun imagePicker(activity: Activity?, selectedImgUris: List<Uri>)
fun onImagePickerResult(requestCode: Int, resultCode: Int, intent: Intent?):List<String>?
基础组件的实现
基础组件的实现可以在 App 中进行注册,如果需要单模块组件中使用 Support 相关功能,可以提供一套默认实现,在但模块运行时引入,在全局有一个 Support 注册中心,以 Map 的形式维护运行中的 Support 对象:
fun <SUPPORT : Any, SUPPORTIMPL : SUPPORT> registerProvider(supportCls: Class<SUPPORT>,provider: SupportProvider<SUPPORTIMPL>)
synchronized(Lock)
supportsProviderMap[supportCls] = provider
if (supportsMap.containsKey(supportCls))
supportsMap.remove(supportCls)
规划
开发到现在,这边的三个组件已经能够基本完成我们对于组件化核心需求,但是,也是有一些方向可以进一步优化整套方案的使用:
- Modular-Support 组件引入依赖注入的方式实现 API 的调用,使用方可以不再需要关心实例对象的获取
- Modular-Support 组件可以提供给 Weex、RN、H5、Flutter 业务一些原生的功能
- Modular-Plugin 能够进一步压缩打包时间,并且让开发中的依赖配置更加灵活
- Modular-Plugin 继续优化管理和依赖打包的功能,提高效率
关注公众号:初一十五a
解锁 《Android十三大板块文档》,让学习更贴近未来实战。已形成PDF版
内容如下:
1.2022最新Android11位大厂面试专题,128道附答案
2.音视频大合集,从初中高到面试应有尽有
3.Android车载应用大合集,从零开始一起学
4.性能优化大合集,告别优化烦恼
5.Framework大合集,从里到外分析的明明白白
6.Flutter大合集,进阶Flutter高级工程师
7.compose大合集,拥抱新技术
8.Jetpack大合集,全家桶一次吃个够
9.架构大合集,轻松应对工作需求
10.Android基础篇大合集,根基稳固高楼平地起
11.Flutter番外篇:Flutter面试+Flutter项目实战+Flutter电子书
12.高级Android组件化强化实战
13.十二模块之补充部分:其他Android十一大知识体系
整理不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔
以上是关于从大厂APP看Android组件化实践最终篇的主要内容,如果未能解决你的问题,请参考以下文章