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