特别说明
当前博客平台账号已废弃,如果有使用细节问题请前往我新博客平台进行讨论交流。
个人博客平台 HuRuWo的技术小站
文章首发于个人博客HuRuWo的技术小站,如果本文非vip用户无法完全浏览或者图片无法打开,可前往个人博客文章地址查看文章并留言讨论。
个人博客文章地址全面组件化---DDComponentForAndroid分析(1)
更多技术文章访问本人博客HuRuWo的技术小站,包括 Electron从零开发 Android 逆向 app 微信数据抓取 抖音数据抓取 闲鱼数据抓取 小红书数据抓取 其他软件爬虫 等技术文章
前言
关于组件化,其实从毕业到现在都在使用组件化的开发方式。因为公司有多个android开发人员,平时需要协同开发。但是之前的组件化方案都是公司的老员工留下来的东西,虽然也能达到组件化开发的目标。但使用起来不是很方便,简单来说就是---僵硬。
后来前辈离职,具体前辈突然离职了,作为应届生的我。正好这是一个机会,于是我准备开始实现自己的方案。
半个月前看到了简书上的文章:
Android彻底组件化demo发布
Android彻底组件化方案实践
颇受启发,虽然作者写的很详细。但是水平问题,一时看懂有点难。所以上下分析了将近三天,才有一点眉目。由于作者是从一个开发者视角讲解的框架,所以作为使用者理解有点模糊。
我打算从使用者的角度来分析这个框架,从一个新的项目如何一步一步的引入这个组件化方案。水平有限,如有误差,还望谅解。
1.全面组件化---DDComponentForAndroid分析(1)
2.全面组件化---DDComponentForAndroid分析(2)
3.全面组件化---DDComponentForAndroid分析(3)
一点姿势补充(摘抄)
组件化和模块化以及插件化
模块化是一种指导理念,其核心思想就是分而治之、降低耦合。而在Android工程中如何实施,目前有两种途径,也是两大流派,一个是组件化,一个是插件化。
组件化需要解决的几大问题
- 代码解耦。如何将一个庞大的工程拆分成有机的整体?
- 组件单独运行。上面也讲到了,每个组件都是一个完整的整体,如何让其单独运行和调试呢?
- 数据传递。因为每个组件都会给其他组件提供的服务,那么主项目(Host)与组件、组件与组件之间如何传递数据?
- UI跳转。UI跳转可以认为是一种特殊的数据传递,在实现思路上有啥不同?
- 组件的生命周期。我们的目标是可以做到对组件可以按需、动态的使用,因此就会涉及到组件加载、卸载和降维的生命周期。
- 集成调试。在开发阶段如何做到按需的编译组件?一次调试中可能只有一两个组件参与集成,这样编译的时间就会大大降低,提高开发效率。
- 代码隔离。组件之间的交互如果还是直接引用的话,那么组件之间根本没有做到解耦,如何从根本上避免组件之间的直接引用呢?也就是如何从根本上杜绝耦合的产生呢?只有做到这一点才是彻底的组件化。
整个框架结构
- app是主项目,负责集成众多组件,控制组件的生命周期
- reader和share是我们拆分的两个组件
- componentservice中定义了所有的组件提供的服务
- basicres定义了全局通用的theme和color等公共资源
- basiclib中是公共的基础库,一些第三方的库(okhttp等)也统一交给basiclib来引入
- 图中没有体现的module有两个,一个是componentlib,这个是我们组件化的基础库,像Router/UIRouter等都定义在这里;另一个是build-gradle,这个是我们组件化编译的gradle插件,也是整个组件化方案的核心。
如何使用到自己的全新项目中
首先下载源码
https://github.com/luojilab/DDComponentForAndroid
主要是用来做参考和对比的,也可以不下载
新建自己的项目
- 新建一个名为"zujianhua"的工程,并导入一些基本依赖库。
为了方便起见,我使用了参考源码中的代码,后期会改为自己的代码。
主要是三个library:
:componentservice
, :basicres
, :basiclib
依赖关系:app-->componentservice-->basicres-->basiclib
作用:
- componentservice: 组件服务,用于数据传输
- basicres: 公共资源
- basiclib: 公共代码
- 导入插件 build-gradle 非必需
为什么说非必需,因为可以在其他地方导入。发布到一个maven 仓库中,提供引用即可。
为了方便分析,这里先导入到项目里,只发布到本地文件系统。(后面讲解如何发布到meven)
关于搭建maven 仓库
build-gradle 是一个gradle 插件工程,需要生成对应的jar文件提供给.gradle 文件使用:
点击右上角-->Gradle-->buid-gradle-->Tasks-->upload-->uploadArchives
点击即可,等待:
出现新的文件夹:repo,也就是插件发布的地址。
- 关键步骤,使用插件
- 首先添加仓库地址,并配置引入
repositories {
google()
jcenter()
maven {
url uri('./repo')
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
classpath 'com.mrzhang.andcomponent:build-gradle:0.0.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
- 在根目录的gradle.properties文件中,增加属性:
mainmodulename=app
意义:告知插件 工程主项目
具体调用代码: 在gradle插件工程里面
- app组件build.gradle 使用 插件
- 新增 gradle.properties 文件 配置该 组件的信息
暂时先配置:
isRunAlone=true
isRunAlone 标记当前是否需要单独调试,为ture就表示需要单独调试。不会拉起其他组件。
2.去掉 apply plugin: 'com.android.application'
改为 apply plugin: 'com.dd.comgradle'
表示应用自己的插件。
而这个插件会判断当前你应该是
apply plugin: 'com.android.application'
还是
apply plugin: 'com.android.library'
运行app 测试一下:
报错
Error:Execution failed for task ':app:transformClassesWithComponentCodeForDebug'.
> you should set applicationName in combuild
原因没有配置combuild
,因为在插件中需要combuild 判断组件加载方式。
在什么时机加载组件以及如何加载组件?目前com.dd.comgradle提供了两种方式,字节码插入和反射调用。
- 字节码插入模式是在dex生成之前,扫描所有的ApplicationLike类(其有一个共同的父类),然后通过javassist在主项目的Application.onCreate()中插入调用ApplicationLike.onCreate()的代码。这样就相当于每个组件在application启动的时候就加载起来了。
- 反射调用的方式是手动在Application.onCreate()中或者在其他合适的时机手动通过反射的方式来调用ApplicationLike.onCreate()。之所以提供这种方式原因有两个:对代码进行扫描和插入会增加编译的时间,特别在debug的时候会影响效率,并且这种模式对Instant Run支持不好;另一个原因是可以更灵活的控制加载或者卸载时机。
这两种模式的配置是通过配置com.dd.comgradle的Extension来实现的,下面是字节码插入的模式下的配置格式,添加applicationName的目的是加快定位Application的速度。
所以我们在build.gradle
增加:
combuild {
applicationName = 'com.example.administrator.zujianhua.AppApplication'
isRegisterCompoAuto = true
}
接下来开始第二部分:拆分业务,如何让单独的组件跑起来
新建一个module
1.取名OneCompone
表示是第一个
依赖关系OneCompone-->componentservice-->basicres-->basiclib
- 修改组件
gradle.properties
组件的工程目录下新建文件gradle.properties文件,增加以下配置:
isRunAlone=true
- 应用组件化编译脚本
同上,引入apply plugin: 'com.dd.comgradle'
,并配置combuild
-
为了解决模块的单独运行,插件需要配置runalone 资源文件。
专门配置组件单独运行的资源。
具体如下:在组件内 于java同级新建runalone 目录
(如想修改,可查看插件源码)
为方便起见,我直接复制源代码中的文件夹。
sync一下,再看一下目录结构:
我们可以尝试单独安装 两个组件到手机上:
这样我们就做到了单独调试。
接下来如何数据传递:
- 引入路由框架
componentlib
componentservice
>componentlib
整个框架结构变成:
路由:这个应该可以修改成自己的路由框架
- 注意这里在debug 时候需要引入onecomponent
所以修改app的gradle.properties
文件
isRunAlone=true
debugComponent=onecomponent
debugComponent 就表示 引入onecomponent 组件 在debug下
- 配置路由添加Service
service在这里作为一个数据提供的角色,可以在任何地方通过路由获取到service。
- componentservice 配置oneservice接口,也就是面向接口提供数据(oneFragment)
public interface OneService {
Fragment getOneFragment();
}
- 在onecompent 里写一个oneservice的实现
public class OneServiceImpl implements OneService {
@Override
public Fragment getOneFragment() {
return new OneFragment();
}
}
- 注入到Router的hashmap中
public class OneAppLike implements IApplicationLike {
Router router = Router.getInstance();
@Override
public void onCreate() {
//注入
router.addService(OneService.class.getSimpleName(), new OneServiceImpl());
}
@Override
public void onStop() {
//移除
router.removeService(OneService.class.getSimpleName());
}
}
- 调用OneAppLike 注入:
由于组件分离,无法直接调用。所以提供两种调用方式:
-
isRegisterCompoAuto
设置为true,由com.dd.comgradle
插件完成引用。
(如何做到的,可以看接下来的插件分析) -
isRegisterCompoAuto
设置为false,则需要手动加载。
假设 app需要加载 one ,需要:
public class AppApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
//如果isRegisterCompoAuto为false,则需要通过反射加载组件
Router.registerComponent("com.example.onecomponent.applike.OneAppLike");
}
}
同理,如果a需要加载b。需要在a的application里面注册b。
- 如何拿到数据
前面都是提供数据,拿到数据很简单。
Router router = Router.getInstance();
if (router.getService(OneService.class.getSimpleName()) != null) {
OneService service = (OneService) router.getService(OneService.class.getSimpleName());
Fragment fragment = service.getOneFragment();
- 额外的,动态卸载和加载组件
调用Router.registerComponent(组件注册类名)
和Router.unregisterComponent(组件注册类名)
尝试启动:
UI跳转
这部分其实没什么讲的,而且各个路由框架有自己的跳转方式以及配置方法。
这里看一下源码中的路由框架如何跳转的
新建twocomponet,配置同上
添加twoactivity,配置runalone
TwoUIRouter继承IComponentRouter接口,实现路由跳转内容
TwoApplike 同样把TwoUIRouter加入到Router中
设置isRegisterCompoAuto 为true或者 手动注册。
Router.registerComponent("com.huruwo.twocomponent.applike.TwoAppLike");
6.代码跳转 从onefragement-->twoactivity
UIRouter.getInstance().openUri(getActivity(), "component://two", null);
效果:
这篇文章已经够长了,先写到这里吧。
总结
这个部分解决了组件化的几个难点:
- 代码解耦
- 组件的单独调试
- 组件的数据传输
- 组件之间的UI跳转
- 组件的生命周期
这里遗留了两个问题:
- 集成调试
- 代码隔离
将在下篇文章中引入解决。
相关源代码已同步到github
https://github.com/HuRuWo/ZuJianHua