| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 移动开发 -> Architecture(5)电商APP组件化探索,快点来白嫖 -> 正文阅读 |
|
[移动开发]Architecture(5)电商APP组件化探索,快点来白嫖 |
组件化缘由记得刚开始接触Android开发的时候,只知道MVC分层架构,而且感觉Model,View以及Controller太简单了,也能称之为分层架构,随便写就是MVC。就像在接触设计模式之前,你可能已经写了无数个单例模式,只是那个时候你可能并不知道,你已经在用设计模式了,你不会去想是用DCL还是使用内部类实现的单例优雅。 后来当一个类中的代码上千行之后,就开始想着抽取公共方法作为工具类,使用封装、继承以及多态来优化自己的代码,直到随着业务的发展,在View层的逻辑越来越多,无法抽取时,发现MVC的天花板其实很低,Activity跟Fragment作为View层经常会跟Model层纠缠不清,及时进行抽取之后,也还是很臃肿。MVP的出现,彻底解决了这个问题,解耦Model层跟View层,使得整个项目的代码显得更加简洁。 在项目初期的的时候,感觉MVP还是很不错的,当项目逐渐变大的时候,每次你改动了很小的一部分,你也需要重新编译整个APP,举个例子,就拿购物车来说,我修改了数量框的样式,我需要重新编译整个APP,为了加快速度,我可能要开启InstantRun,可能要使用Freeline来加速编译,这并不是我想要的,而且在使用InstantRun之后,output目录下生成的apk是差量包,只能供开发调试,给测试是无法安装的,我要是想通过脚本上传到fir给测试人员,那又得打一个全量的包,并且InstantRun也不是很稳定。 组件化效果毫无悬念,组件化势在必行,在网上看了很多相关的资料,对组件化有一个初步的了解,然后就开始组件化了,下面1以我自己的项目为例,放两张组件化之前跟之后的图对比一下。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tl4NVlL8-1630660709055)(https://user-gold-cdn.xitu.io/2018/1/21/161189f8e013e5f5?imageView2/0/w/1280/h/960/ignore-error/1)] 可以明显的发现我们的Module变多了,就像MVC切换到MVP之后,需要写很多的Presenter组件化最大的好处就是可以模块可以单独开发调试,这样效率一下子就上来了,还是拿购物车举例,购物车实际上就只有一个界面,也就是一个Fragment,加上启动页跟Fragment的父Activity,也就两个界面,可以说想慢都慢不下来,下面就我在组件化过程中遇到的一些问题进行总结一下。 正文指导思想组件拆分组件化的目的在于将一个project划分成业务组件、基础组件、路由组件。其中业务组件是相互隔离的,可以单独调试,基础组件提供业务组件所公用的功能,路由组件为业务组件之间通信提供支持。 一般来讲,一个APP可以由一个app壳,然后集成多个Module,这是理想的情况,但是从运营的需求到产品的设计到UI出图,可能你就会对组件化很绝望,并不是那么的理想,很多时候我们程序入口所在的Module实际上跟其它很多Module是关联的,实际上没法拆分,本文将会以这种比较复杂的情况进行组件化分析。 组件隔离组件化的一个很大的特性在于可以单独调试,但是由于业务组件之间的隔离,所以导致了多个组件之间无法进行通信,其实我觉得是很正常的,既然是单独调试,就必然不应该跟其它的Module间进行依赖,不管是编译期还是运行期都应如此,不然组件化就没有任何意义了,但是由于我们的业务组件都是相互关联的,如果不依赖其他的组件的话,作为一个单独的APP运行有时候是需要参数的,鉴于此,我们可以在Application初始化的时候,新增一个页面作为参数配置,或者直接在Application中固定写死。 核心法则不管我们如何划分,如何依赖,组件间的关系都要严格遵守一个准则:编译器隔离,运行期按需依赖。 整体架构[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rInEBvxH-1630660709057)(https://user-gold-cdn.xitu.io/2018/1/21/161189f8e037580c?imageView2/0/w/1280/h/960/ignore-error/1)] 通过组件化将项目按照业务进行化分成GoodsModule,CartModule,UserModule,OrderModule四个模块,模块间通过RouterModule进行通信,也就是说业务组件依赖于路由组件,RouterModule依赖于Base,也就是BaseModule,LibraryModule,基础库跟第三方库,然后MainModule实际上相当于程序的入口跟容器,通过MainModule依赖上述四个Module,完成整个APP的打包。 当然在单独调试的时候,GoodsModule,CartModule,UserModule,OrderModule又各自成为一个APP,可以单独进行调试,这样就实现了APP的组件化,下面就组件化过程中遇到的一些问题总结一下。 组件化分析在组件化的过程中,由于Module之间是隔离的,所以就产生了一系列问题,现在就组件化前后的遇到的问题总结如下:
接下来会根据这几个问题,提出对应的解决方法 组件划分业务划分由于我们做的是一个电商项目,网上也查找了很多资料,感觉他们举的例子都有些过于简单,因为模块间基本上没有什么耦合,所以很好拆分,不过还是很感谢他们提供了一种解决思路。玩过京东,淘宝都知道,大致分为几个大的模块:商品模块,购物车模块,订单模块,用户模块。没错,我也是这么拆分我们APP的。但是拆着拆着就发现问题了,模块间耦合性太高,我们过了SplashActivity之后就是MainActivity,看图说话 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rvbRGnON-1630660709059)(https://user-gold-cdn.xitu.io/2018/1/21/161189f8e020de07?imageView2/0/w/1280/h/960/ignore-error/1)] 所以网上的一些一进来就是一个空的APP壳的方法并不适用,从一开始就遇到了这个棘手的问题,有点尴尬,按照之前的模块划分,在用户登陆的情况下MainModule一进来就必须拿到GoodsModule,CartModule以及UserModule中的三个Fragment。所以首先必须得解决这个问题,很显然之前的使用一个APP壳来合并多个Module的情况并不适用,起初我直接定义了一个MainModule,然后让他直接引用多个Module,那么MainModule就承担了APP壳的功能,这样一来,就可以解决MainModule对其它Module的引用问题,但是违背了组件化的业务组件隔离的原则。 所以不能让MainModule依赖另外三个Module,但是如果我不引用其他的Module,那么很显然我无法拿到这四个Fragment的引用,有一点可以很明确,那就是编译期业务Module之间必须不可见,这点是毫无疑问的。但是运行期是可见的,因为所有的Module在运行期间肯定都是通过直接或者间接依赖,不然有些Module就没用了,在运行时获取实例,那么很自然地就会想到反射了,没错就是反射。 依赖划分除了业务模块之外,我们还会有一些公用的工具类以及资源文件,也就是Base类,比如说多个Module共同使用的资源文件,我们都可以放在一个Module里面,另外就是还有第三方依赖,这里我新建了两个Module一个是BaseModule,一个是LibraryModule。整体关系如下
模式切换定义开关切换的时候需要一个开关,来表示是单个Module间运行还是多个Module间运行,很容易想到是一个布boolean类型的标志,可能你也想到了,在gradle.properties中来定义,网上好像都是这么做的,实际上我们还可以在BaseModule以及LibraryModule定义,原因很简单,只需要所有的Module中都能够访问就行了,只要遵循这个原则都是OK的,只是在gradle.properties中定义跟使用都比较方便。
这里我不仅仅定义了isModuleRun,还定义了isDebug,是不是感觉有些奇怪,不是可以通过BuildConfig.Debug来判断当前是否是Debug模式么,因为我们的url配置信息都是写在BaseModule中以便于所有的Module调用,他是一个Library,关于Library这里还有一个问题注意下,由于Library的Module打包方式是使用release模式打包的,所以BuildConfig.Debug永远是false,所以我们需要额外定义一个变量isDebug,然后手动在Debug跟Release中进行切换,然后在BaseModule的gradle中进行判断
使用开关ApplicationisModuleRun为false的时候,Application跟AndroidManifest都是以Library的形式参与编译,不需要启动的Activity以及自定义的Application反之则需要。 isModuleRun=false 无序修改
isModuleRun=false 在main/debug目录下新建一个AndroidManifest.xml文件
引用方式 在Module的gradle目录下进行引用 修改插件
新增applicationId
切换AndroidManifest文件
资源冲突假如我们在CartModule中定义了一个Application,然后在当前Module中的strings.xml中定义了app_name,同时在OrderModule中的strings.xml中也定义了这个app_name,那么合并你的时候就会出现冲突,我们只可以通过将上述字段分别改成cart_name跟order_name来解决这个问题,在严格的开发规范下,可以通过这种差异化命名来解决,因为不同的Module基本上资源文件的名称基本都不一样,即时冲突也是少量的冲突,很容易解决。 当然除了这种方式之外可以在build.gradle中给资源文件名添加前缀
可以强行检查,命名都需要价格前缀,这样反而违背了组件化的初衷,使得操作变麻烦了,不过感觉这种方式不是很有必要,当然有时候还可能出现图片名字相同,这个其实可以还原到组件化之前的项目中分析,是不可能发生的事情,所以归根到底还是没有良好的开发规范跟开发习惯造成,没必要为这种去做一些修改,毕竟约定大于配置。 依赖配置通过最开始的整体架构图可以看出来,凡是能够在Library跟Application之间进行切换的Module毫无疑问是需要依赖我们Base的两个Module的,其实可以合并成一个Module,我这里分了两个,一个是BaseModule,一个是LibraryModule。下面通过build.gradle中的配置来梳理一下他们的依赖关系: MainModule
编译期间组件进行隔离,所以MainModule只依赖了RouterModule,刚才说的还有在运行期按需依赖,这里是通过gradle的脚本实现控制的
BusinessModule这里指的是Goods/Cart/User/OrderModule,其实是平行的
业务Module依赖于RouterModule RouterModule
RouterModule依赖了LibraryModule BaseModule
BaseModule作为一个基础库,依赖了LibraryModule LibraryModule这个作为最底层的劳苦大众,实际上就是提供了一个依赖,所以就没有什么好依赖,只能自己跟自己玩儿。
组件通信其实在当初进行模块划分的时候,是根据业务来的,所以当我们进入到一个模块之后,大部分逻辑应该还是在这个模块内进行处理的,但是偶尔还是会跟别的Module进行打交道,看一个界面 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UhB63xrb-1630660709062)(https://user-gold-cdn.xitu.io/2018/1/21/161189f8e008ae32?imageView2/0/w/1280/h/960/ignore-error/1)] 就拿GoodsModule跟CartModule来说,这两个Module是可以进行相互跳转的,在GoodsModule的列表页面点击购物车图标可以进入到CartModule的购物车列表,购物车列表点击商品也可以进入GoodsModule的商品详情页。除了这个跳转实际上还有变量的获取,比如在首页,我需要同时获取到GoodsModule中的HomeFragment、SortFragment,CartModule中的CartFragment,UserModule中的MineFragment。我是在MainModule中直接依赖了四个业务Module,实际上可以不这样,我们也可以使用Arouter来进行获取Fragment的实例。 获取实例其实这里的实例大多数情况下指的就是Fragment,下面以Fragment为例,别的实例如法炮制即可
由于模块间是隔离的,所以我们没办法直接创建Fragment的实例,那么这个时候其实很容易想到的就是反射,发射可谓无所不能,下面贴一下代码。
Arouter是阿里巴巴退出的一款路由框架,在组件中进行路由操作表方便,下面举例说明 目标Fragment中加入注解
在任何地方获取实例
方法调用在不同的Module之间都存在方法的调用,我们可以在每个Module里面定义一个接口,并且实现这个接口,然后在需要调用的地方获取到这个接口,然后进行方法调用即可。为了统一管理,我们把每个Module的接口都定义在RouterModule里面,然后由于各个业务Module都依赖于这个RouteModule,然后只需要通过反射获取到这个接口,进行方法调用就可以了。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0NeZmnD5-1630660709064)(https://user-gold-cdn.xitu.io/2018/1/21/161189f8e03411d1?imageView2/0/w/1280/h/960/ignore-error/1)] ModuleCall Module之间回调的接口
Service接口继承自ModuleCall可以定义一些回调方法供本身之外的其他Module进行调用
Impl实现类则是对应在每个Module中的具体回调,是实现Service接口的直接子类
下面还是通过反射跟Arouter两种方式进行说明 学习分享,共勉Android高级架构师进阶之路 CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》 题外话,我在阿里工作多年,深知技术改革和创新的方向,Android开发以其美观、快速、高效、开放等优势迅速俘获人心,但很多Android兴趣爱好者所需的进阶学习资料确实不太系统,完整。今天我把我搜集和整理的这份学习资料分享给有需要的人
外话,我在阿里工作多年,深知技术改革和创新的方向,Android开发以其美观、快速、高效、开放等优势迅速俘获人心,但很多Android兴趣爱好者所需的进阶学习资料确实不太系统,完整。今天我把我搜集和整理的这份学习资料分享给有需要的人
[外链图片转存中…(img-wZKNvg0i-1630660709064)]
[外链图片转存中…(img-W9GJVpLE-1630660709065)]
[外链图片转存中…(img-TtF29xzh-1630660709066)]
|
|
移动开发 最新文章 |
Vue3装载axios和element-ui |
android adb cmd |
【xcode】Xcode常用快捷键与技巧 |
Android开发中的线程池使用 |
Java 和 Android 的 Base64 |
Android 测试文字编码格式 |
微信小程序支付 |
安卓权限记录 |
知乎之自动养号 |
【Android Jetpack】DataStore |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 12:51:49- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |