美文网首页Android架构大牛聚集之地
全面组件化---DDComponentForAndroid分析(

全面组件化---DDComponentForAndroid分析(

作者: 五谷观精分道长 | 来源:发表于2017-11-09 23:51 被阅读2619次

    前言

    关于组件化,其实从毕业到现在都在使用组件化的开发方式。因为公司有多个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
    1. 为了解决模块的单独运行,插件需要配置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

    相关文章

      网友评论

      本文标题:全面组件化---DDComponentForAndroid分析(

      本文链接:https://www.haomeiwen.com/subject/bgaemxtx.html