目录
为什么需要组件化组件化和模块化模块化架构组件化架构组件化带来的优势组件化需解决的问题资源冲突解决AndroidManifest独立调试单工程方案多工程方案页面跳转Arouter 实现组件间方法调用组件化的消息通信方式选择广播事件总线Application生命周期分发为什么需要组件化
小项目是不需要组件化的。当一个项目有数十个人开发,编译项目要花费10分钟,修改一个bug就可能会影响到其他业务,小小的改动就需要进行回归测试,如果是这种项目,那么我们需要进行组件化了
组件化和模块化
在技术架构演进的过程一定是先出现模块化后出现组件化,因为组件化就是解决了模块化的问题。
模块化架构
创建一个>implementation project(':B')依赖 B 模块,但是 B 模块又需要跳转到 A 模块的某个页面,于是 B 模块又依赖了 A 模块。这样的开发模式依然没有解耦,改一个bug依然会改动很多模块,并不能解决大型项目的问题。
如下图所示,一开始我们定义的module之间并没有过多耦合:
然后,随着项目的不断迭代,相互调用的情况会增多,也会增加一些库的扩展和调用,工程的架构可能变为:
可以看出,各种业务之间的耦合非常严重,导致代码非常难以维护,更难以测试,扩展和维护性非常差,这样的架构肯定会被替代。
随着时间的推移,出现了组件化、插件化等组织架构。
组件化架构
这里先提几个概念,我们日常业务需求开发的组件叫做业务组件,如果这个业务需求是可以被普遍复用的,那么叫做业务基础组件,譬如图片加载、网络请求等框架组件我们称为基础组件。搭建所有组件的app组件称为壳组件/工程。接下来看一张架构图:
实线表示直接依赖关系,虚线表示间接依赖。比如壳工程肯定是要依赖业务基础组件、业务组件、module_common公共库的。业务组件依赖业务基础组件,但并不是直接依赖,而是通过”下沉接口“来实现间接调用。业务组件之间的依赖也是间接依赖。最后common组件依赖所有需要的基础组件,common也属于基础组件,它只是统一了基础组件的版本,同时也提供了给应用提供一些抽象基类,比如BaseActivity、BaseFragment,基础组件初始化等。
组件化带来的优势
加快编译速度:每个业务组件都可以单独运行调试,速度提升好几倍。举个例子:video组件单独编译运行时间为3s,因为此时AS只会运行video组件以及video组件依赖的组件的task,而如果集成编译时间为10s,app所引用的所有的组件的task都会执行。可见,效率提升了3倍。
提高协作效率:每个组件都有专人维护,不用关心其他组件是怎么实现的,只需要暴露对方需要的数据。测试也不需要整个回归,只需要重点测试修改的组件即可。
功能重用:一次编码处处复用,再也不需要了。尤其是基础组件和业务基础组件,基本上调用者根据文档就可以一键集成和使用。
确保了整体技术方案的统一性,为未来插件化公用一套底层模型做准备。
前面有提到非大型项目一般不会进行组件化,但是就像上面提到的功能重用,这个优势并不是只能用到大型项目>
组件化需解决的问题
如何解决资源配置冲突问题?
业务组件如何实现单独调试?
业务组件间没有依赖,如何实现页面跳转?
业务组件间没有依赖,如何实现数据通信?
业务组件间没有依赖,如何实现消息通信
壳工程Application生命周期如何下发?
资源冲突解决
AndroidManifest
每个module都有一份AndroidManifest文件来记载信息,最终生成一个App的时候,只会有一份AndroidManifest来知道APP应该去如何配置,Manifest>
在build/intermediates/manifest_merge_blame_file下会生成一份合并报告
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.liang.mosic"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="21"
->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml
android:targetSdkVersion="30" />
->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:12:5-67
-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:12:22-64
<application
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:6:5-48:19
android:name="com.liang.mosic.ModuleApplication"
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:10:9-42
android:allowBackup="true"
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:7:9-35
android:appComponentFactory="androidx.core.app.CoreComponentFactory"
-->[androidx.core:core:1.5.0] C:\Users\Administrator.gradle\caches\transforms-2\files-2.1\4c9b62de2468f1520f5d232befb24ab8\core-1.5.0\AndroidManifest.xml:24:18-86
android:debuggable="true"
android:icon="@mipmap/ic_launcher"
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:8:9-43
android:label="@string/app_name"
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:9:9-41
android:supportsRtl="true"
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:11:9-35
android:testOnly="true"
android:theme="@style/AppTheme" >
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:13:9-40
<!-- <activity android:name=".MainActivity"> -->
<!-- <intent-filter> -->
<!-- <action android:name="android.intent.action.MAIN" /> -->
<!-- <category android:name="android.intent.category.LAUNCHER" /> -->
<!-- </intent-filter> -->
<!-- </activity> -->
<activity
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:21:9-28:20
android:name="com.liang.mosic.AdaviceActivity"
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:21:19-50
android:theme="@style/AppWelcome" >
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:22:13-46
<intent-filter>
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:23:13-27:29
<action android:name="android.intent.action.MAIN" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:17-69
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:25-66
<category android:name="android.intent.category.LAUNCHER" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:17-77
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:27-74
</intent-filter>
</activity>
<activity android:name="com.liang.mosic.ModuleMainActivity" >
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:29:9-40:20
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:29:19-53
<!-- <intent-filter> -->
<!-- <action android:name="android.intent.action.MAIN" /> -->
<!-- <category android:name="android.intent.category.LAUNCHER" /> -->
<!-- </intent-filter> -->
<intent-filter>
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:35:13-39:29
<action android:name="com.liang.main" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:36:17-60
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:36:25-57
<category android:name="android.intent.category.DEFAULT" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:17-76
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:27-73
</intent-filter>
</activity>
<activity android:name="com.liang.mosic.ModuleExampleActivity" >
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:41:9-47:20
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:41:19-56
<intent-filter>
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:42:13-46:29
<action android:name="com.liang.moduleFragment" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:43:17-70
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:43:25-67
<category android:name="android.intent.category.DEFAULT" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:17-76
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:27-73
</intent-filter>
</activity>
<activity android:name="com.liang.a.MainActivity" >
-->[:a] C:\Users\Administrator\Desktop\mosic-master\mosic-master\a\build\intermediates\library_manifest\debug\AndroidManifest.xml:17:9-23:20
-->[:a] C:\Users\Administrator\Desktop\mosic-master\mosic-master\a\build\intermediates\library_manifest\debug\AndroidManifest.xml:17:19-61
<intent-filter>
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:23:13-27:29
<action android:name="android.intent.action.MAIN" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:17-69
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:25-66
<category android:name="android.intent.category.LAUNCHER" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:17-77
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:27-74
</intent-filter>
</activity>
<activity android:name="com.liang.b.BActivity" >
-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:21:9-27:20
-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:21:19-58
<intent-filter>
-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:22:13-26:29
<action android:name="com.liang.b.act" />
-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:23:17-61
-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:23:25-58
<category android:name="android.intent.category.DEFAULT" />
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:17-76
-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:27-73
</intent-filter>
</activity>
</application>
</manifest>
最终编译出的app会将其他所有module的AndroidManifest文件合并,合并规则为:
- 如果功能module有Application,主module没有声明,则使用功能module的Application;如果主module有定义Application,其他module没有,则使用主module的;如果功能module有多个自定义Application,解决冲突后使用;如果主module有Application,功能module也有,则解决冲突后,最后编译的主module的Application会在AndroidManifest中。
如果子module中声明icon、theme等属性,会导致合并冲突,需要申明属性:
xmlns:tools="http://schemas.android.com/tools" tools:replace="android:icon,android:theme"
权限申明:
在子module中申明的权限,会集成到主module中,四大组件也是相同的规则。shareUid只有在主module中申明,才会打包到最终的AndroidManifest中。
独立调试
单工程方案
所谓的单工程方案就是把所有组件都放到一个工程下,先看一下整体的目录:
ps:
module_%20开头表示基础组件,
fun_%20前缀表示业务基础组件,
biz_前缀表示业务组件,
export_前缀表示业务组件暴露接口。
单工程利弊分析:
- 利:一个模块修改后只需要编译一下,依赖它的其他模块就能马上感知到变化。弊:没能做到完全的职责拆分,每个模块的开发者都有修改其他模块的权限。
首先在%20gradle.properties%20文件内声明一个变量:
//%20gradle.properties isModule%20=%20true
isModule%20为%20true%20时表示组件可以作为%20apk%20运行起来,false%20表示组件只能作为%20library。我们根据需要改变这个值后同步下gradle即可。
然后在某个%20module%20的%20build.gradle%20文件内用这个变量做三个地方的判断:
//%20build.gradle
//%20区分是应用还是库
if(isModule.toBoolean())%20{
apply%20plugin:%20'com.android.application'
}else%20{
apply%20plugin:%20'com.android.library'
}
android%20{
defaultConfig%20{
//%20如果是应用需要指定application
if(isModule.toBoolean())%20{
applicationId%20"com.xxx.xxx"
}
}
sourceSets%20{
main%20{
//%20应用和库的AndroidManifest文件区分
if(isModule.toBoolean())%20{
manifest.srcFile%20'src/main/debug/AndroidManifest.xml'
}else%20{
manifest.srcFile%20'src/main/AndroidManifest.xml'
}
}
}
}
由于library是不需要%20Application%20和启动Activity页,所以我们要区分这个文件,应用manifest指定的路径没有特定,随意找个路径创建即可。在应用AndroidManifest.xml里我们要设置启动页:
<manifest%20xmlns:android="http://schemas.android.com/apk/res/android" %20%20%20%20package="com.sun.biz_home"> %20%20%20%20<application %20%20%20%20%20%20%20%20android:allowBackup="true" %20%20%20%20%20%20%20%20android:label="@string/home_app_name" %20%20%20%20%20%20%20%20android:supportsRtl="true" %20%20%20%20%20%20%20%20android:theme="@style/home_AppTheme"> %20%20%20%20%20%20%20%20<activity%20android:name=".debug.HomeActivity"> %20%20%20%20%20%20%20%20%20%20%20%20<intent-filter> %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20<action%20android:name="android.intent.action.MAIN"%20/> %20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20<category%20android:name="android.intent.category.LAUNCHER"%20/> %20%20%20%20%20%20%20%20%20%20%20%20</intent-filter> %20%20%20%20%20%20%20%20</activity> %20%20%20%20</application> </manifest>
library%20的%20AndroidManifest.xml%20不需要这些:
<manifest%20xmlns:android="http://schemas.android.com/apk/res/android" %20%20%20%20package="com.sun.biz_home"> </manifest>
gradle%20依赖%20module%20的方式主要有两种:
- implementation:%20A%20implementation%20B,B%20implementation%20C,%20但%20A%20不能访问到%20C%20的东西。api:A%20api%20B,B%20api%20C,A能访问到C的东西。
一般来说我们只需要使用%20implementation%20即可,api%20是会造成项目编译时间变长,而且会引入该模块不需要的功能,代码之间耦合变得严重了。不过%20module_common%20是统一了基础组件版本的公共库,所有组件都应需要依赖它并拥有基础组件的能力,所以基本每个业务组件和业务基础组件都应该依赖公共库:
dependencies%20{
implementation%20project(':module_common')
}
而%20common%20组件依赖基础组件应该是用%20api,因为把基础组件的能力传递给上层业务组件:
dependencies%20{
api%20project(':module_base')
api%20project(':module_util')
}
多工程方案
多工程就是每个组件都是一个工程,例如创建一个工程后>
多工程的利弊就是和单工程相反的:
- 利:做到职责完全拆分,其他项目复用更加方便,直接一行依赖引入。弊:修改后需要上传到maven仓库,其他工程再次编译后才能感知到变化,多了上传和编译的时间。
多工程组件依赖需要用到maven仓库。把每个组件的aar上传到公司内网的maven仓库,然后像这样去依赖:
implementation%20'com.xxx.xxx:module_common:1.0.0'
我们把三方库统一放到%20config.gradle%20内管理:
ext%20{
%20%20%20%20dependencies%20=%20[
%20%20%20%20%20%20%20%20%20%20%20%20"glide":%20"com.github.bumptech.glide:glide:4.12.0",
%20%20%20%20%20%20%20%20%20%20%20%20"glide-compiler":%20"com.github.bumptech.glide:compiler:4.12.0",
%20%20%20%20%20%20%20%20%20%20%20%20"okhttp3":%20"com.squareup.okhttp3:okhttp:4.9.0",
%20%20%20%20%20%20%20%20%20%20%20%20"retrofit":%20"com.squareup.retrofit2:retrofit:2.9.0",
%20%20%20%20%20%20%20%20%20%20%20%20"retrofit-converter-gson"%20%20:%20"com.squareup.retrofit2:converter-gson:2.9.0",
%20%20%20%20%20%20%20%20%20%20%20%20"retrofit-adapter-rxjava2"%20:%20"com.squareup.retrofit2:adapter-rxjava2:2.9.0",
%20%20%20%20%20%20%20%20%20%20%20%20"rxjava2":%20"io.reactivex.rxjava2:rxjava:2.2.21",
%20%20%20%20%20%20%20%20%20%20%20%20"arouter":%20"com.alibaba:arouter-api:1.5.1",
%20%20%20%20%20%20%20%20%20%20%20%20"arouter-compiler":%20"com.alibaba:arouter-compiler:1.5.1",
%20%20%20%20%20%20%20%20%20%20%20%20//%20our%20lib
%20%20%20%20%20%20%20%20%20%20%20%20"module_util":%20"com.sun.module:module_util:1.0.0",
%20%20%20%20%20%20%20%20%20%20%20%20"module_common":%20"com.sun.module:module_common:1.0.0",
%20%20%20%20%20%20%20%20%20%20%20%20"module_base":%20"com.sun.module:module_base:1.0.0",
%20%20%20%20%20%20%20%20%20%20%20%20"fun_splash":%20"com.sun.fun:fun_splash:1.0.0",
%20%20%20%20%20%20%20%20%20%20%20%20"fun_share":%20"com.sun.fun:fun_share:1.0.0",
%20%20%20%20%20%20%20%20%20%20%20%20"export_biz_home":%20"com.sun.export:export_biz_home:1.0.0",
%20%20%20%20%20%20%20%20%20%20%20%20"export_biz_me":%20"com.sun.export:export_biz_me:1.0.0",
%20%20%20%20%20%20%20%20%20%20%20%20"export_biz_msg":%20"com.sun.export:export_biz_msg:1.0.0",
%20%20%20%20%20%20%20%20%20%20%20%20"biz_home":%20"com.sun.biz:biz_home:1.0.0",
%20%20%20%20%20%20%20%20%20%20%20%20"biz_me":%20"com.sun.biz:biz_me:1.0.0",
%20%20%20%20%20%20%20%20%20%20%20%20"biz_msg":%20"com.sun.biz:biz_msg:1.0.0"
%20%20%20%20]
}
这样方便版本统一管理,%20然后在根目录的%20build.gradle%20内导入:
apply%20from:%20'config.gradle'
最后在各自的模块引入依赖,比如在%20module_common%20中这么引入依赖即可。
dependencies%20{
api%20rootProject.ext.dependencies["arouter"]
%20%20kapt%20rootProject.ext.dependencies["arouter-compiler"]
%20%20api%20rootProject.ext.dependencies["glide"]
%20%20api%20rootProject.ext.dependencies["okhttp3"]
%20%20api%20rootProject.ext.dependencies["retrofit"]
%20%20api%20rootProject.ext.dependencies["retrofit-converter-gson"]
%20%20api%20rootProject.ext.dependencies["retrofit-adapter-rxjava2"]
%20%20api%20rootProject.ext.dependencies["rxjava2"]
%20%20api%20rootProject.ext.dependencies["module_util"]
%20%20api%20rootProject.ext.dependencies["module_base"]
}
个人觉得多工程适合"很大"的工程,每个业务组件可能都需要一个组开发,类似淘宝这样的app。但这只是针对业务组件来说的,业务基础组件和基础组件修改的频率不会很大,最好都是单工程上传至maven仓库来使用。本文的例子是为了方便所以把所有组件写到一起了,最好的方式就是把%20fun_%20和%20module_%20开头的组件都拆分成单工程独立开发,业务组件写到一个工程内。
页面跳转
做完组件之间的隔离后,暴露出来最明显的问题就是页面跳转和数据通信的问题。一般来说,页面跳转都是显示startActivity跳转,在组件化项目内就不适用了,隐式跳转可以用,但每个Activity都要写>
<activity%20android:name="com.liang.lib_video.videoplayer.VideoActivity"> %20%20%20%20<intent-filter> %20%20%20%20%20%20%20%20<action%20android:name="com.intent.openVideoActivity"></action> %20%20%20%20%20%20%20%20<category%20android:name="android.intent.category.DEFAULT"%20/> %20%20%20%20</intent-filter> </activity>
Intent%20intent%20=%20new%20Intent();
intent.setClass("包名","Activity路径");
intent.setComponent(new%20ComponentName("包名"));
startActivity(intent);
使用上述方式跳转会奔溃,提示在AndroidManifest文件中注册,这里需要注意的是,setClass/setComponent是APP的包名而不是所在module的包名。%20可以参考最终生成的AndroidManifest文件。使用隐式跳转也可以先用resolveActivity进行验证。%20如果不想要其他应用通过隐式打开,需要设置exported=false。
隐式跳转是整个系统都能接收到,会相对想好性能,所以最好的方式还是用路由框架。
实际上市面已经有比较成熟的路由框架专门就是为了组件化而生的,比如美团的WMRouter,阿里的ARouter等,本例使用%20ARouter%20框架,看下ARouter页面跳转的基本操作。
首先肯定是引入依赖,以%20module_common%20引入ARouter举例,build.gradle%20应该添加:
android%20{
defaultConfig%20{
javaCompileOptions%20{
%20%20%20%20%20%20%20annotationProcessorOptions%20{
%20%20%20%20%20%20%20%20%20%20arguments%20=%20[AROUTER_MODULE_NAME:%20project.getName()]
%20%20%20%20%20 %20}
%20%20%20%20}
}
compileOptions%20{
%20%20%20%20%20%20sourceCompatibility%20JavaVersion.VERSION_1_8
%20%20%20%20%20%20targetCompatibility%20JavaVersion.VERSION_1_8
%20%20}
}
dependencies%20{
api%20rootProject.ext.dependencies["arouter"]
%20%20kapt%20rootProject.ext.dependencies["arouter-compiler"]
}
kapt注解依赖没有办法传递,所以我们不可避免得需要在每个模块都声明这些配置,除了%20api%20rootProject.ext.dependencies["arouter"]%20这行。然后需要全局注册%20ARouter,我是在%20module_common%20统一注册的。
class%20AppCommon:%20BaseApp{
%20%20%20%20override%20fun%20onCreate(application:%20Application)%20{
%20%20%20%20%20%20%20%20MLog.d(TAG,%20"BaseApp%20AppCommon%20init")
%20%20%20%20%20%20%20%20initARouter(application)
%20%20%20%20}
%20%20%20%20private%20fun%20initARouter(application:%20Application)%20{
%20%20%20%20%20%20%20%20if(BuildConfig.DEBUG)%20{
%20%20%20%20%20%20%20%20%20%20%20%20ARouter.openLog()
%20%20%20%20%20%20%20%20%20%20%20%20ARouter.openDebug()
%20%20%20%20%20%20%20%20}
%20%20%20%20%20%20%20%20ARouter.init(application)
%20%20%20%20}
}
接着我们在%20module_common%20模块内声明一个路由表用作统一管理路径。
//%20RouterPath.kt
class%20RouterPath%20{
%20%20%20%20companion%20object%20{
%20%20%20%20%20%20%20%20const%20val%20APP_MAIN%20=%20"/app/MainActivity"
%20%20%20%20%20%20%20%20const%20val%20HOME_FRAGMENT%20=%20"/home/HomeFragment"
%20%20%20%20%20%20%20%20const%20val%20MSG_FRAGMENT%20=%20"/msg/MsgFragment"
%20%20%20%20%20%20%20%20const%20val%20ME_FRAGMENT%20=%20"/me/MeFragment"
%20%20%20%20%20%20%20%20const%20val%20MSG_PROVIDER%20=%20"/msg/MsgProviderImpl"
%20%20%20%20}
}
然后在MainActivity类文件上进行注解:
@Route(path%20=%20RouterPath.APP_MAIN)
class%20MainActivity%20:%20AppCompatActivity()%20{
}
任意模块只需要调用%20ARouter.getInstance().build(RouterPath.APP_MAIN).navigation()%20即可实现跳转。如果我们要加上数据传递也很方便:
ARouter.getInstance().build(RouterPath.APP_MAIN)
%20%20%20%20%20%20%20%20%20%20%20%20.withString("key",%20"value")
%20%20%20%20%20%20%20%20%20%20%20%20.withObject("key1",%20obj)
%20%20%20%20%20%20%20%20%20%20%20%20.navigation()
然后在MainActivity使用依赖注入接受数据:
class%20MainActivity%20:%20AppCompatActivity()%20{
%20%20%20%20@Autowired
%20%20%20%20String%20key%20=%20""
}
Arouter>
在%20export_biz_msg%20组件下声明%20IMsgProvider,此接口必须实现%20IProvider%20接口:
interface%20IMsgProvider:%20IProvider%20{
%20%20%20%20fun%20onCountFromHome(count:%20Int%20=%201)
}
然后在%20biz_msg%20组件里实现这个接口:
@Route(path%20=%20RouterPath.MSG_PROVIDER)
class%20MsgProviderImpl:%20IMsgProvider%20{
%20%20%20%20override%20fun%20onCountFromHome(count:%20Int)%20{
%20%20%20%20 %20%20//%20这里只是对数据进行分发,有监听计数的对象会收到
%20%20%20%20%20%20%20%20MsgCount.instance.addCount(count)
%20%20%20%20}
%20%20%20%20override%20fun%20init(context:%20Context?)%20{
%20%20%20%20%20%20%20%20//%20对象被初始化时调用
%20%20%20%20}
}
在%20biz_home%20首页组件中发送计数:
val%20provider%20=%20ARouter.getInstance().build(RouterPath.MSG_PROVIDER).navigation()%20as%20IMsgProvider provider.onCountFromHome(count)
可以看到其实和页面跳转的方式基本雷同,包括获取%20Fragment%20实例的方式也是这种。ARouter把所有通信的方式都用一种api实现,让使用者上手非常容易。
组件化的消息通信方式选择
广播
作为Android中四大组件之一,Broadcast的职责是用于Android系统通信,但是普通的广播是全局广播,会造成安全泄露以及效率问题,如果只是在应用内部通知,可以使用更为高效的LocalBroadCast,相对于全局广播,本地广播只会在APp内部传播,不会造成隐私泄露,同时无法接受其他应用发送的广播,相对于全局广播来说更加高效。
事件总线
由于系统级别的广播传递比较耗时,消息通信科使用通过记录对象、使用监听者模式实现的事件总线框架,比如EventBus、LivaData等。
通过将消息的公用部分 ,如自定义消息的bean放入到baseMOdule下的单独模块来实现组件间消息的传递。组件化的数据库存储和消息通信的实现方式大同小异,都是将公用的东西放入到baseModule,如果内容比较多或者对于职责界限划分要求高的话可在base下新建一个DataBaseModule
Application生命周期分发
当>
首先我们在 module_common 公共库内声明一个接口 BaseApp:
public interface BaseAppInit {
boolean onInitCreate(Application application);
boolean onInitTerminal(Application application);
}
然后每个组件都要创建一个 App 类实现此接口,比如在某个业务组件:
public class AudioInit implements BaseAppInit {
@Override
public boolean onInitCreate(Application application) {
return false;
}
@Override
public boolean onInitTerminal(Application application) {
return false;
}
}
剩下最后一步就是从 app 壳工程分发 application 的生命周期了,这里用到反射技术:
val moduleInitArr = arrayOf(
"com.liang.lib_audio.app.AudioInit",
"com.liang.lib_audio.app.VideoInit",
"com.liang.lib_audio.app.LoginInit",
)
class App: Application() {
override fun onCreate() {
super.onCreate()
initModuleApp(this)
}
private fun initModuleApp(application: Application) {
try {
for(appName in moduleInitArr) {
val clazz = Class.forName(appName)
val module = clazz.getConstructor().newInstance() as BaseApp
module.onCreate(application)
}
}catch (e: Exception) {
e.printStackTrace()
}
}
}
我们只需要知道的每个实现 BaseApp 接口的类的全限定名并写到moduleInitArr数组里,然后通过反射获取 Class 对象从而获取构造函数创建实体对象,最后调用 BaseApp 的 onCreate 方法将 application 传入,每个Application生命周期的方法都可以通过这种方式传递。由于反射会消耗一定的性能,这个操作可以放在子线程,然后线程间通信。 当然,在每个module定义相对应的初始化方法,然后主module 调用也可以实现初始化,此处使用反射是为了最大程度的解耦。
以上就是Android开发组件化架构设计原理到实战的详细内容,更多关于Android组件化架构设计的资料请关注易采站长站其它相关文章!










