组件化方案调研

组件化方案调研

组件化概念

组件化就是将一个app分成多个Module,如下图,每个Module都是一个组件(也可以是一个基础库供组件依赖),开发的过程中我们可以单独调试部分组件,组件间不需要互相依赖,但可以相互调用,最终发布的时候所有组件以lib的形式被主app工程依赖并打包成一个apk。

1.png

模块化、插件化和组件化的关系

对于这三者的关系:有不同的说法。
截取一些,以供参考,其实虽然每个人都有每个的不通过的理解和说法,但是内涵其实是一样的。

模块化与组件化

  1. 得到(张明庆)

    在技术开发领域,模块化是指分拆代码,即当我们的代码特别臃肿的时候,用模块化将代码分而治之、解耦分层。具体到 android 领域,模块化的具体实施方法分为插件化和组件化。

  2. 《 Java 应用架构设计:模块化模式与 OSGi 》一书中对它的定义是:模块化是一种处理复杂系统分解为更好的可管理模块的方式。

  3. 《安居客》(张磊)组件和模块做个区别定义

    • 组件:指的是单一的功能组件,如地图组件(MapSDK)、支付组件(AnjukePay)、路由组件(Router)等等;
    • 模块:指的是独立的业务模块,如新房模块(NewHouseModule)、二手房模块(SecondHouseModule)、即时通讯模块(InstantMessagingModule)等等;模块相对于组件来说粒度更大。

综上所述,模块化与组件化都是对于一个整体功能的封装,只不过粒度不一样。我们本文中所指的组件化,和上述3中提到的模块化是一致的。

插件化与组件化

  1. 一套完整的插件化或组件化都必须能够实现单独调试、集成编译、数据传输、UI 跳转、生命周期和代码边界这六大功能。

  2. 插件化和组件化最重要而且是唯一的区别的就是:插件化可以动态增加和修改线上的模块,组件化的动态能力相对较弱,只能对线上已有模块进行动态的加载和卸载,不能新增和修改。

  3. 插件化让我们的App的运行的时候能够动态的进行组装,就像我们使用chrome的插件一样,非常的方便,甚至演变到后期,插件化越来越像[虚拟机]发展,使用一个类似[boot]的壳,就可以像Java虚拟机加载Java文件一样加载一个App。插件化主要涉及对系统加载dex的依赖,所以适配起来坑比较多。

  4. 组件化可以说和插件化有异曲同工之妙,只不过插件化是在[运行时],而组件化是在[编译时]。换句话说,插件化是基于多APK的,而组件化本质上还是只有一个APK。编译一个大型App的时间时间很长,可能2,3分钟都不够.[组件化] 就可以很好的解决这样的问题,此外,由于整个App的各个业务被分离了,所以它们之间的耦合度也就被降低了,各个业务线可以由专门的开发同学进行开发,相互之间也不会有干扰,提升开发效率

组件化详细方案

代码组件之间解耦

按照业务逻辑划分模块,对于一般app来说,可以分为三层。

  1. 基础库(比如网络库,数据库,utils,图片加载库)
  2. 基础模块,但是没有独立运行调试的必要,一般只是当作com.android.library被别的独立模块引用(比如登陆模块,分享模块,视频播放模块)
  3. 业务模块,可以进行单独编译,打包,测试(比如拍摄模块,搜索模块,视频流模块,小视频模块)

这三个层次只能向下依赖,比如3层依赖2层,2层依赖1层。每一层里面互相不能依赖。
这符合依赖倒置原则

业界模块划分的例子

  • 安居客


    4.jpg
  • 滴滴组件化的图


    3.jpg
  • 微信

6.png

组件工程单独开发调试

先来看看Android组件化需要实现的目标。(什么是组件化构建?)

  1. 项目模块能够单独启动测试
  2. 能够根据需求引入或删除某些业务模块
  3. 通过不同模块的组合,组成不同的App

对于第一点:Android是通过应用com.android.application或com.android.library来决定该模块是以App模式还是以Library模式构建。App模式和Library模式的最大区别就是,App能够启动,而Library不可以。所以如果我们的模块能独立启动的话,我们需要每次手动去改动模块的build.gradle文件。好一点的做法定义一个布尔值来判断是否处于debug模式,但是这里有个问题是,不是每个模块都能独立启动的。所以无论采用何种方案,都需要我们手动管理。

对于第二点:当我们开发好业务模块后,可能我们需要频繁的新增或删除某些业务模块。如果是这样的话,我们也是需要频繁手动修改App的build.gradle。

对于第三点:有时候,我们可能会在不同的App中引用相同的组件(例如:滴滴的普通版和企业版,普通版包含企业版的功能),这个时候,我们也不希望要频繁手动管理组件依赖,特别是在组件还可以独立运行的时候。

所以,在我们实践组件化的时候,最大的问题就是,我们需要频繁的手动build.gradle文件来管理组件应用的插件和App的依赖。

对于这一部分,主要的就是开发一个gradle插件,对工程进行自动化管理,避免人为切换导致的各种错误。

组件之间互相通信

由于组件之间完全解耦,互相无法直接进行调用,所以通过接口+实现的结构进行组件间的通信。每个组件声明自己提供的服务 Service API,这些 Service 都是一些接口,组件负责将这些 Service 实现并注册到一个统一的路由 Router 中去,如果要使用某个组件的功能,只需要向Router 请求这个 Service 的实现,具体的实现细节我们全然不关心,只要能返回我们需要的结果就可以了。在组件化架构设计图中 Common 组件就包含了路由服务组件,里面包括了每个组件的路由入口和跳转。

由于各个模块是严格划分解耦的,各个模块对与彼此的存在是完全不知道的,所以各个组件的互相通信就需要一个通用的协议来进行。于是引入了路由框架。

8.png

通过上图可以看到,我们在最基础的Common库中,创建了一个路由Router,中间有n个模块Module,这个Module实际上就是Android Studio中的module,这些Module都是Android Library Module,最上面的Module Main是可运行的Android Application Module。

这几个Module都引用了Common库,同时Main Module还引用了A、B、N这几个Module,经过这样的处理之后,所有的Module之间的相互调用就都消失了,耦合性降低,所有的通信统一都交给Router来处理分发,而注册工作则交由Main Module去进行初始化。这个架构思想其实和Binder的思想很类似,采用C/S模式,模块之间隔离,数据通过共享区域进行传递。模块与模块之间只暴露对外开放的Action,所以也具备面向接口编程思想。

图中的红色矩形代表的是行动Action,Action是具体的执行类,其内部的invoke方法是具体执行的代码逻辑。如果涉及到并发操作的话,可以在invoke方法内加入锁,或者直接在invoke方法上加上synchronized描述。

图中的黄色矩形代表的是供应商Provider,每个Provider中包含1个或多个Action,其内部的数据结构以HashMap来存储Action。首先HashMap查询的时间复杂度是O(1),符合我们对调用速度上的要求,其次,由于我们是统一进行注册,所以在写入时并不存在并发线程并发问题,在读取时,并发问题则交由Action的invoke去具体处理。在每一个Module内都会有1个或多个供应商Provider(如果不包含Provider,那么这个Module将无法为其他Module提供服务)。

途中蓝色矩形代表的是路由Router,每个Router中包含多个Provider,其内部的数据结构也是以HashMap来存储Provider,原理也和Provider是一样的。之所以用了两次HashMap,有两点原因,一个是因为这样做,不容易导致Action的重名,另一个是因为在注册的时候,只注册Provider会减少注册代码,更易读。并且由于HashMap的查询时间复杂度是O(1),所以两次查找不会浪费太多时间。当查找不到对应Action的时候,Router会生成一个ErrorAction,会告之调用者没有找到对应的Action,由调用者来决定接下来如何处理。

一次请求流程
通过Router调用的具体流程是这样的:

9.png

任意代码创建一个RouterRequest,包含Provider和Action信息,向Router进行请求。
Router接到请求,通过RouterRequest的Provider信息,在内部的HashMap中查找对应的Provider。
Provider接到请求,在内部的HashMap中查找到对应的Action信息。
Action调用invoke方法。
返回invoke方法生成的ActionResult。
将Result封装成RouterResponse,返回给调用者。
上述描述,引用:Android架构思考

这只是一个原理性的描述,不同的开源框架具体实现细节可能不同,想了解具体实现细节,可以研究一下开源路由代码。

开源框架实现:

  • WMRouter(美团方案)
  • ARouter(阿里方案)
  • Andromeda(支持跨进程)
  • DDComponentForAndroid(得到方案)
  • ModularizationArchitecture(支持跨进程)

UI 跳转问题

可以说 UI 跳转也是组件间通信的一种,但是属于比较特殊的数据传递。不过一般 UI 跳转基本都会单独处理,一般通过短链的方式来跳转到具体的 Activity。每个组件可以注册自己所能处理的短链的 Scheme 和 Host,并定义传输数据的格式,然后注册到统一的 Router 中,Router 通过 Scheme 和 Host 的匹配关系负责分发路由。但目前比较主流的做法是通过在每个 Activity 上添加注解,然后通过 APT 形成具体的逻辑代码。

目前开源的框架有:

  • ARouter 框架,通过注解方式进行页面跳转,阿里出品
  • ActivityRouter
  • WMRouter 美团开源框架
  • DeepLinkDispatch

资源冲突

  • 对于多个 Bussines Module 中资源名冲突的问题,可以通过在 build.gradle 定义前缀的方式解决:
defaultConfig {
   ...
   resourcePrefix "new_house_"
   ...
}
  • 多个mainfest问题
    每个可以独立运行的组件,都需要自己独立的mainfest,根据组件是否是独立运行,配置不同的Manifest路径
sourceSets {
    main {
        if (isDebug.toBoolean()) {
            manifest.srcFile 'src/debug/AndroidManifest.xml'
        } else {
            manifest.srcFile 'src/release/AndroidManifest.xml'
        }
    }
}

组件加载

  • 多个application的问题
    可以尝试用两种方式
    • 字节码插入模式是在dex生成之前,扫描所有的ApplicationLike类(其有一个共同的父类),然后通过javassist在主项目的Application.onCreate()中插入调用ApplicationLike.onCreate()的代码。这样就相当于每个组件在application启动的时候就加载起来了。

    • 反射调用的方式是手动在Application.onCreate()中或者在其他合适的时机手动通过反射的方式来调用ApplicationLike.onCreate()。之所以提供这种方式原因有两个:对代码进行扫描和插入会增加编译的时间,特别在debug的时候会影响效率,并且这种模式对Instant Run支持不好;另一个原因是可以更灵活的控制加载或者卸载时机。

对于1,2方案,具体可以查看得到(DDComponentForAndroid)方案中的实现。

业内一些组件化开源方案

  • 得到方案(DDComponentForAndroid)
  • CC(ComponentCaller),渐进式组件方案
  • ModularizationArchitecture
  • Atlas(阿里方案)

可行性(组件化与****项目如何结合)

综上所述,组件化是一个系统性工程,需要从编译,代码结构,通信框架,打包,测试等各个环节进行重构。
对于已有的app来说,采用渐进式的组件化是比较稳妥地方式。
下面针对****进行一些分析,将****中的module库进行一个划分

  • 基础库

      baseAppInfoLib
      common-lib-net
    
  • 功能库

      sohuUpload
      downloadsdk
      sohuMediaPlayerSdk
      sohuMediaPlayerLib
      sohuDanmuLib
      scaleview 
      sohuVideoEditor 
      sohuPrivilegeLib
    
  • 组件库

      qianliyanlib
      sohuVideoMobile
      fivesixapp
    

短期规划

目前拍摄模块(qianliyanlib)相对独立,先从拍摄模块进行解耦

首先与拍摄模块有直接耦合关系的是上传视频组件,上传视频组件中,耦合了一部分视频转码的状态展示。
可以考虑先将拍摄模块与上传模块先行进行组件化。两个模块中的状态互相依赖,则通过注册各自模块提供的service进行接口编程方式实现。

后期如果有新的需求,并且是比较大的功能模块,那么就直接用组件模式进行开发。

通过这个组件化后,整个拍摄模块与上传模块,就可以单独调试与运行,在解耦过程中,逐步探索组件化的一些细节。为后期整体app组件化积累经验。

后期规划

可以按照功能模块进行划分如下:

  1. 红包模块
  2. 搜索模块
  3. 商场订单模块
  4. 首页框架模块
  5. 小视频播放模块(包含列表与详情)
  6. 视频信息流模块(包含首页tab,热点tab,订阅流,包含流播放,视频详情中播放)
  7. 个人中心模块

参考资料

http://blog.spinytech.com/2016/12/28/android_modularization/

https://github.com/luckybilly/AndroidComponentizeLibs

https://mp.weixin.qq.com/s/6Q818XA5FaHd7jJMFBG60w

http://blog.zhaiyifan.cn/2016/10/20/android-new-project-from-0-p11/

https://mp.weixin.qq.com/s/6Q818XA5FaHd7jJMFBG60w

http://zjutkz.net/2016/10/07/%E5%85%B3%E4%BA%8EAndroid%E4%B8%9A%E5%8A%A1%E7%BB%84%E4%BB%B6%E5%8C%96%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%9D%E8%80%83/

http://www.trinea.cn/android/didi-internationalization-android-evolution/

http://tangpj.com/2018/07/22/calces-componentization/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,568评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,473评论 2 59
  • iOS组件化方案探索 一、什么是组件化? 1、什么是组件? "组件"一般来说用于命名比较小的功能块,如:下拉刷新组...
    yehot阅读 15,976评论 14 207
  • 从小到大,我认识很多人,也有很多朋友,有高的矮的胖的瘦的,有男的女的有文化的和文化水平不太高的,有活泼的有喜欢安静...
    田野虎阅读 315评论 0 3
  • 题目写的是2018年刚开始的4个小时,但其实我真正要写的应该是从2017年最后一天的下午7点到2018年刚开始的第...
    CY_M阅读 119评论 0 1