ComponentizationApp icon indicating copy to clipboard operation
ComponentizationApp copied to clipboard

组件化插件化理解

Open chuyun923 opened this issue 9 years ago • 23 comments

hi,很高兴看到您在知乎上的留言,有些问题向您请教一下~~

通过gradle 配置的方式,将打 debug 包和 release 包分开。这样会有一个好处,开发一个模块,在 debug 的时候,可以打成一个 apk ,独立运行测试,可以完全独立于整个宿主 APP 的其他所有组件;待到要打 release 包的时候,再把这个模块作为一个 library ,打成 aar ,作为整个宿主 APP 的一部分。而 debug 和 release 的切换都是通过 gradle 配置,可以做到无缝切换。至于模块之间的跳转,可以用别名的方式,而不是用 Activity 和 Fragment 类名。这样所有的模块和宿主 APP 都是完全解耦的,彻底解决了并行开发的可能造成的交叉依赖等问题。

这一块理解在demo中理解的不是很好。不知是否想表达如下意思:

  1. 所谓组件划分的力度是以module为单位的,一个module为一个组件,在发布组装的时候作为一个aar引入
  2. 上面的文字表述的意思是:一个APP由 一个主客(宿主)+若干组件(aar)组成;期望的一个状态是 debug时,只有某一个开发的module引入,其他module并没有加入到宿主中;release时,所有的组件都加入到宿主中?具体在demo中是怎么体现的?

chuyun923 avatar Sep 27 '16 07:09 chuyun923

@chuyun923 假如是四个人同时开发一个 APP ,每个人负责一个模块,我们先想一下正常的开发模式,一个人在自己的模块里写了一个 util(只是一个例子,也可能是其他的类),然后提交代码了,另一个人更新下来了代码,接下来写代码刚好用到了这个 util 里的一个方法或者变量,于是直接就拿来用来,久而久之,这两个模块的耦合度会越来越高。未来某一天,那个人把 util 里的那个方法或者变量删了,结果整个团队都编译不过了。

为什么他们能相互引用呢?因为他们都是作为宿主的一个 library 而存在的啊,都是属于宿主的一部分。如果要想他们之间没有耦合,在没有任何黑科技引入的情况下,就只好把每个模块都打成 apk ,两个完全独立的 apk 工程,是无法引用彼此的类的。而且每个模块都是一个可以独立运行的 apk ,这样就可以去单独地去测试维护这个模块。

而最终是要打成一个 apk 的,因此在 release 的时候,其他模块又不能作为 apk 存在,否则正常情况下无法加载到模块里的类。所以 release 的时候,就把这个模块作为一个 library ,而整个工程就是一个普通的 APP 工程。

但是这样就涉及到可能在 debug 和 release 时候频繁地改代码,于是 Gradle 的优势就体现出来了,通过 Gradle 的配置就可以实现无需更改代码,在我写的 demo 里只需要将 gradle.properties 里的 isDebug 值改一下就可以实现无缝切换了。

cxyxlxdm avatar Sep 27 '16 08:09 cxyxlxdm

@liangzhitao 你好,您上面的回答是一些相互之间关联性比较弱的模块可以使用。

不知道面对相互之间有强关联性的情况该如何解决,如下:

A.用户登录模块 B.用户信息模块 C.用户个性化推荐模块

上面三个模块,B,C 必然需要走过 A 的界面(Activity),不然无法获取到用户信息,这个时候,B,C 模块的开发者该如何独立的进行测试,维护?

因为当A,B,C 作为独立 apk 时并不能共享很多数据,这个时候有些信息的传递反而会复杂。

不知道我理解的对不对,求指导

flyer88 avatar Sep 27 '16 09:09 flyer88

@flyer88 这是个很好的问题。

实际上在 MDCC 上 oasisfeng 也有讲到这个问题,他给出的方案是不管是传递数据还是提供服务,都可以使用 BroadcastReceiver 和 ContentProvider ,甚至是共享进程,来进行通信。这是一种非常轻量的解决方案,同样是不牵涉任何黑科技,甚至是稍微复杂一点的技术。

不过我其实想到有另外一种方案,如果有一些后端的经验或者对 Atlas 有一些了解的话,很容易可以想到 OSGi 这种方式。每个模块单独提供出服务,供其他模块去使用。这是一种比较复杂的解决方案,在组件化这样一个轻量的解决方案里显得重一些,需要在编译期和运行期分别做处理,使得编译期和运行期所引用的类要都能够被找到。

需要注意的一点,本着最大化解耦的原则,这两种方式,都要求当前模块的负责人在提供对外服务的时候,要对所有的可能会用到的接口进行收敛,不管是使用 BroadcastReceiver 和 ContentProvider ,还是类 OSGi ,都要尽可能少地暴露接口。

cxyxlxdm avatar Sep 27 '16 11:09 cxyxlxdm

但是这样就涉及到可能在 debug 和 release 时候频繁地改代码,于是 Gradle 的优势就体现出来了,通过 Gradle 的配置就可以实现无需更改代码,在我写的 demo 里只需要将 gradle.properties 里的 isDebug 值改一下就可以实现无缝切换了。

修改app 和 componentizationlibrary下的 gradle.properties配置isDebug为true时,run起来的应该是componentizationlibrary中的LibraryActivity作为LAUNCHER启动?然而我修改成true之后,还是启动的app中的MainActivity。

chuyun923 avatar Sep 27 '16 14:09 chuyun923

qq20160927-7 2x 要在这里选择安装哪个 module 里打出来的包。

或者也可以点击右侧边栏的 install group 里的任务安装,不过不推荐,会有 bug 。 qq20160927-8 2x

推荐在终端里用命令去安装。

cxyxlxdm avatar Sep 27 '16 15:09 cxyxlxdm

嗯,了解了,修改了gradle.properties之后并不会导致gradle重新构建项目,在build里面加个空格,sync一下就好了

chuyun923 avatar Sep 28 '16 03:09 chuyun923

有个问题请教下,正如你所说利用gradle配置 在release跟debug包中自由切换是编译生成aar还是apk,对单模块调试很方便,反映到demo中

module app下的bulid.gradle文件中有如下配置 dependencies { println isDebug.toBoolean() provided fileTree(dir: project(':componentizationlibrary').projectDir.absolutePath + '/outputs/jar/', include: 'sharedDependency.jar') if (!isDebug.toBoolean()) { compile project(':componentizationlibrary') } else { compile 'com.android.support:appcompat-v7:24.2.1' compile 'com.android.support:design:24.2.1' } } 实际在命令行gradle assembleDebug的过程中 isDebug.toBoolean()如果为true(gradle.properties配置为true)是会报类引用不到的错误,所以这个分支判断是否多余

fqcheng220 avatar Oct 13 '16 08:10 fqcheng220

@fqcheng220 哪个类引用不到?日志贴一下看看。

cxyxlxdm avatar Oct 14 '16 01:10 cxyxlxdm

:app:compileDebugJavaWithJavac - is not incremental (e.g. outputs have changed, no previous execution, etc.). E:\MyWork\ComponentizationApp-master\app\src\main\java\cn\easydone\componentizationapp\MainActivity.java:12: 错误: 程序包cn.easydone.componentizationlibrary不存在
import cn.easydone.componentizationlibrary.LibraryActivity; ^ E:\MyWork\ComponentizationApp-master\app\src\main\java\cn\easydone\componentizationapp\MainActivity.java:36: 错误: 找不到符号
Intent intent = new Intent(MainActivity.this, LibraryActivity.class); ^ 符号: 类 LibraryActivity
2 个错误
:app:compileDebugJavaWithJavac FAILED

fqcheng220 avatar Oct 14 '16 03:10 fqcheng220

@fqcheng220 在 extra_config.gradle 的脚本可以导出一个 Library 的 'sharedDependency.jar'

provided fileTree(dir: project(':componentizationlibrary').projectDir.absolutePath + '/outputs/jar/', include: 'sharedDependency.jar')

就是为了引用的时候不报你日志里的这个错误。

cxyxlxdm avatar Oct 14 '16 06:10 cxyxlxdm

@liangzhitao 现在实际情况是ComponentizationApp-master目录下 gradle assembleDebug 就会报我提的错误呢(gradle.properties配置为true)但是如果我切换到componentizationlibrary目录下gradle assembleDebug可以正常生成componentizationlibrary对应的apk, 所以你上面提到的app下的build.gradle 貌似对编译app模块不起作用 不能在isDebug为true时候工作

我看了下app模块下有outputs/jar/sharedDependency.jar生成,所以大概是什么原因导致这个错误呢

fqcheng220 avatar Oct 14 '16 07:10 fqcheng220

@fqcheng220 demo 是没问题的,你再检查检查吧。

cxyxlxdm avatar Oct 17 '16 05:10 cxyxlxdm

@liangzhitao 志涛,这个思路很棒!

baoyachi avatar Oct 26 '16 10:10 baoyachi

@baoyachi :)

cxyxlxdm avatar Oct 26 '16 11:10 cxyxlxdm

hi, 你好 @liangzhitao 请教一下, 假设一个应用是一个FragmentActivity+多个fragment, 每个fragment一个模块,如果每个单独的fragment设置为一个模块,他怎么设置他作为单独的apk运行?

csatshell avatar Nov 13 '16 07:11 csatshell

@csatshell 真要这样用的话,那就没办法了,至少是我没想到有什么办法。也不建议这样用的,最好一个 module 至少保留一个 Activity ,将 Activity 作为 Fragment 的容器,这样也不容易出现 Fragment 嵌套很多层的情况。

cxyxlxdm avatar Nov 13 '16 09:11 cxyxlxdm

求知乎讨论的原贴,我想看看

jiezigg avatar Nov 25 '16 02:11 jiezigg

@zyn4399 https://www.zhihu.com/question/19981105

flyer88 avatar Nov 25 '16 03:11 flyer88

这个思路很棒,很好的解决团队协同开发的问题。用比较小的代价,换来项目的解耦、开发效率的提升。 对于暂时还没有需求做插件化的我,非常实用。

chenjunqi avatar Dec 22 '16 06:12 chenjunqi

至于模块之间的跳转,可以用别名的方式,而不是用 Activity 和 Fragment 类名。这样所有的模块和宿主 APP 都是完全解耦的,彻底解决了并行开发的可能造成的交叉依赖等问题。

我想问一下,启动别的module的activity可以用action等方式, 那么引用别的module的fragment要怎么引用呢

caibou avatar Dec 22 '16 09:12 caibou

假如是四个人同时开发一个 APP ,每个人负责一个模块,我们先想一下正常的开发模式,一个人在自己的模块里写了一个 util(只是一个例子,也可能是其他的类),然后提交代码了,另一个人更新下来了代码,接下来写代码刚好用到了这个 util 里的一个方法或者变量,于是直接就拿来用来,久而久之,这两个模块的耦合度会越来越高。未来某一天,那个人把 util 里的那个方法或者变量删了,结果整个团队都编译不过了。 为什么他们能相互引用呢?因为他们都是作为宿主的一个 library 而存在的啊,都是属于宿主的一部分。如果要想他们之间没有耦合,在没有任何黑科技引入的情况下,就只好把每个模块都打成 apk ,两个完全独立的 apk 工程,是无法引用彼此的类的。而且每个模块都是一个可以独立运行的 apk ,这样就可以去单独地去测试维护这个模块。

按照你这么说, 还是解决不了那个人把 util 里的那个方法或者变量删了,结果整个团队都编译不过了的问题啊 @liangzhitao

caibou avatar Dec 22 '16 10:12 caibou

我遇到和@fqcheng220 类似的问题,如果插件化的思路是为了在debug=true的时候能够进行多个module的并行开发,此时主工程通过shareDependency.jar引入的依赖不会像module一样进行资源合并,这样只解决了依赖存在不报错的问题而不能使得主工程正常执行业务逻辑。既然无法正常运行业务,还不如不要允许主工程成为可运行状态,对于开发者来说更加友好。可以使用类似module处理manifest的方法,将debug=true时候的manifest中主activity注释掉。

ytqiu avatar Feb 03 '17 06:02 ytqiu

@ytqiu 参考 https://github.com/airbnb/DeepLinkDispatch

cxyxlxdm avatar Feb 03 '17 07:02 cxyxlxdm

@csatshell 可以在src 下新建一个debug包,用WrapActivity包住fragment,并作为启动类。 并且在realse模式下 过滤调debug java { exclude 'debug/**' }

xxm-wu avatar Oct 19 '17 03:10 xxm-wu