组件化实践详解(二)

在上一篇文章《组件化实践详解(一)》中我们介绍了组件化实践的目标和实践步骤,本文继续说说关于组件化实践遇到的问题及思考。

本文概述

1、组件内的架构设计

这条本来我是不想写的,但是很多组件化的文章里都会费尽心思的写组件内的架构设计。

那我也谈一谈我的看法:首先回归初心,想想组件化的目的,为了各个业务组件可以单独运行。划重点:目的是单独运行,把之前在App Module的代码挪到自己单独的Module,然后能够独立运行;而不是大面积重构!!我也相信对于大部分团队,实际上并没有很多的时间去做重构,尤其是在做组件化的过程中同时大面积重构,确定做了风险评估吗

对于组件化的整体设计,需要遵循制定的规则,但是对于组件内的架构设计,实际上不需要特殊的要求,代码你爱怎么写就可以怎么写,不管你使用MVC、MVP还是MVVM,根据各自情况合理选择就好了。这个话题本来就不属于组件化项目的范畴。

2、调试方式

假如之前的几步我们都顺利完成,现在整个Project已经变成了下图整个样子。

那我们的Module要怎么才能跑起来呢?

  1. 我们创建Module的时候如果选择的是Application工程,毋庸置疑肯定是能跑起来的,但是却没有办法被真正的Host宿主引用了
  2. 创建Module的时候选择的是Library工程,可以被引用但是无法自己单独运行

二者结合呢?借助冯大大在MDCC上的分享,将Module分为两个模式:Release模式和Debug模式,Release模式下作为Library,供宿主依赖;而在Debug模式下则作为Application工程,自己单独运行

build.gradle中根据gradle中的一个属性值来判断处于哪种模式下:

    if (isDebug.toBoolean()) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }
     sourceSets {
        main {
            if (isDebug.toBoolean()) {
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/release/AndroidManifest.xml'
            }
        }
    }   

在Release中的AndroidManifest配置默认启动的主Activity。

这样一来调试的问题也就解决了。但是实际上这仅仅是一个Demo雏形,我们思考几个带出来的问题:

  1. 如果我这个业务组件涉及了别的好几个Module,也就是我自己使用Debug模式运行的时候别的Module需要使用Release模式,那么每个Module都需要一个单独的变量去控制,Module过多导致变量过多,出错可能性陡增
  2. 直接的源码依赖:假如大家在一个分支上开发不同的组件,有极大可能会导致冲突或者调用异常导致影响研发效率;当然肯定有人会说一般不会在一个分支开发,确实,但做了组件化之后分支数其实是可以变少的
  3. 如果是Host直接源码依赖子Module,那当子Module作为Application工程运行的时候一旦你Build项目,那各种美如画的报错让你苦不堪言

如何对这个情况做优化:各个独立Module提供稳定版本的aar

  • 单独的业务组件开发完成之后,记录一个版本号同时提供一个稳定的aar
  • 别的依赖模块直接去compile需要的业务组件的aar即可
  • Host宿主则是compile这些业务组件稳定的aar
  • 依赖于稳定的aar,那么自己的业务组件开发并不会影响到别人,而且Build的时候宿主也不会出错
  • 针对众多的aar包,最好建一个maven仓库来统一管理
  • 生成及上传aar包这步,可以写个脚本帮忙生成及上传

3、工程化经验

以下介绍些关于工程化的经验

3.1 Application

部分业务组件一定会遇到依赖一些三方组件需要提前初始化的情况,正常我们的做法都是在应用的Application中做的。此时我们在独立的Module开发,没有了应用的Application,那么可以自己创建一个Module的Application,以下提几种实现的思考

  1. Module的Application只工作在Debug模式下,而在宿主中也同样注册需要的三方组件,Release模式下没有这个Application则不会重复注册
  1. Module的Application同时工作于这两种模式下,但是真正打包生成Apk之后实际系统认可的只有App的Application,而别的Module Application只是被系统认为是一个没有特殊意义的普通类。那我们可以在真正的Application方法调用的时候通过反射调用Module Application的相应方法
  1. 源于第二种方案,区别在于将Module组件中的初始化工作,延迟到组件使用的时候才去初始化,好处就是使用时才加载

备注:而怎么判断调用业务Module呢?两个场景:UI跳转或者方法调用,这两种判断可以使用路由框架来协助。

3.2 Application与Tinker的兼容

Tinker作为热修复的可靠解决方案,想必很多App都会集成,但是Tinker集成稍繁琐的地方就在于:为了确保Application也能修复,需要改造Application,改造完成之后打的包出来真正的Application已经被修改,而写上了我们逻辑的Application实际上变成了一个普通类,只是相应方法被真正的Application调用

那我想把Tinker这个模块也单独作为一个Module来使用,能行吗?首先来思考一个问题:Application要在哪里,Library中还是Host

答案是Library中,因为各个Module中可能会存在不方便获取Context的场景,解决方案之一就是使用Application的Context。假设Application放在Host中,那Module肯定是无法使用的。当然刚刚我们说到Module自己的Application,但是别忘了如果Module没有呢?

把Application放到Library中也不是说移就能移:

  • 正常情况下我们会发现Host Application中也有一大堆的逻辑或者是组件初始化,而这些组件要全被移出去还需要 a long long time
  • 代码中有一大堆通过Host Application拿到的Context,现在忽然移出去,必定一大堆报错

面对如此抉择,那到底是移还是不移?一个好方法是原来Host的Application只做较小改动:并不移出来之前的各种逻辑和组件,而是作为一个普通类,在Library中的Application方法执行时去回调相应Host Application的方法;随后在组件化的过程中逐渐的移出来这些业务和组件。这样的改动成本最小又满足了当下的需要

3.3 资源冲突

在拆分出来多个Module或者新建Module进行开发,新建资源的时候可能会有命名的冲突,对Gradle熟悉的同学可能会表示使用resourcePrefix来进行限定,但是坦白说效果一般,倒是不如在编码规范中加上一条以相应Module的标示作为命名的前缀

3.4 ButterKnife的使用

ButterKnife——相信很多同学都用过,这是一个注解框架,一般在绑定View的时候使用,减少了很多无意义的代码。在正常开发中我们用起来也是6的飞起!然而当ButterKnife跑在Library工程中的时候各种Build失败就出现了:原因在于Android Library中的R文件字段并不是常量,Module在Debug模式下是Application工程可以开心玩耍,等真正集成的时候切换回Release模式就呵呵哒了

在ButterKnife8.0之后也支持了在Library中使用,解决方式就是同时生成了一个R2,这个就是常量,因而可以在Library工程中使用。

推荐使用Android ButterKnife Plugin Plus插件,方便的一键生成然后将R更改为R2;或者自己仿照去写一个AS插件,直接生成R2

备注:同时注意R2只能使用在注解中,因而点击事件要写成这样:

    @OnClick(R2.id.tv_back_selerole)
    public void onClick(View view) {
        if (view.getId() == R.id.tv_back_selerole) {
            dealBack();
        }
    }

3.5 避免过大的基础库

这个问题的引出是在组件化相对成熟的阶段,已经初步完成了我们的预期目标,但是细化的过程中逐渐意识到一个问题:Library库越来越大,其实单独的一个Module并不会需要那么多的组件,但是单独Module运行的时候还是被引用上了,也会拖慢单独Module的执行速度

于是我们提出了另外一个名词:去中心化。将基础库进行细粒度的拆分,将开发中一定会用到的例如网络请求、EventBus、公共类等放在了Library中,而将别的不常用三方组件如地图等移出去,只供需要的Module去依赖,而普通的Module则只依赖常用的Library。

这样可以有效的避免Library逐渐变得膨胀,也给各Module只去依赖自己需要的特定Library能力

4、组件化成效

历经千辛万苦我们对项目做了组件化实践,那究竟收获了哪些好处呢?

  • 代码结构层次清晰明了;
  • 组件间界限清晰、有明确边界,低耦合;
  • 开发过程体验好,快速编译;
  • 版本周期内没有动到的组件快速回归;
  • 方便A/BTest;

原文发布于微信公众号 - 双十二技术哥(gh_b0e7544783e2)

原文发表时间:2018-04-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Jackson0714

架构之旅1 - 扣减库存

生活中,我们总是用各种电商app抢购商品,但是库存数是很少的,特别是秒杀场景,商品可能就一件,那如何保证不会出现超卖的情况呢?

1545
来自专栏FreeBuf

OpenSSH曝高危漏洞,Linux主机面临暴力破解威胁

OpenSSH软件被爆出一个简单却高危的漏洞,攻击者可以在短时间内进行数千次的登录尝试。 ? OpenSSH是最流行的Linux系统进行远程控制的软件。一般来说...

2947
来自专栏Laoqi's Linux运维专列

“考虑不全面”导致的大问题!!!

今天做了个小实验,由于自己的考虑不全面,导致了大问题的产生! 因为此问题我在全网几乎都未找到满意的答案,所以现在打算分享出来! 问题原因: 1 2 ...

4168
来自专栏飞雪无情的博客

使用AlarmManager设置的定时服务在Android4.4上可能不准确了

Android4.4在前天发布了,随着Android4.4的源代码的放出,相信会有更多的手机会慢慢的升级到Android4.4,作为苦逼的Android开发人员...

963
来自专栏MongoDB中文社区

MongoDB 3.6中的新功能 (1) - 发展的速度

New in MongoDB 3.6. What’s New in MongoDB 3.6. Part 1 – Speed to Develo

1421
来自专栏恰童鞋骚年

《大型网站技术架构》读书笔记之五:万无一失之网站的高可用架构

此篇已收录至《大型网站技术架构》读书笔记系列目录贴,点击访问该目录可获取更多内容。

1433
来自专栏企鹅号快讯

黑客XSS攻击原理 真是叹为观止!

网络世界中的黑客XSS攻击原理 在现实世界的攻击实例中,保存型XSS漏洞可能会造成严重后果的特点表现得非常明显。 当收件人查阅电子邮件时,邮件内容在浏览器中显示...

52210
来自专栏進无尽的文章

扒虫篇-此构建版本无效

一个公司的项目 1.0 版本被苹果拒绝了,问题修改好,再次上传后,构建版本时,发现上传的版本都是提示:此构建版本无效。

1201
来自专栏更流畅、简洁的软件开发方式

js的动态加载、缓存、更新以及复用(二)恼人的命名冲突

  上一篇发出来后得到了很多回复,在此首先感谢大家的热情捧场!有的推荐第三方框架,比如 In.js、requrieJS、sea.js、lab.js等。这个开阔了...

2468
来自专栏企鹅号快讯

分布式金融系统调优实践

引言: 某银行采用分布式架构对其核心产品系统进行重构,重构后该系统由多个技术模块和业务模块组成,存在联机交易、异步消息、自动任务、批量等交易形态。各模块之间交互...

2418

扫码关注云+社区

领取腾讯云代金券