一、起因 早期的时候,一个项目都在一个一起,通过包名来控制不同的模块和功能。这种方式的缺点:
层次混乱:无论怎么做分包,随着项目增大,就会失去层次感,接手的人扑街
耦合度高,低内聚:。包名约束太弱,稍不注意就不同业务包直接相互调用
不易于版本管理,容易代码冲突
难以重用
所以,很容易想到组件化的好处:
不相互依赖
可以互相交互
高度解耦
自由拆卸组合
重复利用
二、组件化环境 各个模块组件都能单独打包,对于测试是很友好的。在正式上线的时候,所有模块都需要App 壳才能运行。
2.1 Gradle gradle 的根在哪里? 就是 settings.gradle 这个文件!然后,我们整个项目有个 gradle ,这个 project 就在项目根目录下 build.gradle 。 build 的步骤就是:
settings.gradle
Project 级别的 build.gradle
壳工程的 build.gradle
library 中的 build.gradle
app 和 各个 module 中都有 gradle 文件,里面可能会包含相同代码,比如 编译工具版本、最小支持版本。我们可以在project 下面新建 gradle ,比如命名为 derry.gradle,在里面写上 ext 扩展块 ,代码如下:
1 2 3 4 5 6 ext { compileSdkVersion 30 defaultConfit { minSdkVersion 16 } }
不过此时还不能被系统所认识,只能知道这是 key-value 的形式,我们只能将其引入到project 所属的gradle 中 才能实现,那么,在 Project 的 build.gradle 中可以写如下代码:
1 apply from: 'derry.gradle'
所以,在各个模块中,可以使用这个公共的gradle 文件了:
1 2 3 4 def ext = rootProject.extandroid { compileSdkVersion ext.compileSdkVersion }
这里不去定义 def ext 也是可以的,但是为什么要这么做呢?这是为了性能考虑,因为这样定义一下就相当于局部变量了,能提高运行速度(老师说这个可以在面试时候去说的,说明真的玩过gradle)。
最后,放上App 壳和各个模块之间的配置,可以做到:
配置代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 ext { isRelease = false url = [ "debug" : "https://192.188.22.99/debug" , "release" : "https://192.188.22.99/release" ] androidID = [ compileSdkVersion : 30 , buildToolsVersion : "30.0.1" , applicationId : "com.derry.derry" , minSdkVersion : 16 , targetSdkVersion : 30 , versionCode : 1 , versionName : "1.0" , testInstrumentationRunner: "androidx.test.runner.AndroidJUnitRunner" ] appID = [ app: "com.derry.modularproject" , login: "com.derry.login" , register: "com.derry.register" ] dependenciesID = [ "appcompat" : "androidx.appcompat:appcompat:1.2.0" , "constraintlayout" : "androidx.constraintlayout:constraintlayout:2.0.1" , "material" : "com.google.android.material:material:1.1.0" , ] }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 apply from : 'derry.gradle' buildscript { repositories { google() jcenter() } dependencies { classpath "com.android.tools.build:gradle:4.0.1" } } allprojects { repositories { google() jcenter() } } task clean(type: Delete) { delete rootProject.buildDir }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 apply plugin: 'com.android.library' println "Derry ---> lib Student hao 2" android { compileSdkVersion androidID.compileSdkVersion buildToolsVersion androidID.buildToolsVersion defaultConfig { minSdkVersion androidID.minSdkVersion targetSdkVersion androidID.targetSdkVersion versionCode androidID.versionCode versionName androidID.versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt' ), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: "libs" , include: ["*.jar" ]) dependenciesID.each {k,v -> implementation v} testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 if (isRelease) { apply plugin: 'com.android.library' } else { apply plugin: 'com.android.application' } android { compileSdkVersion 30 buildToolsVersion "30.0.1" defaultConfig { if (!isRelease) { applicationId appID.login } minSdkVersion 16 targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt' ), 'proguard-rules.pro' } } sourceSets { main { if (!isRelease) { manifest.srcFile 'src/main/debug/AndroidManifest.xml' } else { manifest.srcFile 'src/main/AndroidManifest.xml' java { exclude "**/debug/**" } } } } } dependencies { implementation fileTree(dir: "libs" , include: ["*.jar" ]) implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' }
上面代码可能有一些重复的,每个 gradle 文件可能侧重某一个点,需要注意甄别。
三、组件之间的通信方式 如果订单模块想要访问个人模块的信息,那我们必须要有个什么注册表,我们将这些功能注册到这个注册表中,需要调用的时候,就通过这个注册表。
组件化几种可行的通信方式:
EventBus:缺点是EventBean 的维护成本太高,不好管理
广播:不好管理,都统一发送出去了,并且后续Android版本广播都需要动态注册了(这点存疑,需要验证)
使用隐式意图:这个就更麻烦了,要求每个 Activity 都必须有自己唯一 的action 名字
类加载方式:容易写错包名的类,相对而言缺点较少,可以尝试
使用全局 Map :因为所有的 module 都需要依赖公共基础库,所以可以在公共基础库中添加一个 Map ,注册所有的Activity, 需要注册很多对象,相对而言缺点少,可以尝试
其中类加载和 全局 Map 的方式使用代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public void jumpPersonal (View view) { try { Class targetClass = Class.forName("com.xiangxue.personal.Personal_MainActivity" ); Intent intent = new Intent(this , targetClass); intent.putExtra("name" , "derry" ); startActivity(intent); } catch (ClassNotFoundException e) { e.printStackTrace(); } Class<?> targetActivity = RecordPathManager.startTargetActivity("personal" , "Personal_MainActivity" ); startActivity(new Intent(this , targetActivity)); }
需要注意的一点是,使用类加载方式,我们使用的是 Class.forName 的方式,这个是反射吗?这并不是反射,这只是类加载!反射我们是指反射属性,方法等,我们也不会看到导入的包里面有 Reflect 等反射的包名
当然,使用全局 Map 的方式必须还要在 Common 公共依赖里面有管理类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public class RecordPathManager { private static Map<String, List<PathBean>> maps = new HashMap<>(); public static void addGroupInfo (String groupName, String pathName, Class<?> clazz) { List<PathBean> list = maps.get(groupName); if (null == list) { list = new ArrayList<>(); list.add(new PathBean(pathName, clazz)); maps.put(groupName, list); } else { maps.put(groupName, list); } } public static Class<?> startTargetActivity(String groupName, String pathName) { List<PathBean> list = maps.get(groupName); if (list == null ) { Log.d(Config.TAG, "startTargetActivity 此组名得到的信息,并没有注册进来哦..." ); return null ; } for (PathBean pathBean : list) { if (pathName.equalsIgnoreCase(pathBean.getPath())) { return pathBean.getClazz(); } } return null ; } }
然后,我们可以在各个 Module 里面需要注册当前 Module 所拥有的全部 Activity (当然,这个并不优雅,开发者可能会忘记,可以采用注解或者其他的方式去做,这里只是简单实现):
1 2 3 4 5 6 7 8 9 10 11 12 public class AppApplication extends BaseApplication { @Override public void onCreate () { super .onCreate(); RecordPathManager.addGroupInfo("app" , "MainActivity" , MainActivity.class); RecordPathManager.addGroupInfo("order" , "Order_MainActivity" , Order_MainActivity.class); RecordPathManager.addGroupInfo("personal" , "Personal_MainActivity" , Personal_MainActivity.class); } }
所以,全局Map 的方案跳转的时候,只需要标明你想跳转哪个module ,以及module 中的哪个 Activity。但是上述方式还是有点麻烦,这时候,阿里开源的 Arouter就应运而生了 。
组件化通信框架很多,但是目前最优秀的是 ARouter 。
组件化:模块之间没有依赖,便于重用
插件化:侧重动态化加载某些功能,主要问题是兼容性问题,支付宝都放弃了,因为你兼容了 5.0 ,能兼容 11.0 吗
模块化:模块化是业务层面的拆分,组件化是功能层次的划分
Google 表态说是 FrameWork 仍然还是 Java ,不会改成 Kotlin