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

特别说明

当前博客平台账号已废弃,如果有使用细节问题请前往我新博客平台进行讨论交流。

个人博客平台 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跳转可以认为是一种特殊的数据传递,在实现思路上有啥不同?
  • 组件的生命周期。我们的目标是可以做到对组件可以按需、动态的使用,因此就会涉及到组件加载、卸载和降维的生命周期。
  • 集成调试。在开发阶段如何做到按需的编译组件?一次调试中可能只有一两个组件参与集成,这样编译的时间就会大大降低,提高开发效率。
  • 代码隔离。组件之间的交互如果还是直接引用的话,那么组件之间根本没有做到解耦,如何从根本上避免组件之间的直接引用呢?也就是如何从根本上杜绝耦合的产生呢?只有做到这一点才是彻底的组件化。

整个框架结构

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容