美文网首页Android TechAndroid架构
Android彻底组件化demo发布

Android彻底组件化demo发布

作者: 格竹子 | 来源:发表于2017-08-23 14:19 被阅读26744次

    得到Android组件化方案已经开源,参见Android组件化方案开源。方案的解读文章是一个小的系列,这是系列的第二篇文章:
    1、Android彻底组件化方案实践
    2、Android彻底组件化demo发布
    3、Android彻底组件化-代码和资源隔离
    4、Android彻底组件化—UI跳转升级改造
    5、Android彻底组件化—如何使用Arouter

    今年6月份开始,我开始负责对“得到app”的android代码进行组件化拆分,在动手之前我查阅了很多组件化或者模块化的文章,虽然有一些收获,但是很少有文章能够给出一个整体且有效的方案,大部分文章都只停留在组件单独调试的层面上,涉及组件之间的交互就很少了,更不用说组件生命周期、集成调试和代码边界这些最棘手的问题了。有感于此,我觉得很有必要设计一套完整的组件化方案,经过几周的思考,反复的推倒重建,终于形成了一个完整的思路,整理在我的第一篇文章中Android彻底组件化方案实践。这两个月以来,得到的Android团队按照这个方案开始了组件化的拆分,经过两个开发周期的努力,目前已经拆分五个大的业务组件以及数个底层lib库,并对之前的方案进行了一些完善。从使用效果上来看,这套方案完全可以达到了我们之前对组件化的预期,并且架构简单,学习成本低,对于一个急需快速组件化拆分的项目是很适合的。现在将这套方案开源出来,欢迎大家共同完善。代码地址:https://github.com/mqzhangw/JIMU

    一、JIMU使用指南

    首先我们看一下demo的代码结构,然后根据这个结构图再次从单独调试(发布)、组件交互、UI跳转、集成调试、代码边界和生命周期等六个方面深入分析,之所以说“再次”,是因为上一篇文章我们已经讲了这六个方面的原理,这篇文章更侧重其具体实现。

    JIMU结构图.png

    代码中的各个module基本和图中对应,从上到下依次是:

    • app是主项目,负责集成众多组件,控制组件的生命周期
    • reader和share是我们拆分的两个组件
    • componentservice中定义了所有的组件提供的服务
    • basicres定义了全局通用的theme和color等公共资源
    • basiclib中是公共的基础库,一些第三方的库(okhttp等)也统一交给basiclib来引入

    图中没有体现的module有两个,一个是componentlib,这个是我们组件化的基础库,像Router/UIRouter等都定义在这里;另一个是build-gradle,这个是我们组件化编译的gradle插件,也是整个组件化方案的核心。

    我们在demo中要实现的场景是:主项目app集成reader和share两个组件,其中reader提供一个读书的fragment给app调用(组件交互),share提供一个activity来给reader来调用(UI跳转)。主项目app可以动态的添加和卸载share组件(生命周期)。而集成调试和代码边界是通过build-gradle插件来实现的。

    1 单独调试和发布

    单独调试的配置与上篇文章基本一致,通过在组件工程下的gradle.properties文件中设置一个isRunAlone的变量来区分不同的场景,唯一的不同点是在组件的build.gradle中不需要写下面的样板代码:

    if(isRunAlone.toBoolean()){    
    apply plugin: 'com.android.application'
    }else{  
     apply plugin: 'com.android.library'
    }
    
    

    而只需要引入一个插件com.dd.comgradle(源码就在build-gradle),在这个插件中会自动判断apply com.android.library还是com.android.application。实际上这个插件还能做更“智能”的事情,这个在集成调试章节中会详细阐述。

    单独调试所必须的AndroidManifest.xml、application、入口activity等类定义在src/main/runalone下面,这个比较简单就不赘述了。

    如果组件开发并测试完成,需要发布一个release版本的aar文件到中央仓库,只需要把isRunAlone修改为false,然后运行module:assembleRelease命令就可以了。这里简单起见没有进行版本管理,大家如果需要自己加上就好了。值得注意的是,发布组件是唯一需要修改isRunAlone=false的情况,即使后面将组件集成到app中,也不需要修改isRunAlone的值,既保持isRunAlone=true即可。所以实际上在Androidstudio中,是可以看到三个application工程的,随便点击一个都是可以独立运行的,并且可以根据配置引入其他需要依赖的组件。这背后的工作都由com.dd.comgradle插件来默默完成。

    项目中有三个application工程.png

    2 组件交互

    在这里组件的交互专指组件之间的数据传输,在我们的方案中使用的是接口+实现的方式,组件之间完全面向接口编程。

    在demo中我们让reader提供一个fragment给app使用来说明。首先reader组件在componentservice中定义自己的服务

    public interface ReadBookService {
        Fragment getReadBookFragment();
    }
    

    然后在自己的组件工程中,提供具体的实现类ReadBookServiceImpl:

    public class ReadBookServiceImpl implements ReadBookService {
        @Override
        public Fragment getReadBookFragment() {
            return new ReaderFragment();
        }
    }
    

    提供了具体的实现类之后,需要在组件加载的时候把实现类注册到Router中,具体的代码在ReaderAppLike中,ReaderAppLike相当于组件的application类,这里定义了onCreate和onStop两个生命周期方法,对应组件的加载和卸载。

    public class ReaderAppLike implements IApplicationLike {
        Router router = Router.getInstance();
        @Override
        public void onCreate() {
            router.addService(ReadBookService.class.getSimpleName(), new ReadBookServiceImpl());
        }
        @Override
        public void onStop() {
            router.removeService(ReadBookService.class.getSimpleName());
        }
    }
    

    在app中如何使用如reader组件提供的ReaderFragment呢?注意此处app是看不到组件的任何实现类的,它只能看到componentservice中定义的ReadBookService,所以只能面向ReadBookService来编程。具体的实例代码如下:

    Router router = Router.getInstance();
    if (router.getService(ReadBookService.class.getSimpleName()) != null) {
        ReadBookService service = (ReadBookService) router.getService(ReadBookService.class.getSimpleName());
        fragment = service.getReadBookFragment();
        ft = getSupportFragmentManager().beginTransaction();
        ft.add(R.id.tab_content, fragment).commitAllowingStateLoss();
    }
    

    这里需要注意的是由于组件是可以动态加载和卸载的,因此在使用ReadBookService的需要进行判空处理。我们看到数据的传输是通过一个中央路由Router来实现的,这个Router的实现其实很简单,其本质就是一个HashMap,具体代码大家参见源码。

    通过上面几个步骤就可以轻松实现组件之间的交互,由于是面向接口,所以组件之间是完全解耦的。至于如何让组件之间在编译阶段不不可见,是通过上文所说的com.dd.comgradle实现的,这个在第一篇文章中已经讲到,后面会贴出具体的代码。

    3 UI跳转

    页面(activity)的跳转也是通过一个中央路由UIRouter来实现,不同的是这里增加了一个优先级的概念。具体的实现就不在这里赘述了,代码还是很清晰的。

    具体的功能介绍和使用规范,请大家参见文章:
    android彻底组件化—UI跳转升级改造

    4 集成调试

    集成调试可以认为由app或者其他组件充当host的角色,引入其他相关的组件一起参与编译,从而测试整个交互流程。在demo中app和reader都可以充当host的角色。在这里我们以app为例。

    首先我们需要在根项目的gradle.properties中增加一个变量mainmodulename,其值就是工程中的主项目,这里是app。设置为mainmodulename的module,其isRunAlone永远是true。

    然后在app项目的gradle.properties文件中增加两个变量:

    debugComponent=readercomponent,com.mrzhang.share:sharecomponent
    compileComponent=readercomponent,sharecomponent
    

    其中debugComponent是运行debug的时候引入的组件,compileComponent是release模式下引入的组件。我们可以看到debugComponent引入的两个组件写法是不同的,这是因为组件引入支持两种语法,module或者modulePackage:module,前者直接引用module工程,后者使用componentrelease中已经发布的aar。

    注意在集成调试中,要引入的reader和share组件是不需要把自己的isRunAlone修改为false的。我们知道一个application工程是不能直接引用(compile)另一个application工程的,所以如果app和组件都是isRunAlone=true的话在正常情况下是编译不过的。秘密就在于com.dd.comgradle会自动识别当前要调试的具体是哪个组件,然后把其他组件默默的修改为library工程,这个修改只在当次编译生效。

    如何判断当前要运行的是app还是哪个组件呢?这个是通过task来判断的,判断的规则如下:

    • assembleRelease → app
    • app:assembleRelease或者 :app:assembleRelease → app
    • sharecomponent:assembleRelease 或者:sharecomponent:assembleRelease→ sharecomponent

    上面的内容要实现的目的就是每个组件可以直接在Androidstudio中run,也可以使用命令进行打包,这期间不需要修改任何配置,却可以自动引入依赖的组件。这在开发中可以极大加快工作效率。

    5 代码边界

    至于依赖的组件是如何集成到host中的,其本质还是直接使用compile project(...)或者compile modulePackage:module@aar。那么为啥不直接在build.gradle中直接引入呢,而要经过com.dd.comgradle这个插件来进行诸多复杂的操作?原因在第一篇文章中也讲到了,那就是组件之间的完全隔离,也可以称之为代码边界。如果我们直接compile组件,那么组件的所有实现类就完全暴露出来了,使用方就可以直接引入实现类来编程,从而绕过了面向接口编程的约束。这样就完全失去了解耦的效果了,可谓前功尽弃。

    那么如何解决这个问题呢?我们的解决方式还是从分析task入手,只有在assemble任务的时候才进行compile引入。这样在代码的开发期间,组件是完全不可见的,因此就杜绝了犯错误的机会。具体的代码如下:

      /**
     * 自动添加依赖,只在运行assemble任务的才会添加依赖,因此在开发期间组件之间是完全感知不到的,这是做到完全隔离的关键
     * 支持两种语法:module或者modulePackage:module,前者之间引用module工程,后者使用componentrelease中已经发布的aar
     * @param assembleTask
     * @param project
     */
    private void compileComponents(AssembleTask assembleTask, Project project) {
        String components;
        if (assembleTask.isDebug) {
            components = (String) project.properties.get("debugComponent")
        } else {
            components = (String) project.properties.get("compileComponent")
        }
        if (components == null || components.length() == 0) {
            return;
        }
        String[] compileComponents = components.split(",")
        if (compileComponents == null || compileComponents.length == 0) {
            return;
        }
        for (String str : compileComponents) {
            if (str.contains(":")) {
                File file = project.file("../componentrelease/" + str.split(":")[1] + "-release.aar")
                if (file.exists()) {
                    project.dependencies.add("compile", str + "-release@aar")
                } else {
                    throw new RuntimeException(str + " not found ! maybe you should generate a new one ")
                }
            } else {
                project.dependencies.add("compile", project.project(':' + str))
            }
        }
    }
    

    6 生命周期

    在上一篇文章中我们就讲过,组件化和插件化的唯一区别是组件化不能动态的添加和修改组件,但是对于已经参与编译的组件是可以动态的加载和卸载的,甚至是降维的。

    首先我们看组件的加载,使用章节5中的集成调试,可以在打包的时候把依赖的组件参与编译,此时你反编译apk的代码会看到各个组件的代码和资源都已经包含在包里面。但是由于每个组件的唯一入口ApplicationLike还没有执行oncreate()方法,所以组件并没有把自己的服务注册到中央路由,因此组件实际上是不可达的。

    在什么时机加载组件以及如何加载组件?目前com.dd.comgradle提供了两种方式,字节码插入和反射调用。

    • 字节码插入模式是在dex生成之前,扫描所有的ApplicationLike类(其有一个共同的父类),然后通过javassist在主项目的Application.onCreate()中插入调用ApplicationLike.onCreate()的代码。这样就相当于每个组件在application启动的时候就加载起来了。
    • 反射调用的方式是手动在Application.onCreate()中或者在其他合适的时机手动通过反射的方式来调用ApplicationLike.onCreate()。之所以提供这种方式原因有两个:对代码进行扫描和插入会增加编译的时间,特别在debug的时候会影响效率,并且这种模式对Instant Run支持不好;另一个原因是可以更灵活的控制加载或者卸载时机。

    这两种模式的配置是通过配置com.dd.comgradle的Extension来实现的,下面是字节码插入的模式下的配置格式,添加applicationName的目的是加快定位Application的速度。

    combuild {
        applicationName = 'com.mrzhang.component.application.AppApplication'
        isRegisterCompoAuto = true
    }
    

    demo中也给出了通过反射来加载和卸载组件的实例,在APP的首页有两个按钮,一个是加载分享组件,另一个是卸载分享组件,在运行时可以任意的点击按钮从而加载或卸载组件,具体效果大家可以运行demo查看。

    加载和卸载示例.png

    二、组件化拆分的感悟

    在最近两个月的组件化拆分中,终于体会到了做到剥丝抽茧是多么艰难的事情。确定一个方案固然重要,更重要的是克服重重困难坚定的实施下去。在拆分中,组件化方案也不断的微调,到现在终于可以欣慰的说,这个方案是经历过考验的,第一它学习成本比较低,组内同事可以快速的入手,第二它效果明显,得到本来run一次需要8到10分钟时间(不过后面换了顶配mac,速度提升了很多),现在单个组件可以做到1分钟左右。最主要的是代码结构清晰了很多,这位后期的并行开发和插件化奠定了坚实的基础。

    总之,如果你面前也是一个庞大的工程,建议你使用该方案,以最小的代价尽快开始实施组件化。如果你现在负责的是一个开发初期的项目,代码量还不大,那么也建议尽快进行组件化的规划,不要给未来的自己增加徒劳的工作量。

    本文提出的Android组件化方案已经开源,参见Android组件化方案开源项目

    JIMU的讨论群,群号693097923,欢迎大家加入:


    进群请扫码

    相关文章

      网友评论

      • 期待1990nice:请问如何单独打包某个组件?现在的 组件的 isRunAlone=true ,执行 assembleRelease ,还是用的是 apply plugin is com.android.library
        期待1990nice:@左大人 多谢
        左大人:执行 `gradle :readercomponent:assembleRelease` 即可单独打包
      • SinFeeLoo:求大神解答!!!导入项目,编译遇到了这个错误:Unknown host 'd29vzk4ow07wi7.cloudfront.net'. You may need to adjust the proxy settings in Gradle.
        SinFeeLoo:https://www.jianshu.com/p/66baf3e58c6a
      • Yufee:build好慢有可能是哪些问题
      • 浩运:你好,组件间通信的时候,A组件的接口,怎样暴露给B接口勒,也即ReadBookService 是定义在哪里的?公共lib中么?如果是公共lib中,是不是每次定义接口都要手动修改公共lib
      • CokeNello:文中的示例Demo,有地址吗?
      • Todo2:组件化和插件化的开发里程总结
        https://www.jianshu.com/p/df2a6717009d
      • castlet:您好,请教个问题,组件间公用的实体类该如何处理呢?
      • 小红军storm:ReadBookServiceImpl 在哪里注册到router的, 类似这种的Router.registerComponent("com.luojilab.share.applike.ShareApplike");
      • 木子fight:按这个方式做最后可以统一打包成aar给别的app集成吗?
        格竹子:@木子fight 支持aar集成,也支持project集成
      • 19eaee51acd4:google推荐的instant app里面,涉及到的feature module和楼主的思想有点类似,加上gradle3.0的dependency scope : implementation,我觉得应该也能实现代码组件化解耦,不知道楼主对这种方案怎么看呢?
        格竹子:@江小米0052 没研究过feature module,gradle3.0倒是写了篇文章专门讲解了,你可以看看,应该是组件化系列的第三篇文章
      • DDDDLU:谢谢分享
      • Chauncey_Chen:请问 如何解决 主app 有四个业务线 fragment的情况?
        格竹子:@Chauncey_Chen 如果这四个fragment来自不同的组件,那就需要组件都向外提供fragment,demo中reader组件就提供类似的功能
      • 西园无忌:as3.0下运行时报了错误:java.lang.RuntimeException: Unable to start activity ComponentInfo{com.luojilab.share.kotlin/com.luojilab.share.kotlin.ShareMessageActivity}: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
        西园无忌:@西园无忌 嗯
        格竹子:@西园无忌 报错说的很明白了,theme设置不对,改一下就好了
      • b87b0c450928:运行或打包release报错
        Error:(1, 1) A problem occurred evaluating project ':app'.
        > Failed to apply plugin [id 'com.dd.comgradle']
        > Project with path ':chargencomponent' could not be found in project ':app'.

        isRunAlone=true
        debugComponent=chargecomponent
        compileComponent=chargencomponent

        运行或者打debug包就没这问题。
        格竹子:@何处繁华笙歌 Project with path ':chargencomponent' could not be found in project ':app'.问题已经说得比较明确了吧,你的module名字是chargencomponent吗,拼写和路径确定没错吧
        b87b0c450928:ComBuild类里
        if (assembleTask.isDebug) {
        components = (String) project.properties.get("debugComponent")
        } else {
        components = (String) project.properties.get("compileComponent")
        }
        跟这里有关系吗?
      • DOAING:你好,请教个问题。就是common做统一的lib依赖的时候,其他module直接依赖他,但是其他的module找不到。比如一个基类的activity 继承与v7包的,其他module activity再去继承它,如果删掉当前module下的v7包就找不到了,不是依赖了以后可以间接使用common下的v7包吗?
        格竹子:@DOAING 是啊,只要你用的是compile就可以,如果你用implementation就不可以了
        DOAING:@格竹子 我描述的不太清楚,我有个lib组件添加了很多依赖比如v7包,其他组件依赖他,那么其他组件是不是就不用重复引用一些包了?比如我说的那个v7 或者v4包之类的。
        格竹子:@DOAING 没怎么看明白你的意思:sweat:
      • 9813c3ee0b09:请问,运行demo报
        Error:Execution failed for task ':sharecomponentkotlin:transformClassesWithComponentCodeForDebug'.
        > you should set applicationName in combuild
        这是应该修改哪块?
      • 汤增雷:谢谢分享,谢谢罗辑思维团队~~
        格竹子:@汤增雷 客气啦,欢迎使用~~~
      • 48aef81ea7ef:您好,我正在跟着demo的逻辑看代码。发现这么一个问题:启动主项目app,点击“点击启动分享组件”。按着代码逻辑,肯定执行了sharecomponent中ShareUIRouter的verifyUri函数,但我在该函数中打印的日志,在控制台却看不到。请问这是为什么
        格竹子:@nobodyZheng 你用的是最新的代码吗?这块最近有一个大的改动,ShareUiRouter改为自动生成了。如果能正常跳转,这个方法肯定是执行到,你可以debug试一下
      • forevan:请教个问题,一般的app都是在登录之后才会进去app进行其他的操作,而登录返回的token之类的一般是存在本地db中的,而网络请求又是依赖于这个db中的token,而抽出来的独立模块中网络请求也依赖这个token,这种情况下怎么去解决这种依赖问题呢?:disappointed_relieved:
        格竹子:@ministorm 这种情况复用就比较麻烦了,就像有的组件用的Picasso有的用的是glide,都是图片库,当然就没法复用了。如果必须复用,只能把db升级为一个组件,向外提供相同的接口,组件都是针对接口编程,每个app提供自己的db组件。
        一般组件复用的前提是,公司的基础库是共用的,一旦做不到这一点,可能就在于前期设计的时候考虑的不周,需要重新考虑一下设计
        forevan:@格竹子 那是不是可以这么理解 db封装成单独的库 所有组件都依赖此 ?还有一种场景就是两个app的大部分功能都是一样的 但是db中存储的数据不大一样 这样的话好多的组件需要依赖不同的db库 即使功能大部分相同也没办法达到复用?
        格竹子:@ministorm 组件化既包括业务模块抽成组件,还包括逻辑模块抽成依赖库,有时候依赖库要先行。这种情况下就需要把db的操作封装成一个公共的依赖库,所有组件都可以直接调用
      • db59667c4a37:当两个组件引用同一个库时,打包会出错也。在运行主app时也会报组件中的类找不到库引用。如果两个组件都加上库引用又一直编译不通过,clean是无效的,求解呀
      • 0483aff081d9:你可以组件提供的服务(componentservice)中创建一个自定义的抽象类MyFragment,除了继承Fragment之外,还提供刷新数据等抽象方法。组件中具体的MyFragmentImpl继承MyFragment,并完成这些抽象方法的逻辑处理。而在app中是针对MyFragment来编程的,此时除了可以调用Fragment的方法,还可以调用自己定义的方法。

        看到你之前回答中提到这个,这个fragment实例怎么获取?
        0483aff081d9:@格竹子 你的Demo,我都研究过了,你reader里的ReadBookServiceImpl是new Fragment,我需要获取的不是重新new一个fragment,而是已经存在的fragment
        格竹子:@花歹 请看github源码,里面reader就是提供一个fragment
      • fa47c82221a6:还有一个问题想请教一下作者,在加载组件的时候提供了两种方法,一种是字节码插入,一种是通过反射。字节码插入的方法可以更灵活的控制加载或者卸载时机,这个灵活体现在哪里呢?project.android.registerTransform(new ComCodeTransform(project))这行代码作用是不是调用字节码插入的?
        fa47c82221a6:@格竹子 哦哦,明白了,非常感谢
        格竹子:@我是乔鹏 你理解错了吧,反射的方式可以更灵活的控制加载或者卸载时机,字节码插入只在Application的onCreate方法中自动注入了注册代码。registerTransform是字节码插入的入口,具体逻辑在ComCodeTransform类中
      • andev009:将两个组件工程(read,share)和app主工程的isRegisterCompoAuto都改成false,运行app,为什么read自动加载呢?单独跑read,为什么share能找到呢?字节码插入模式isRegisterCompoAuto什么时候才失效呢?
        小红军storm:@格竹子 // Router.registerComponent("com.luojilab.reader.applike.ReaderAppLike");
        这个代码不是注释吗吗 read组件的isRegisterCompoAuto又是false,为什么运行app的时候read组件还会加载,始终没有搞明白
        格竹子:@andev009 文章中专门讲到了有两种加载(注册)方式,一种是字节码插入,就是设置isRegisterCompoAuto=true,另一种是使用反射加载Router.registerComponent。你可以检查一下是否有Router.registerComponent的代码。
      • fa47c82221a6:首先为作者点赞!有一个问题想请教一下,在组件化的时候,拆分的粒度应该如何把握?哪些东西应该单独拆出去,哪些东西应该是一起的?
        fa47c82221a6:@格竹子 请教一下作者,两个或者多个组件都要用到的图片资源应该怎么处理?copy几份还是下沉到basicres?
        fa47c82221a6:@格竹子 好的,谢谢啦
        格竹子:@我是乔鹏 这个就是经验问题了,一般业务上独立的就需要单独拆出来,这个没有明确的标准,只能结合自己的业务来自己把握
      • 一个大西瓜CPI:楼主请问一下,打开组件activity的时候可以通过openUrl传数据,当这个activity finish需要回传数据怎么办?
        格竹子:@一个大西瓜CPI 你说的是startActivityForResult吗?这个在github上的issue中有解释
      • jinyb:楼主通过修改gradle的方案,解决了需要手动修改配置修改模块工程属性以及控制代码边界的思路非常棒。但是个人有几点疑问同楼主探讨下。
        1、既然已经通过assemble的时候去加载依赖,那么再通过设置isRunAlone为false去打包aar的意义在哪里,编译全工程都需要加载所有的依赖,直接依赖工程不就好了吗?如果说这样可以省去打包的时间,那单独编译子工程也是需要时间的。而且还需要考虑版本同步的问题。最后这也反倒混淆了一部分人对于这个参数的理解。

        2、组件卸载的意义在哪里了。这里的卸载的意思最后是禁止该组件的访问而已,组件还是存在于整个应用之中,只是访问不到而已。因为在打包的时候,整个组件一直都存在于应用中。顶多加载和卸载释放了一个service的引用内存而已,感觉意义不。

        不知道个人想法是否没能理解楼主真正的意思
        jinyb:@格竹子 还有一个建议就是,楼主的例子中,让组件reader依赖了share是做法不太恰当。个人觉得业务组件之间不应该存在依赖,组件之间应该是平行的关系。这样也更让读者觉更能感受组件化的意义。
        jinyb:@格竹子 恩,个人感觉把这两个功能好好的用起来了,还需要很多精细化的配置支持。
        格竹子:1、目前默认的方式就是直接依赖工程,但同时提供了一个aar集成的方式,目的除了节省编译时间之外,最主要的是支持并行开发,这个可能你暂时用不到,但是在大项目里面这个是必须要有的,很多情况下都是两到三个版本在并行,使用aar可以精确的控制版本。如果项目不大,就直接使用以来工程就好了。
        2、卸载在节省内存上效果不是很大,但是在某些情况可以起到补救的作用,例如某个模块出现非技术问题,可以快速下掉这个模块,甚至可以结合降维来使用,把某个模块直接使用web页呈现。这些动作都需要热修复技术的配合。
        所以这两个功能可能使用的频率真的很小,但是作为一个整体方案不得不考虑,你可以暂时不用,后面有需要的时候在使用
      • a667f5bbb41f:修改了componetlib的包名之后, isRegisterCompoAuto = true的方式失效
        a667f5bbb41f:修改了"com.mrzhang.component.componentlib.applicationlike.IApplicationLike".equals(ctClassInter.name)这个地方,还有其他位置需要修改吗?
        a667f5bbb41f: @格竹子 已经修改了ConCodeTransfrom中写死的like路径。还是不行😂
        格竹子:@冷漠无恒 代码中有个地方硬编码了,你看一下github上的wiki,有个定制化文档。修改gradle插件之后重新发布就可以了
      • 小萌新1:想问一下,runalone 的文件内容全部要手动去写吗? 如果是自动生成怎么生成? 谢谢
        小萌新1:@格竹子 博主,我这边遇到资源文件lib添加完icon之后无法在模块内调用啊。是编译的问题吗?
        小萌新1:@格竹子 好的 谢谢
        格竹子:@小萌新1 现在只能手写,之前想自定义androidstudio插件的方式来做,没搞定~~
      • jason_syf:项目主要功能模块已经明朗,新开类似项目需要移植几个功能模块,复制修改的我头晕眼花,所以就想模块化会不会好一点,github搜索android组件化,按star排序就看到了你们的demo,学习一下:joy:
        格竹子:@jason_syf 欢迎使用,这儿方案接入还是比较简单的,接入文档可以看wiki,欢迎使用反射注册组件的方式~~
      • RamboMing:应用实例时,发现两个问题:1.假设分享组件有很多个Activity,主host需要直接跳转到具体的activity中,按作者思路,需要定义很多host或者重写很多openUri?;2.假设主host启动其他activity需要startActivityForResult(),除了用第三方EventBus等之类,如何回传数据?
        格竹子:@RamboMing 这部分的确还不够完善。第一个问题比较简单,需要定义多个host,然后在openUri中增加这个host的解析即可。第二个问题,把requestCode当做一个参数传递过去,当有这个参数是调用startActivityForResult,其他写法想改都是一样的。
        这个功能最好是做成apt生成的,github上有个anno分支,是一位热心开发者贡献的,我暂时还没时间去优化这块,欢迎你试用并提意见。
      • 2f5339872af5:模块间无感知 Android Studio3.0 可以使用implementation 标记依赖
        https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html

        另外Gradle的脚本定制和插件定制不太熟悉啊,楼主是从哪儿系统的学习这些知识的?
        2f5339872af5:@格竹子 可以使用runtimeOnly project (':bizTwoModule')应该可以解决你的问题,之前implementation是我理解错了。目前我这边遇到一个问题,我这边每一个Module里面的依赖都是使用Dagger For Android 完成注入的。在合并成主项目的时候需要把每一个子Moudle的Componet进行构建满足依赖提供给主Module的Componet使用,同时构建主Module的Componet。目前是主Module直接依赖于n个子Module,(未做到无感知)。想做到无感知我目前的想法是只能再定义一个DaggerComponetModule所有子Modue的Componet和Module定义在这个Module中,同时这个Module又要直接依赖于n个子Module。同时n个子Module又要同时依赖于DaggerComponetModule这个Moudle。问题1:这样是否会由于循环依赖导致问题?
        问题2:将Componet和Moudle的定义从具体模块中剥离出来是否合适?
        目前使用Dagger实现的小Demo:https://github.com/hunter524/AArcDemo
        格竹子:@Htao 大致看了implementation的作用,在a依赖b,b依赖c的情况下,c对a是不可见的,有点类似于下属的下属不是你的下属的意思。但是在组件化中,app对组件的依赖只有a依赖b这一层,没法做到无感知吧
        格竹子:@Htao implementation还没真正用过,我研究一下,谢谢啦。

        插件这块的知识,也谈不上系统,主要在之前做热修复的时候正好用到过,当时也是用到哪里就google哪里,多搜几篇文章就可以了。
      • 171Arios:赏你两颗糖,看了几遍,看不懂,怀疑我智商有问题。另外对maven库不是很理解,还有gradle plugin来打包也不是很理解……公司急着组件化,只能多看几遍吧,感觉作者写的东西还是需要有点基础的人才看得懂的。
        171Arios:@格竹子 好的,我先多看看有问题再问您
        格竹子:@171Arios 涉及到的都是一些很容易掌握的东西,如果你没有maven库,完全可以按照demo中本地文件夹的方式来做。其他的遇到问题google一下基本都可以解决
      • longzekai:非常好,对于组件化这块我也在做一些类似的尝试,但也如楼主所说,确实有很多棘手的问题要处理,另外也看了阿里开源的Atlas 框架,说实话,阿里的文档确实要加强,如今楼主又在这一领域做出了一大贡献,3Q~,对了,老罗的视频我也是一个忠实粉丝哦。
        格竹子:这个方案与Atlas要简洁很多,不需要修改很多东西就可以接入,侵入性很低。Atlas还具备一些动态性和热修复的能力,这些都是组件化之后的功能,目前得到的方案是没有的
      • ff9eeaef4684:@格竹子 模块之间activity怎么跳转
        格竹子: @湾仔小馒头 请看UI跳转章节
      • AWeiLoveAndroid:楼主好人
        格竹子: @阿韦爱Android 谢谢
      • 街道shu记:还有为什么我修改你的demo里面share组件的外层的manifest文件的shareActivity不可旋转,运行app不生效呢?
        格竹子:@街道书记 那需要切换成第一种语法,就只配置modulename,这样就会依赖当前工程里面的具体module,代码会随时生效
        街道shu记:@格竹子 那我修改代码,怎么能立马生效呢?
        格竹子:share是通过aar集成进去的,集成支持两种语法,是不一样的
      • 街道shu记:格子君我又来了啊,看了你的demo,又运行了app,有个问题,为什么我运行read组件,它可以跳转到share组件啊,按说这两个不能交互啊,怎么跳转过去了啊?:smile:
        格竹子:@街道书记 在集成调试章节有讲,是gradle.properties里面配置的
      • woitaylor:今天才抽时间看了,感谢博主的奉献。有时间看看代码。
        格竹子: @woitaylor 欢迎使用
      • RamboMing:好文,感谢分享!请问照着敲demo 时 build-gradle 中提示很多方法中不到是什么原因
        格竹子:@RamboMing 看代码是没有错误,你的groovy类是集成Plugin<Project>吧,按道理是不应该出现问题的,建议在比照着demo看看其他地方是否不一致吧
        RamboMing:@格竹子 if (!module.equals(mainModuleName)) {
        project.android.sourceSets{
        main{

        }
        }
        }

        project.android.sourceSets{ main{}} 提示错误,没有该方法

        一下是build.gradle配置
        apply plugin: 'groovy'
        apply plugin: 'maven'
        dependencies{
        compile 'com.android.tools.build:gradle:2.3.2'
        compile group:'org.javassist',name:'javassist',version:'3.20.0-GA'
        compile gradleApi()
        compile localGroovy()

        }
        repositories{
        mavenCentral()
        }
        group='com.zujian.andcomponent'
        version='1.0.0'
        uploadArchives{
        repositories{
        mavenDeployer{
        repository(url:uri('../repo'))
        }
        }
        }

        格竹子:方便的话贴一下日志吧,没有日志我也不好分析
      • sun_wenming:小白还是不理解其作用。哎,还得加把劲。
        格竹子: @A我是文明人 💪👍
        sun_wenming:@格竹子 指的如何使用,第一次听到 组件化的概念,个人开发者。让我多探索吧:arrow_up:
        格竹子:作用?你是指原理还是不清如何使用?
      • 鱼儿情未了:http://api.cdpit.com
        格竹子: @PHP_Hander 这是什么鬼?
      • 446fce01c97a:大神,研究了两天。求教啊!!
        问题一:
        我在你的项目中新建了一个reviewcomponent.然后也照着你的代码实现了此模块包括ReviewAppLike和ReviewUIRouter,并且这个项目能单独运行。但是在主模块app中通过中央路由加载此模块 Router.registerComponent("com.meiyue.reviewcomponent.applike.ReviewAppLike")
        它加载不了这个项目 会报classnotfound异常?
        如果我在gradle.properties中配置debugComponent=readercomponent,sharecomponent,reviewcomponent 默认加载也不行,在MainActivity中手动加载模块也不行 为什么会找不到类?

        问题二:
        我没有通过中央仓库找,因为我新建的reviewcomponent生成不了aar文件,只我手动设置为library的时候才能生成aar,这个也不知道为什么,自定义的gradle不是自动切换的吗?
        格竹子: @Newki assembleRelease 写错了
        格竹子: @Newki 修改gradle.properties内容后需要sync后才生效。要生成aar,只需要修改isRunAlone为false,然后同样sync才可以生效,然后运行assembkeRelease就可以生成aar了
        446fce01c97a:问题一在gradle.properties中需要配置reviewcomponent。我没有去maven仓库找aar,直接写死库名,然后报的错是R文件找不到,单独运行没问题。如果不配置reviewcomponent那么在MainActvity中会报classnotfound。现在是为什么在gradle.properties中配置reviewcomponent失败?
      • 酷玩技术派:罗粉: 插眼
        格竹子:@tengX :smile: :smile:
        酷玩技术派:以后看的时候 传送过来
        格竹子:@tengX 插眼什么鬼?
      • 格竹子: github添加了如何定制化的说明。
        代码地址:https://github.com/luojilab/DDComponentForAndroid
      • 7f8a7778edf6:第一步的引入com.dd.comgradle插件 报找不到 怎么解决
        格竹子: @祈祷_9197 这个不用去查资料吧,看一下gradle插件里面的源码就可以了,有注释的
        7f8a7778edf6:@格竹子 那个关于本地的 componentrelease是如何被生成arr文件的和如何调用的,应当怎样搜索相关知识 可以回答下吗
        格竹子:现在demo是放在repo文件夹的,你如果要使用需要发布到自己的maven库
      • Alcal:很高兴,看到这篇文章,之前没有接触过插件化,后来在构建项目时候就自己尝试分层,libs common app plug1.... ,看到作者的文章后才发现其实已经在组件化构建了。希望作者下次出一个插件化的文章资料
        格竹子: @Alcal 谢谢啦
        Alcal:@格竹子 嗯嗯,期待作者发布更好的文章。
        格竹子:👍当代码写到一定程度,组件化是个水到渠成的事情。
        插件化可能近期不会推出,第一篇文章也讲了,插件化唯一的好处就是可以动态的添加和修改功能模块,这个应用的场景很少,并且带来的成本太大。即使要开始做这块,估计也要比较晚才会动手了。
      • bingoogolapple:设置 sourceSets 这一块有办法统一在 build-gradle 里设置吗
        格竹子:@bingoogolapple 客气,有问题随时提issue
        bingoogolapple:@格竹子 3ks
        格竹子:更新最新代码吧,这块已经合并到gradle插件里面了
      • longforus:楼主你好,从你的demo中我受益匪浅,现在发现了一个问题不知道你那里有没有?
        就是执行过插入字节码的任务后,第二次执行构建会报错,
        * What went wrong:
        Execution failed for task ':basiclib:clean'.
        > Unable to delete file: E:\TDownload\DDComponentForAndroid-master\basiclib\build\intermediates\intermediate-jars\debug\classes.jar

        感觉是这个jar在上次任务的进程中没有被释放,必须手动结束java进程,删除build文件夹,第二次构建才能成功
        格竹子:@longforus 这个就不清楚了,是不是多一次clean就可以了?
        longforus:@格竹子 开不开都是一样的,难道和我用的3.0有关?
        格竹子:这个问题我还真没有遇到,你开启instantrun了吗?
      • 格竹子:在项目开源之后,很高兴多个大牛发来自己的组件化方案一起探讨,收益很大。组件化虽然是个老生常谈的话题,但是真要做的完美是一项艰难的工作,希望大家可以贡献自己的智慧,一起把整个方案完善起来!
      • 2c0ffbf87bd2:Error:Dependency AndroidComponent:readercomponent:unspecified on project app resolves to an APK archive which is not supported as a compilation dependency. 大神,我编译你的包,故意在appmoudlel里面compile其他的moudle导致上面的报错。是你在代码里面哪里写了脚本做限制了?
        格竹子:@维维诺 代码都在build-gradle这个module里面,具体类就是ComBuild
        2c0ffbf87bd2:@格竹子 大神,限制出现上面错误的脚本是在build-gradle中的哪个文件哪段代码啊?请大神指教。。。
        格竹子: 哈哈,这就是编译脚本做的限制,因为不能这么做,否则会产生直接引用,破坏组件化。
        具体的限制是每个组件默认都是application工程,之间不能直接引用
      • 7df74064ba54:作者你好,这个框架很棒,学习到了好多。另外请教一个问题,组件的生命周期是在Application的onCreate处初始化,那么组件的onStop在何处调用?
        格竹子:@ViTeTam 按需加载时存在的,只是demo中没有展示而已,每个组件是单独处理生命周期的,因此如果有卸载,时机应该不是统一的。你说的用ActivityLifecycleCallbacks来处理不太明白怎么做?
        7df74064ba54:@格竹子 既然如此,是否可以考虑用ActivityLifecycleCallbacks来做处理?毕竟初始化都是统一在Application的onCreate,也就是说不存在懒加载的场景,那么统一销毁也是可以的
        格竹子:👍,这个问题在文中没有讲清楚,卸载是根据业务来不同定制的,demo中有个例子,通过一个按钮来卸载组件,而实际上应该是组件使用完毕由业务代码来处理的。
      • 2c0ffbf87bd2:能问下大神,build-gralde怎么用么?
        格竹子:@维维诺 这个gradle的源码,可以发布到repo文件夹中或者公司的仓库。如果你对新建自定义gradle插件或者发布工程不熟悉,可以搜一下相关的文章,一般只需要几篇文章就可以搞明白
      • 25b86548811b:isRegisterCompoAuto = false,也会自动字节码插入啊 ??
        格竹子:@Kevin_d349 首先你要区分两种注册模式,injectApplicationCode是在自动化注册模式下使用的,并且对于gradle插件代码,你修改之后需要重新发布一下才能生效;另一种是反射方式,需要配置isRegisterCompoAuto = false,并且手动在application中写反射的代码
        25b86548811b:@格竹子 ReaderAppLike是被injectApplicationCode调用的吗?如果是 为什么我注释掉injectApplicationCode还是被调用了呢? 还有ShareApplike除了手动点击外没地方调用啊 为什么第一次进来也是被注册了的呢 求解
        格竹子:这个是在当前module作为host的时候才会生效
      • Chauncey_Chen:我觉得作者,可以参考下阿里的ARouter路由,它用注解和依赖注入的方式进行Module之间的路由...,您可以使用或者作为参考自己实现.
        格竹子:@Chauncey_Chen 我在文中也讲到了,UI跳转部分可以使用ARouter,但数据交互上针对接口还是更好的方式,后面我会在开源项目中增加类似ARouter的UI跳转的模块,欢迎继续关注哈
      • zhuhf:“combuild {
        applicatonName = 'com.mrzhang.component.application.AppApplication'
        isRegisterCompoAuto = true
        }”

        ------- readercomponent 的 isRegisterCompoAuto = false,也会自动字节码插入,这个开关似乎没发现哪里用到?
        小红军storm:@格竹子 readercomponent 的 isRegisterCompoAuto = false 也会自动插桩吗,不是为true才插桩吗,我发现 好几个答案都没回答清楚这个问题
        格竹子:@zhuhf 这个变量的使用在ComCodeTransform中,你可以仔细看下
      • 格竹子:多啰嗦几句:方案最关键的是com.dd. comgradle插件,源码就在build-gradle这个module中。欢迎大家运行demo,实地感受一下组件化带来的便利。源码地址:https://github.com/luojilab/DDComponentForAndroid
        格竹子:最近大家回复和简信踊跃,谢谢大家,但是辛苦大家后面有问题到github上提交issue,这样可以让更多的人看到,回答也会更及时一些。
      • zhuhf:“如果组件开发并测试完成,需要发布一个release版本的aar文件到中央仓库,只需要把isRunAlone修改为false,然后运行assembleRelease命令就可以了。”

        ------ 应该是 xxxcomponent:assembleRelease 吧?
        格竹子:@zhuhf 谢谢啊
        格竹子:@zhuhf 是的,这是笔误,已经修改了
      • 卜俊文:问一个问题: 主app获取组件的fragment(这是一个自定义的fragmnet)后,如果想要调用这个自定的fragment的自定义方法,这该怎么办,有的时候可能我在主页面会调用某个fragment,让他刷新数据,这该怎么操作
        格竹子:@卜俊文 在交互上,app和组件是平等的,app向外提供服务也是采用同样的方式。特别在拆分初期,很多基础服务还留在app里面,这时候就需要app想组件提供各种服务。

        欢迎运行源码,很多问题分析源码后就能明白了
        卜俊文:哦哦,那如果组件想要调用app里面的方法呢?
        格竹子:@卜俊文 这是个很常见的场景,在我之前的回复中也解答过这种问题。

        你可以组件提供的服务(componentservice)中创建一个自定义的抽象类MyFragment,除了继承Fragment之外,还提供刷新数据等抽象方法。组件中具体的MyFragmentImpl继承MyFragment,并完成这些抽象方法的逻辑处理。而在app中是针对MyFragment来编程的,此时除了可以调用Fragment的方法,还可以调用自己定义的方法。

        这种模式与装饰器模式十分类似,包括包装来扩展功能。
      • 1d66a3c50b68:大神你好请教个问题,我的餐饮组件需要地图组件的 Fragment 显示覆盖物,数据该如何传递比较好?
        格竹子:@FTD彤 QQ群可能我没时间维护,现在群主要负法律责任的:fearful: ,有问题欢迎到github上提issue,我有时间一定解答的
        1d66a3c50b68:@格竹子 大神搞个qq群吧,一起学习研究下。
        格竹子:这个问题比较具体哈

        你可以在地图组件中提供的服务中创建一个自定义的抽象类MapFragment,除了继承Fragment之外,还提供诸如显示覆盖物、删除覆盖物等抽象方法。地图中具体的MyFragment继承MapFragment,并完成这些抽象方法的逻辑处理。然后地图的service将这个MyFragment当做MapFragment的实现类提供出去。

        餐饮组件针对MapFragment进行面向抽象编程即可。具体可以参考demo。
      • 键盘男:在我看来,在gradle.properties配置isRunAlone来切换library、application并不是好方案,之前某个作者某篇文章就这么干,估计很多人就跟他了。

        在我团队的组件化方案,是多个application来依赖业务module,不会library属性。因为配置越多,越难理解。

        ----

        下面是请教问题:

        是否每个业务组件,都发布到maven仓库?还是类似使用compile project()的模式,直接依赖本地代码?(即 compile modulePackage:module@aar到底做了什么)
        格竹子:@键盘男 嗯,第一个问题见仁见智吧,我的感觉是与组件调试相关的代码不是无关代码,而是必要的组成部分,并且在发布aar或者集成到其他组件的时候,这些代码是不会参与的。哪种都是可以的,方便使用并在团队达成共识都是没问题。

        第二个问题,建议完整运行一下demo,aar是打包之后自动拷贝带componentrelease这个文件夹下面的(这里可以换成maven库),自动拷贝的操作也是由com.dd. comgradle插件完成的。

        该方案的核心都在插件的源码中,建议使用插件,很多问题都自动解决了。
        键盘男:@格竹子

        1.多个application,入口activity是放在application的,跟module一点关系都没有。一个业务对应一个application,我认为不是累赘,是更清晰的代码边界。

        如果你把入口activity和一些初始化代码放在module,那就是module耦合了很多无关代码。但你可以在application做任何事情,多个同事还可以人手一个application,互相不影响。

        ----

        2.你是引用module的 build/outputs/aar/ 里面的文件吗?
        格竹子:首先回答你的第一个问题:你说的多个application的形式我之前也尝试过,这样有个缺点就是每个组件要单独调试的话都要创建一个application项目,并且会涉及到设置入口activity的问题。感觉这样一是module工程太多,二来不够内聚,所以把application项目内聚到每个组件里面,相当于自身的一个host框架,从而可以单独调试。至于isRunAlone的切换,这里的方案和之前的文章是截然不同的,不论是单独调试组件还是把组件集成到其他组件(当做library),isRunAlone永远都是true,不需要频繁更改。唯一需要更改的是发布aar的时候。
        第二个问题:目前支持两种方式来管理组件,目前得到更多使用的是直接集成(每次编译本地代码),没有把aar发布到maven仓库,原因在于每个组件还在优化中,这样就可以及时把修改编译打包。方案的创新之处在于虽然是每次都编译本地组件代码,但是没有直接使用compile project()的形式,原因在代码边界章节阐述了。而是通过一个gradle插件来自动引入,判断是否是打包命令,如果是则引入,否则不引入,这样就可以根绝错误调用。
        至于使用aar也是同样的道理,不过就是使用aar就不需要每次再编译一次了而已。
      • c498954c2d50:看完了 ,一点还是没搞懂。 *.aar(实际上就是一个功能模块)是怎摸编译到componentrelease文件夹下的?
        (当然 ,可以运行该模块,然后复制build/outputs/aar/*.aar到componentrelease文件夹下的)
        格竹子:@格竹子 有问题欢迎到github上提issue,顺便给个star,哈哈
        格竹子:你如果使用了源码中的com.dd. comgradle这个插件,只要手动把inRunAlone改为false,运行mudule:assembleRelease就会自动将生成的aar拷贝到componentrelease文件夹下面。注意此处是唯一需要修改inRunAlone的情况,如果想单独运行,请将inRunAlone修改会true。(修改inRunAlone之后需要sync才会生效)
      • 5004766320f5:其他比较好懂,build-gradle这块有什么资料推荐吗?apply plugin: 'com.dd.comgradle'类似这种怎么自定义的?
        andyhaha007:怒赞楼主 学习了大神
        格竹子: @Sprite_bc41 这个你搜一下自定义gradle插件吧,多找几篇文章就好了
      • 2c0ffbf87bd2:您好,大神,我想问下第五点代码边界中的那段代码是放在哪里的?
        2c0ffbf87bd2:@格竹子 谢谢。
        格竹子:@维维诺 这个在gradle插件里面,具体的路径是build-gradle下面的ComBuild.groovy类里面
      • 前山饭店:看来我的看好几篇才能理解:pensive:
        格竹子: @前山饭店 组件化虽然老生长谈,但是要讲清楚还是挺费劲的,文章我改了好多次,可能表述还是不够准确,有问题随时交流吧
      • 82ef9df2a80f:666,组件的卸载与加载能理解成组件入口View视图的显隐吗?数据传递这块感觉现在的路由框架都能携带数据过去了额
        格竹子:@面对疾风吧啊 我在代码边界章节里面专门讲了, 这样就没有做到完全隔离,很容易就直接使用组件的实现类,从而导致组件化的工作前功尽弃。使用开源项目中的插件可以做到在开发期间组件完全不可见,但是参与编译打包,就可以完全消除这个隐患。这也是这个方案最关键的地方。
        82ef9df2a80f:@格竹子 if (BuildConfig.One) {
        //One模块是可以运行的

        } else {
        //One模块是作为library
        RouterService.registerComponent("com.onemodule.OneApplicationLike");
        },这个是app里application的,gradle.properties里面有标记One模块是否是library的布尔值,然后后面copy了楼主你的代码,就是没有用到插件,插件代码看得很蒙蔽技术不够还;这样也是可以拿到One模块里的数据,不知道有什么弊端
        格竹子: @面对疾风吧啊 不仅是入口view的显隐,一旦组件被卸载,那么不论是ui还是数据都是不可达的
      • cheetah747:把isRunAlone一改成false并同步gradle就报错Error:Library projects cannot set applicationId. applicationId is set to 'com.mrzhang.androidcomponent' in default config.
        格竹子: @cheetah747 demo中applicationid实在manifest中设置的,如果在build.gradle中设置,需要在设置applicationid的地方加上当前isRunAlone为true的判断
      • cheetah747:如果basiclib特别大怎么办?不需要拆分吗?。。。。非常非常非常大。。。。
        格竹子: @cheetah747 是不是在第一篇文章中也问了这个问题?参考那篇文章的回答吧
      • xingstarx:支持下,拜读大佬的著作:cold_sweat:
      • 魔攻力缆狂澜聊:哇非常厉害
      • 依然范特稀西:mark,支持一下
      • 疯魔程序员:不错的组件化,值得期待
      • shijunxing:支持一下,第一时间收到推送
        格竹子:👍,谢谢

      本文标题:Android彻底组件化demo发布

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