您的位置:首页 > 移动开发 > Android开发

学习笔记:Android彻底组件化方案实践——Android技术杂货铺

2017-09-28 18:12 253 查看
Android彻底组件化方案实践——Android技术杂货铺

模块化的实现的两种途径:

组件化:组件可以单独运行,项目中缺少任何一个组件都可以运行。组件在编译的时候可以动态添加或修改,在运行的时候不可以。

插件化:插件在编译和运行的时候都可以动态添加或修改。

实现组件化需考虑的问题:

代码解耦

组件单独运行

数据传递

UI跳转

组件的生命周期

集成调试

代码隔离

1.代码解耦

使用IDE中的multiple module这个功能,我们很容易把代码进行初步的拆分。

基础库library,这些代码被其他组件直接引用。比如网络库module可以认为是一个library。

Component,这种module是一个完整的功能模块。

2.组件的单独调试

其实单独调试比较简单,只需要把apply plugin: ‘com.android.library’切换成

apply plugin: ‘com.android.application’就可以,但是我们还需要修改一下AndroidManifest文件,因为一个单独调试需要有一个入口的actiivity。 我们可以设置一个变量isRunAlone,标记当前是否需要单独调试,根据isRunAlone的取值,使用不同的gradle插件和AndroidManifest文件,甚至可以添加Application等Java文件,以便可以做一下初始化的操作。

为了避免不同组件之间资源名重复,在每个组件的build.gradle中增加resourcePrefix “xxx_”,从而固定每个组件的资源前缀。

if(isRunAlone.toBoolean()){
apply plugin: 'com.android.application'}else{
apply plugin: 'com.android.library'}
.....
resourcePrefix "readerbook_"
sourceSets {
main {            if (isRunAlone.toBoolean()) {
manifest.srcFile 'src/main/runalone/AndroidManifest.xml'
java.srcDirs = ['src/main/java','src/main/runalone/java']
res.srcDirs = ['src/main/res','src/main/runalone/res']
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}


3.组件的数据传输

实现方式:采用接口+实现的结构

每个组件声明自己提供的服务Service,这些Service都是一些抽象类或者接口,组件负责将这些Service实现并注册到一个统一的路由Router中去。如果要使用某个组件的功能,只需要向Router请求这个Service的实现,具体的实现细节我们全然不关心,只要能返回我们需要的结果就可以了。这与Binder的C/S架构很相像。

因为我们组件之间的数据传递都是基于接口编程的,接口和实现是完全分离的,所以组件之间就可以做到解耦,我们可以对组件进行替换、删除等动态管理。

这种方式需要解决的几个问题:

a.组件怎么暴露自己的服务?在项目中我们简单起见,专门建立了一个componentservice的依赖库,里面定义了每个组件向外提供的service和一些公共model。将所有组件的service整合在一起,是为了在拆分初期操作更为简单,后面需要改为自动化的方式来生成。这个依赖库需要严格遵循开闭原则,以避免出现版本兼容等问题。

b.service的具体实现是由所属组件注册到Router中的,那么是在什么时间注册的呢?这个就涉及到组件的加载等生命周期

c.一个很容易犯的小错误就是通过持久化的方式来传递数据,例如file、sharedpreference等方式,这个是需要避免的。

if(isRunAlone.toBoolean()){
apply plugin: 'com.android.application'}else{
apply plugin: 'com.android.library'}
.....
resourcePrefix "readerbook_"
sourceSets {
main {            if (isRunAlone.toBoolean()) {
manifest.srcFile 'src/main/runalone/AndroidManifest.xml'
java.srcDirs = ['src/main/java','src/main/runalone/java']
res.srcDirs = ['src/main/res','src/main/runalone/res']
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}


4.组件之间的UI跳转

实现方式:短链

可以说UI的跳转也是组件提供的一种特殊的服务,可以归属到上面的数据传递中去。不过一般UI的跳转我们会单独处理,一般通过短链的方式来跳转到具体的Activity。每个组件可以注册自己所能处理的短链的schme和host,并定义传输数据的格式。然后注册到统一的UIRouter中,UIRouter通过schme和host的匹配关系负责分发路由。

UI跳转部分的具体实现是通过在每个Activity上添加注解,然后通过apt形成具体的逻辑代码。这个也是目前Android中UI路由的主流实现方式。

5.组件的生命周期

生命周期状态:加载、卸载、降维



6.集成调试

每个组件单独调试通过并不意味着集成在一起没有问题,因此在开发后期我们需要把几个组件机集成到一个app里面去验证。由于我们上面的机制保证了组件之间的隔离,所以我们可以任意选择几个组件参与集成。这种按需索取的加载机制可以保证在集成调试中有很大的灵活性,并且可以加大的加快编译速度。

  我们的做法是这样的,每个组件开发完成之后,发布一个relaese的aar到一个公共仓库,一般是本地的maven库。然后主项目通过参数配置要集成的组件就可以了。

  

  


 

  

7.代码隔离

  我们可以使用compile project(xxx:reader.aar)来引入组件吗?虽然我们在数据传输章节使用了接口+实现的架构,组件之间必须针对接口编程,但是一旦我们引入了reader.aar,那我们就完全可以直接使用到其中的实现类啊,这样我们针对接口编程的规范就成了一纸空文。

  我们希望只在assembleDebug或者assembleRelease的时候把aar引入进来,而在开发阶段,所有组件都是看不到的,这样就从根本上杜绝了引用实现类的问题。我们把这个问题交给gradle来解决,我们创建一个gradle插件,然后每个组件都apply这个插件,插件的配置代码也比较简单:

  

//根据配置添加各种组件依赖,并且自动化生成组件加载代码
if (project.android instanceof AppExtension) {
AssembleTask assembleTask = getTaskInfo(project.gradle.startParameter.taskNames)
if (assembleTask.isAssemble
&& (assembleTask.modules.contains("all") || assembleTask.modules.contains(module))) {
//添加组件依赖
project.dependencies.add("compile","xxx:reader-release@aar")
//字节码插入的部分也在这里实现
}
}

private AssembleTask getTaskInfo(List<String> taskNames) {
AssembleTask assembleTask = new AssembleTask();
for (String task : taskNames) {
if (task.toUpperCas
9fec
e().contains("ASSEMBLE")) {
assembleTask.isAssemble = true;
String[] strs = task.split(":")
assembleTask.modules.add(strs.length > 1 ? strs[strs.length - 2] : "all");
}
}
return assembleTask
}


组件化的拆分步骤和动态需求

拆分原则:

从产品需求到开发阶段再到运营阶段都有清晰边界的功能开始拆分,比如读书模块、直播模块等,这些开始分批先拆分出去

在拆分中,造成组件依赖主项目的依赖的模块继续拆出去,比如账户体系等

最终主项目就是一个Host,包含很小的功能模块(比如启动图)以及组件之间的拼接逻辑
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 模块化