全面组件化—DDComponentForAndroid分析(1)

前言

关于组件化,其实从毕业到现在都在使用组件化的开发方式。因为公司有多个android开发人员,平时需要协同开发。但是之前的组件化方案都是公司的老员工留下来的东西,虽然也能达到组件化开发的目标。但使用起来不是很方便,简单来说就是---僵硬。

后来前辈离职,具体前辈突然离职了,作为应届生的我。正好这是一个机会,于是我准备开始实现自己的方案。

半个月前看到了简书上的文章:
Android彻底组件化demo发布
Android彻底组件化方案实践
颇受启发,虽然作者写的很详细。但是水平问题,一时看懂有点难。所以上下分析了将近三天,才有一点眉目。由于作者是从一个开发者视角讲解的框架,所以作为使用者理解有点模糊。

我打算从使用者的角度来分析这个框架,从一个新的项目如何一步一步的引入这个组件化方案。水平有限,如有误差,还望谅解。

1.全面组件化---DDComponentForAndroid分析(1)
2.全面组件化---DDComponentForAndroid分析(2)
3.全面组件化---DDComponentForAndroid分析(3)

一点姿势补充(摘抄)

组件化和模块化以及插件化

模块化是一种指导理念,其核心思想就是分而治之、降低耦合。而在Android工程中如何实施,目前有两种途径,也是两大流派,一个是组件化,一个是插件化。

组件化需要解决的几大问题

  • 代码解耦。如何将一个庞大的工程拆分成有机的整体?
  • 组件单独运行。上面也讲到了,每个组件都是一个完整的整体,如何让其单独运行和调试呢?
  • 数据传递。因为每个组件都会给其他组件提供的服务,那么主项目(Host)与组件、组件与组件之间如何传递数据?
  • UI跳转。UI跳转可以认为是一种特殊的数据传递,在实现思路上有啥不同?
  • 组件的生命周期。我们的目标是可以做到对组件可以按需、动态的使用,因此就会涉及到组件加载、卸载和降维的生命周期。
  • 集成调试。在开发阶段如何做到按需的编译组件?一次调试中可能只有一两个组件参与集成,这样编译的时间就会大大降低,提高开发效率。
  • 代码隔离。组件之间的交互如果还是直接引用的话,那么组件之间根本没有做到解耦,如何从根本上避免组件之间的直接引用呢?也就是如何从根本上杜绝耦合的产生呢?只有做到这一点才是彻底的组件化。

整个框架结构

6650461-92c8e8a0a078f6ef.png

  • app是主项目,负责集成众多组件,控制组件的生命周期
  • reader和share是我们拆分的两个组件
  • componentservice中定义了所有的组件提供的服务
  • basicres定义了全局通用的theme和color等公共资源
  • basiclib中是公共的基础库,一些第三方的库(okhttp等)也统一交给basiclib来引入
  • 图中没有体现的module有两个,一个是componentlib,这个是我们组件化的基础库,像Router/UIRouter等都定义在这里;另一个是build-gradle,这个是我们组件化编译的gradle插件,也是整个组件化方案的核心。

如何使用到自己的全新项目中

首先下载源码

https://github.com/luojilab/DDComponentForAndroid

主要是用来做参考和对比的,也可以不下载

新建自己的项目

  1. 新建一个名为"zujianhua"的工程,并导入一些基本依赖库。

为了方便起见,我使用了参考源码中的代码,后期会改为自己的代码。

主要是三个library:
:componentservice, :basicres, :basiclib

依赖关系:app-->componentservice-->basicres-->basiclib

作用:

  • componentservice: 组件服务,用于数据传输
  • basicres: 公共资源
  • basiclib: 公共代码
  1. 导入插件 build-gradle 非必需

为什么说非必需,因为可以在其他地方导入。发布到一个maven 仓库中,提供引用即可。

为了方便分析,这里先导入到项目里,只发布到本地文件系统。(后面讲解如何发布到meven)

关于搭建maven 仓库

搭建本地的nexus-maven-仓库

build-gradle 是一个gradle 插件工程,需要生成对应的jar文件提供给.gradle 文件使用:

点击右上角-->Gradle-->buid-gradle-->Tasks-->upload-->uploadArchives

点击即可,等待:

gradle插件发布到本地仓库.png

出现新的文件夹:repo,也就是插件发布的地址。

gradle生成的文件夹.png

  1. 关键步骤,使用插件
  • 首先添加仓库地址,并配置引入
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 使用 插件
  1. 新增 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

  1. 修改组件gradle.properties

组件的工程目录下新建文件gradle.properties文件,增加以下配置:

isRunAlone=true
  1. 应用组件化编译脚本
    同上,引入apply plugin: 'com.dd.comgradle',并配置combuild

  2. 为了解决模块的单独运行,插件需要配置runalone 资源文件。
    专门配置组件单独运行的资源。
    具体如下:在组件内 于java同级新建runalone 目录
    (如想修改,可查看插件源码)
    为方便起见,我直接复制源代码中的文件夹。
    QQ截图20171109153326.png
    sync一下,再看一下目录结构:
    QQ截图20171109155310.png

我们可以尝试单独安装 两个组件到手机上:

QQ截图20171109155702.png

这样我们就做到了单独调试。


接下来如何数据传递:

  1. 引入路由框架componentlib
    componentservice>componentlib

整个框架结构变成:

路由:这个应该可以修改成自己的路由框架

  1. 注意这里在debug 时候需要引入onecomponent

所以修改app的gradle.properties文件

isRunAlone=true
debugComponent=onecomponent

debugComponent 就表示 引入onecomponent 组件 在debug下

  1. 配置路由添加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 注入:

由于组件分离,无法直接调用。所以提供两种调用方式:

  1. isRegisterCompoAuto 设置为true,由com.dd.comgradle插件完成引用。
    (如何做到的,可以看接下来的插件分析)
  2. 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(组件注册类名)

尝试启动:

device-2017-11-09-171359.png


UI跳转

这部分其实没什么讲的,而且各个路由框架有自己的跳转方式以及配置方法。

这里看一下源码中的路由框架如何跳转的

  1. 新建twocomponet,配置同上

  2. 添加twoactivity,配置runalone

  3. TwoUIRouter继承IComponentRouter接口,实现路由跳转内容

  4. TwoApplike 同样把TwoUIRouter加入到Router中

  5. 设置isRegisterCompoAuto 为true或者 手动注册。

    Router.registerComponent("com.huruwo.twocomponent.applike.TwoAppLike");

6.代码跳转 从onefragement-->twoactivity

 UIRouter.getInstance().openUri(getActivity(), "component://two", null);

效果:

ui路由.gif


这篇文章已经够长了,先写到这里吧。

总结

这个部分解决了组件化的几个难点:

  • 代码解耦
  • 组件的单独调试
  • 组件的数据传输
  • 组件之间的UI跳转
  • 组件的生命周期

这里遗留了两个问题:

  • 集成调试
  • 代码隔离

将在下篇文章中引入解决。

相关源代码已同步到github
https://github.com/HuRuWo/ZuJianHua

张贴在未分类

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注