概述
模块
模块指的是独立的业务模块,比如直播模块
,会员模块
等。
组件
组件指的是单一的功能组件,如登录组件
、上报组件
等,每个组件都可以以一个单独的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文件:
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
| <?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中配置:
1 2 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,且在其中可以存储一些全局对象:
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
| 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了:
1 2 3 4 5 6
| open class MyBaseApplication : Application() { override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) BaseApplication.init(this) } }
|
另外需要在项目从组件模式转换到集成模式后将组件的Application剔除出项目,可以在组件的Java文件夹下创建一个debug文件夹,用于存放不会在业务组件中引用的类,比如说Application类:
1 2 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
1 2 3 4
| interface ILoginService { fun isLogin(): Boolean fun getUserId(): String }
|
空实现:EmptyLoginService
1 2 3 4 5 6 7 8 9
| class EmptyLoginService : ILoginService { override fun isLogin(): Boolean { return false }
override fun getUserId(): String { return "" } }
|
Service工厂:ServiceFactory
1 2 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的具体实现:
1 2 3 4 5 6 7 8 9
| class LoginService : ILoginService { override fun isLogin(): Boolean { return true }
override fun getUserId(): String { return "1024" } }
|
component_share
分享组件中进行分享的逻辑判断:
1 2 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服务的注册:
1 2 3 4 5 6
| class MainApplication : MyBaseApplication() { override fun onCreate() { super.onCreate() ServiceFactory.register(LoginService()) } }
|
然后根据业务逻辑调用分享组件的功能:
1 2 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组件化的解析后,结合了自己的理解与思考得到的一些想法,在实际场景里可能会有问题,期待在以后的开发中有更多的实践与思考!