概述
模块
模块指的是独立的业务模块,比如直播模块,会员模块等。
组件
组件指的是单一的功能组件,如登录组件、上报组件等,每个组件都可以以一个单独的module开发,并且可以单独抽出来作为SDK对外发布使用。
模块和组件间最明显的区别就是模块相对与组件来说粒度更大,一个模块中可能包含多个组件。并且两种方式的本质思想是一样的,都是为了代码重用和业务解耦。在划分的时候,模块化是业务导向,组件化是功能导向。

一个可用的组件化架构图从上向下分别为APP壳,业务层、组件层和基础层:
- 基础层:基础层包含的是一些基础库以及对基础库的封装,比如常用的图片加载,网络请求,数据上报操作等等,其他模块或者组件甚至App应用都可以引用同一套基础库,因此这些基础库最好在另一个项目/git仓库中维护。在基础库上面可以再增加一个Common组件,它位于App应用所在的项目中,用来添加一些都需要引用到的依赖以及本App应用独有的共有方法,UI控件等。
- 组件层:基础层往上是组件层,组件层包含一些功能组件,比如分享,登录,下载,播放等等。
- 业务层:组件层往上是业务层,一个具体的业务模块会按需引用不同的组件,最终实现业务功能。
- APP壳:在APP模块中根据需求统筹各个业务组件,最终输出为一个完整的应用。
组件单独调试
Android Gradle提供了三种插件,在开发中可以通过配置不同的插件来配置不同的工程。
- App插件:com.android.application
- Library插件:com.android.libraay
- Test插件:com.android.test
可以在gradle.properties配置文件中声明一个boolean变量:isAlone,在build.gradle中通过判断这个变量来动态加载不同的插件,ApplicationId以及AndroidManifest文件:
| 12
 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
 
 | <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="com.hearing.share">
 
 <application
 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=".ShareActivity">
 <intent-filter>
 <action android:name="android.intent.action.MAIN" />
 
 <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter>
 </activity>
 </application>
 
 </manifest>
 
 
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="com.hearing.share">
 
 <application android:theme="@style/AppTheme">
 <activity android:name=".ShareActivity"/>
 </application>
 </manifest>
 
 | 
在组件的build.gradle中配置:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | android {defaultConfig {
 if (isAlone.toBoolean()) {
 
 applicationId "com.hearing.login"
 }
 
 }
 
 sourceSets {
 main {
 
 if (isAlone.toBoolean()) {
 manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
 } else {
 manifest.srcFile 'src/main/AndroidManifest.xml'
 }
 }
 }
 }
 
 | 
另外为了使组件化项目中的所有组件的compileSdkVersion、buildToolsVersion以及开源库版本等都能保持统一,也为了方便修改版本号,统一在Android工程根目录下的build.gradle中定义这些版本号,当然也可以在项目根目录添加一个单独的gradle文件定义这些常量,然后由根build.gradle引入。
组件混淆方案
组件化项目的代码混淆方案采用在集成模式下集中在app壳工程中混淆,各个业务组件不配置混淆文件。
组件Application
组件化开发中每一个组件可能都会自定义一个Application类,当所有组件要打包合并在一起的时候,由于程序只能有一个Application,组件中自己定义的Application无法使用。因此需要想办法在任何一个业务组件中都能获取到一个可用的全局Context,而且这个Context不管是在组件开发模式还是在集成开发模式都是生效的。
在基础库组件中封装了项目中用到的各种Base类(BaseActivity,BaseFragment等),这些基类中有一个BaseApplication类,它主要作用是使各个业务组件和app壳工程中都能访问到全局Context,且在其中可以存储一些全局对象:
| 12
 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
 
 | object BaseApplication {private val mGlobalObjects = mutableMapOf<Any, Any>()
 private var mApplication: Application? = null
 private var mContext: Context? = null
 
 fun init(application: Application) {
 mApplication = application
 mContext = application.baseContext
 }
 
 fun getContext(): Context? {
 return mContext ?: mApplication
 }
 
 fun getApplication(): Application? {
 return mApplication
 }
 
 @Synchronized
 fun putGlobalObject(key: Any, obj: Any) {
 mGlobalObjects[key] = obj
 }
 
 @Synchronized
 fun getGlobalObject(key: Any): Any? {
 return mGlobalObjects[key]
 }
 
 @Synchronized
 fun removeGlobalObject(key: Any): Any? {
 return mGlobalObjects.remove(key)
 }
 }
 
 | 
然后可以在本应用的Common组件中定义一个公用的Application基类,然后在各自组件中定义一个Application继承这个基类,这样就可以通过BaseApplication来获取全局的Context了:
| 12
 3
 4
 5
 6
 
 | open class MyBaseApplication : Application() {override fun attachBaseContext(base: Context?) {
 super.attachBaseContext(base)
 BaseApplication.init(this)
 }
 }
 
 | 
另外需要在项目从组件模式转换到集成模式后将组件的Application剔除出项目,可以在组件的Java文件夹下创建一个debug文件夹,用于存放不会在业务组件中引用的类,比如说Application类:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | sourceSets {main {
 if (isAlone.toBoolean()) {
 manifest.srcFile 'src/main/module/AndroidManifest.xml'
 } else {
 manifest.srcFile 'src/main/AndroidManifest.xml'
 
 java {
 exclude 'debug/**'
 }
 }
 }
 }
 
 | 
组件间数据传递与方法调用
接下来模拟一个组件间数据传递和方法调用的场景:分享组件分享时需要根据用户是否登录来判断分享的动作。
base_component
base_component组件用来提供组件间数据传递和方法调用的功能。
ILoginService
| 12
 3
 4
 
 | interface ILoginService {fun isLogin(): Boolean
 fun getUserId(): String
 }
 
 | 
空实现:EmptyLoginService
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | class EmptyLoginService : ILoginService {override fun isLogin(): Boolean {
 return false
 }
 
 override fun getUserId(): String {
 return ""
 }
 }
 
 | 
Service工厂:ServiceFactory
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | object ServiceFactory {private val mServices = CopyOnWriteArrayList<Any>()
 
 fun register(service: Any) {
 if (!mServices.contains(service)) {
 mServices.add(service)
 }
 }
 
 private fun <T> getService(cls: Class<T>): Any? {
 mServices.forEach {
 if (cls.isInstance(it)) {
 return it
 }
 }
 return null
 }
 
 fun getLoginService(): ILoginService {
 val service = getService(ILoginService::class.java)
 return service as? ILoginService ?: EmptyLoginService()
 }
 }
 
 | 
component_login
登录组件中提供ILoginService的具体实现:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | class LoginService : ILoginService {override fun isLogin(): Boolean {
 return true
 }
 
 override fun getUserId(): String {
 return "1024"
 }
 }
 
 | 
component_share
分享组件中进行分享的逻辑判断:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | object ShareHelper {fun share() {
 val loginService = ServiceFactory.getLoginService()
 if (loginService.isLogin()) {
 Toast.makeText(
 BaseApplication.getContext(),
 "Share from ${loginService.getUserId()}", Toast.LENGTH_SHORT
 ).show()
 } else {
 Toast.makeText(
 BaseApplication.getContext(),
 "Not login", Toast.LENGTH_SHORT
 ).show()
 }
 }
 }
 
 | 
app
在App主Module中进行ILoginService服务的注册:
| 12
 3
 4
 5
 6
 
 | class MainApplication : MyBaseApplication() {override fun onCreate() {
 super.onCreate()
 ServiceFactory.register(LoginService())
 }
 }
 
 | 
然后根据业务逻辑调用分享组件的功能:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | class MainActivity : AppCompatActivity() {
 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 setContentView(R.layout.activity_main)
 findViewById<TextView>(R.id.text_view).setOnClickListener {
 ShareHelper.share()
 }
 }
 }
 
 | 
组件间界面跳转
可以使用开源框架:ARouter或者CC等。
总结
以上组件化的内容是在参考了网络上一些关于Android组件化的解析后,结合了自己的理解与思考得到的一些想法,在实际场景里可能会有问题,期待在以后的开发中有更多的实践与思考!