参考http://blog.csdn.net/guiying712/article/details/55213884
参考https://blog.csdn.net/c10WTiybQ1Ye3/article/details/79578883
很辣鸡
组件化是什么都不知道
组件化,就是相当于把原来一大块集中在一起的业务功能逻辑分成多个模块,像是一个个组件可以组合。
分开呢,可以让每个成员专心开发自己的模块,而不会受到其他的模块的影响。反正就是不好!你得换!来看看别人写的优点。
- 实际业务变化非常快,但是单一工程的业务模块耦合度太高,牵一发而动全身;
- 对工程所做的任何修改都必须要编译整个工程;
- 功能测试和系统测试每次都要进行;
- 团队协同开发存在较多的冲突.不得不花费更多的时间去沟通和协调,并且在开发过程中,任何一位成员没办法专注于自己的功能点,影响开发效率;
- 不能灵活的对业务模块进行配置和组装;
反正我是没看懂
譬如某宝,达成千上百的功能模块时,涉及的开发人可能非常多,而不可能每个人都要去持有全部源码进行开发,所以当项目上了数量级的时候就需要考虑使用组件化开发。
组件化所要知道的
结构组成
main组件: 主要是负责路由转发跳转,没有实际的业务功能,作为app壳连接各个业务组件,需要依赖共同组件。根据是否组件开发,选择依赖业务组件。
业务组件: 实际业务逻辑实现的组件,依赖于共同组件(自己特有的第三方依赖可能还是写在自己的builde.gradle里好,比如即时通信模块的sdk和定位模块的sdk写在自己里面)
共同组件:提供共同使用的方法类,或者规定基类,以及依赖公共库的组件(比如rxjava okhttp retrofit等)
有个需要注意的,后面路由转发可以用arouter来实现。arouter的api是只要在共同组件中依赖就可以了,但是注解解释器annotationProcessor是需要写在注解所使用的地方,也就是各自业务组件的build.gradle中的
总体框架
这个是wustor大神文章中的结构图,我私以为RouterModule ,还有BaseModule,LibraryModule可以归到为一个lib_commom
OB分析
在组件化的过程中,由于Module之间是隔离的,所以就产生了一系列问题,现在就组件化前后的遇到的问题总结如下:
组件划分:如何根据业务对项目进行Module划分
模式切换:如何使得APP在单独调试跟整体调试自由切换
资源冲突:当我们创建了多个Module的时候,如何解决相同资源文件名合并的冲突
依赖关系:多个Module之间如何引用一些共同的library以及工具类
组件通信:组件化之后,Module之间是相互隔离的,如何进行UI跳转以及方法调用
入口参数:我们知道组件之间是有联系的,所以在单独调试的时候如何拿到其它的Module传递过来的参数
那么接下来对wustor大神文章中提到的几个问题我们来回答一下
组件划分
就按业务逻辑划分呗,有啥好问的
模式切换
本文的第一个技术点
开发的时候分成集成模式和组件模式。
组件模式就是各单位自己开发自己的模块,在AS这种对应的就是module,为了让模块可以测试,那就是application形态。 集成模式下,那其他那些组件就是要变成library形态供主module调用。
mainfest文件的切换,集成模式下,业务模块是不需要LAUNCHER界面等
application的切换,在组件模式下,每个业务app都会有自己application,在里面做不同的初始化操作。而到了集成模式下,则所有的业务组件都需要使用同一个application。所以我们让所有业务组件的application去继承一个公共的appliction即可
做法(以下拿一个模块举例,其他一样)
First
在gradle.properties文件中声明一个Boolean值,这个值在全局都是会被读取的。
集成模式下,业务组件为一个独立的app。组件模式下,业务组件作为库被依赖,则
if (isTogether.toBoolean()) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
另外
if (isModuleRun.toBoolean()) {
applicationId "com.kt.newsapp"
}
Second
manifest.xml
main/manifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.kt.newsapp">
<application
android:theme="@style/AppTheme">
<activity android:name=".NewsActivity">
</activity>
</application>
</manifest>
main/module/mainfest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.kt.newsapp">
<application
android:name="debug.NewsApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".NewsActivity">
</activity>
<activity android:name="debug.WelcomeActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
然后在build.gradle的android模块中加上
sourceSets {
main {
if (isTogether.toBoolean()) {
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成开发模式下排除debug文件夹中的所有Java文件
java {
exclude 'debug/**'
}
} else {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
}
}
}
Third
在lib_commom中定义一个baseapplication,然后每个业务组件都创建一个app去继承它。因为自己的application只有在组件模式下有用,而在继承模式下,只能用main组件里的application(因为全局application只能有一个)。所以把自己业务组件的app放置在debug包下。然后在集成开发时剔除掉即可。方法同上面第二点。
然后集成开发时,大概是将所有的业务组件application代码复制进main组件的application里面里面
稍微提一下,debug包下面还有一个welcomeActivity可以为你这个模块做一些初始化,比如说商品详情模块,要进入支付模块,需要传递商品传输,然后组件开发的时候,是获取不到,所以你可以在这个界面里intent.putextre这样,或者有些模块的网络请求是需要夹带token,那我们也可以在这个界面模拟获取token,这个界面甚至可以不用setcontentview。
资源冲突
不同module的同名资源会冲突,是可以通过一些配置强制限定资源前缀,如果没有按照规定会出错,但是觉得不用配置,直接约定好即可,以什么为前缀,就不要多此一举。
依赖关系
见上图,可以将routerModule,basemodule, librarymodule集成在一起
组件通信
这是本文的第二个技术点
虽然在一个模块内部,大部分逻辑应该还是在这个模块内进行处理的,但是偶尔还是会跟别的Module进行打交道,接下来介绍的就是某个业务组件跳转到另外一个组件的fragment
通过main组件来进行路由转发跳转的时候,因为 main组件和各个业务组件都是依赖lib_commom组件,所以他们之间是相互分离,是不能直接获取到fragment的。
但是这是对于编译期来说的,对于运行期来说,大家肯定是可以互相可见的,所以我们想到了反射机制
(Java反射机制是指在运行状态中
对于任意一个类,都能知道这个类的所有属性和方法;
对于任何一个对象,都能够调用它的任何一个方法和属性;
详情查看https://blog.csdn.net/TOYOTA11/article/details/52420046)
反射获取fragment,然后就可以添加到main组件的bottomnavigationActivity中
public static Fragment getFragment(String name) {
Fragment fragment;
try {
Class fragmentClass = Class.forName(name);
fragment = (Fragment) fragmentClass.newInstance();
} catch (Exception e) {
Log.d("error--->", e.toString());
return null;
}
return fragment;
}
反射跳转界面
public static void startActivityWithName(Context context, String name) {
try {
Class clazz = Class.forName(name);
startActivity(context, clazz);
} catch (ClassNotFoundException e) {
CommonUtils.showToast(context, "业务组件单独调试不应该跟其他业务Module产生交互,如果你依然想要在运行期依赖其它组件,那么请参考MainModul");
Log.d("error--->", e.toString());
}
}
具体代码参见ReflectUtils
除了反射的方法外,还有个arouter第三方来获取或者跳转,操作简单方便。(Activity跳转没问题,但是fragment貌似有点问题,暂时没有解决)
最后 组件化项目的混淆方案
组件化项目的Java代码混淆方案采用在集成模式下集中在app壳工程中混淆,各个业务组件不配置混淆文件。集成开发模式下在app壳工程中build.gradle文件的release构建类型中开启混淆属性,其他buildTypes配置方案跟普通项目保持一致,Java混淆配置文件也放置在app壳工程中,各个业务组件的混淆配置规则都应该在app壳工程中的混淆配置文件中添加和修改。
之所以不采用在每个业务组件中开启混淆的方案,是因为 组件在集成模式下都被 Gradle 构建成了 release 类型的arr包,一旦业务组件的代码被混淆,而这时候代码中又出现了bug,将很难根据日志找出导致bug的原因;另外每个业务组件中都保留一份混淆配置文件非常不便于修改和管理,这也是不推荐在业务组件的 build.gradle 文件中配置 buildTypes (构建类型)的原因。