一、概念
组件化:
把一个功能完整的App或模块拆分成多个子模块,每个子模块可以独立编译和运行,也可以任意组合成另一个新的App或模块,每个模块既不相互依赖但又可以相互交互。
组件:
指的是单一的功能组件,如视频组件(VideoSDK)、支付组件(PaySDK)、路由组件(Router)等,每个组件都能单独抽出来制作成SDK。
模块:
指的是独立的业务模块,如直播模块(LiveModule)、首页模块(HomeModule)、即时通信模块(IMModule)等。模块相对于组件来说粒度更大,模块可能包含多种不同的组件。
组件化与模块化:
组件化和模块化的本质思想是一样的,都是为了代码重用和业务解耦。区别在于模块化是业务导向,组件化是功能导向。
组件化与插件化:
组件化是在编译期分模块,插件化是在运行期。插件化用于动态更新模块或者动态修复BUG(热修复技术),相对来说黑科技更多一些。
组件化好处:
- 业务模块解耦。
- 提高工程编译速度。
- 有利于功能复用。
组件化注意事项:
- 组件化需要更多的时间来进行模块拆分。
- 小项目没必要组件化,那样只会带来更多的工作量。
- 组件化需要良好的架构设计,包括业务拆分、组件通信等,需要高水平的架构师统筹全局,经验不足盲目进行组件化反而会适得其反。
二、基础
AndroidManifest合并
每个module都有一份AndroidManifest配置文件,最终生成一个App的时候会将多个AndroidManifest合成一个。
生成路径:app\build\intermediates\manifests\full\debug\AndroidManifest.xml
Application的合并规则:
- 如果功能module有自定义Application,主module没有自定义Application,这时会引用功能module中的Application。
- 如果主module有自定义Application,功能module没有自定义Application,这时会引用主module中的Application。
- 如果功能module有两个自定义Application,那么需要解决冲突(在application标签下添加:tools:replace="android:name",在manifest根标签下添加:xmlns:tools="http://schemas.android.com/tools"),最终会载入后编译的module的Application。
- 如果主module中有自定义Application,其他功能module也有自定义Application,在主module中添加tools:replace解决冲突后,会发现最后编译的主module的Application在AndroidManifest中。
例子:
//settings.gradle(project)
include ':app', ':modulea'
//build.gradle(project)
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
maven {
name 'AliYun Jcenter Repository Proxy'
url 'https://maven.aliyun.com/repository/jcenter'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
maven {
name 'AliYun Jcenter Repository Proxy'
url 'https://maven.aliyun.com/repository/jcenter'
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
//build.gradle(module:modulea)
apply plugin: 'com.android.library'
android {
compileSdkVersion 26
buildToolsVersion "26.0.0"
defaultConfig {
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:26.0.0'
testCompile 'junit:junit:4.12'
}
//AndroidManifest(module:modulea)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tomorrow.modulea">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true">
<activity android:name=".ModuleAActivity"/>
</application>
</manifest>
//ModuleAActivity
public class ModuleAActivity extends AppCompatActivity {
}
//build.gradle(module:app)
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion "26.0.0"
defaultConfig {
applicationId "com.tomorrow.newfeature"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:26.0.0'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testCompile 'junit:junit:4.12'
compile project(':modulea')
}
//AndroidManifest(module:app)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tomorrow.newfeature">
<application
android:name=".MainApplication"
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=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
//MainApplication(module:app)
public class MainApplication extends Application {
private static final String TAG = "MainApplication";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "zwm, onCreate");
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
Log.d(TAG, "zwm, onActivityCreated, activity: " + activity.getLocalClassName());
}
@Override
public void onActivityStarted(Activity activity) {
Log.d(TAG, "zwm, onActivityStarted, activity: " + activity.getLocalClassName());
}
@Override
public void onActivityResumed(Activity activity) {
Log.d(TAG, "zwm, onActivityResumed, activity: " + activity.getLocalClassName());
}
@Override
public void onActivityPaused(Activity activity) {
Log.d(TAG, "zwm, onActivityPaused, activity: " + activity.getLocalClassName());
}
@Override
public void onActivityStopped(Activity activity) {
Log.d(TAG, "zwm, onActivityStopped, activity: " + activity.getLocalClassName());
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
Log.d(TAG, "zwm, onActivitySaveInstanceState, activity: " + activity.getLocalClassName());
}
@Override
public void onActivityDestroyed(Activity activity) {
Log.d(TAG, "zwm, onActivityDestroyed, activity: " + activity.getLocalClassName());
}
});
}
}
//MainActivity(module:app)
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "zwm, onCreate");
setContentView(R.layout.activity_main);
}
}
//app\build\intermediates\manifests\full\debug\AndroidManifest(module:app)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tomorrow.newfeature"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="15"
android:targetSdkVersion="26" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:name="com.tomorrow.newfeature.MainApplication"
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="com.tomorrow.newfeature.MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.tomorrow.modulea.ModuleAActivity" />
<meta-data
android:name="android.support.VERSION"
android:value="26.0.0" />
</application>
</manifest>
组件间通信机制
如果要使用EventBus或者类似RxBus的事件总线,可以使用以下方案,最大程度地完成事件总线的解耦。
其中XXXBus独立为一个module,Base module依赖XXXBus对事件通信的解耦,抽离事件到XXXBus事件总线模块。以后添加事件的Event的实体都需要在上面创建。
组件间跳转
使用路由机制,例如ARouter。
动态创建
动态创建Fragment解耦
使用跨模块获取Fragment非常适合在单Activity+多Fragment的App架构中使用。
方法1:使用反射机制跨模块获取Fragment。
方法2:使用ARouter跨模块获取Fragment。
动态初始化Application
某些功能模块需要做一些初始化操作。
方法1:通过主module的Application获取各个module的初始化文件,然后通过反射初始化的Java文件来调用初始化方法。
方法2:通过在主module的Application中继承Base Module的Application来实现,主module的Application将注册每个module的初始化文件,然后通过Base Module中的Application来对初始化文件做启动封装。
组件化存储
组件化权限
我们将normal级别的权限申请都放到Base module中,然后在各个module中分别申请dangerous的权限。这样分配的好处在于当添加或移除单一模块时,隐私权限申请也会跟随移除,能做到最大程度的权限解耦。当项目需要适配到Android 6.0以上的动态权限申请时,需要在Base module中添加自己封装的一套权限申请工具,其他组件层的module都可以使用这套工具。
路由拦截:
以ARouter为例,模块跳转前会遍历Intercept,然后通过判断跳转路径来找到需要拦截的对象。
组件化的静态变量
//strings.xml(module:modulea)
<resources>
<string name="app_name">ModuleA</string>
<string name="modulea_str">ModuleAStr</string>
</resources>
//modulea\build\generated\source\r\debug\com\tomorrow\R.java(module:modulea)
public final class R {
...
public static final class string {
...
public static int app_name=0x7f050021; //在功能module中是静态变量,不是常量
public static int modulea_str=0x7f050022; //在功能module中是静态变量,不是常量
...
}
...
}
//strings.xml(module:app)
<resources>
<string name="app_name">NewFeature</string>
<string name="app_str">AppStr</string>
</resources>
//app\build\generated\source\r\debug\com\tomorrow\modulea(module:app)
public final class R {
...
public static final class string {
...
public static final int app_name = 0x7f060021; //在Application module中是静态常量
public static final int modulea_str = 0x7f060023; //在Application module中是静态常量
...
}
...
}
//app\build\generated\source\r\debug\com\tomorrow\newfeature(module:app)
public final class R {
...
public static final class string {
...
public static final int app_name=0x7f060021; //在Application module中是静态常量
public static final int app_str=0x7f060022; //在Application module中是静态常量
public static final int modulea_str=0x7f060023; //在Application module中是静态常量
...
}
...
}
各个功能module会生成aar文件,并且被引用到Application module中,最终合并为apk文件。当各个次级module在Application module中被解压后,在编译时资源R.java会被重新解压到build/generated/source/r/debug/包名/R.java中,id属性会被添加声明为final修饰符。
当每个组件中的aar文件汇总到App module中时,也就是编译的初期解析资源阶段,其每个module的R.java释放的同时,会检测到全部的R.java文件,然后通过合并,最终会合并为唯一的一份R.java资源文件。
注意:在功能module中,静态变量并没有被赋予final属性。这样会导致在功能module中,凡是规定必须使用常量的地方都无法直接使用R.java中的变量,包括switch-case和注解。
例如,在ButterKnife框架中,注解中只能使用常量:
//编码
@BindView(R2.id.edittext)
public EditText username;
//当编译成class文件后,R值将被替换为常量
@BindView(2131492969)
public EditText username;
ButterKnife框架使用替换的方法,将R.java文件复制一份,命名为R2.java,然后给R2.java的变量加上final修饰符,在相关的地方直接引用R2资源。
组件化的资源汇合
组件化中,Base module和功能module的根本是Library module,编译时会依次通过依赖规则进行编译,最底层的Base module会被先编译成aar文件,然后上一层编译时因为通过compile依赖,也会将依赖的aar文件解压到模块的build中。全部的功能module都依赖了Base module,但是Application module最终还是得将功能module的aar文件汇总后,才能开始编译操作。最终,只有一个Base module的资源在目录中,并且其他各种依赖包也只有唯一一份。
查看依赖树:
gradlew module_name:dependencies
gradlew module_name:dependencies > dependencies.txt
有些传递依赖标注了*号,表示这个依赖被忽略了,这是因为其他顶级依赖中也依赖了这个传递的依赖,Gradle会自动分析并下载最合适的依赖。
资源冲突:
1.AndroidManifest冲突问题
AndroidManifest中引用了Application的app:name属性,当出现冲突时,需要使用tools:replace="android:name"来声明Application是可被替换的。某些AndroidManifest.xml中的属性被替代的问题,可以使用tools:replace来解决冲突。
2.包冲突
当包冲突出现时,可以先检查依赖报告,使用gradle dependencies的命令查看依赖目录树。依赖标注了*号,表示这个依赖被忽略了。这是因为其他顶级依赖也依赖于这个传递的依赖。如果想使用优先级比较低的依赖,可以使用exclude排除依赖的方式:
compile('com.facebook.fresco:fresco:0.10.0') {
exclude group:'com.android.support', module:'support-v4'
}
3.资源名冲突
在多module开发中,因为无法保证多个module中全部资源的命名是不同的。假如出现相同的情况,就有可能造成资源引用错误的问题。
因为无法保证不同module中的资源名不同,那么Gradle会简单粗暴地使用替换策略:后编译的模块会覆盖之前编译的模块的资源字段中的内容。Application module最后汇总在一起时,资源会先被策略逻辑调整,后加载的资源地址会把前面加载的资源地址替换掉,引起资源引用的错误。
解决的办法有两种:
- 当资源出现冲突时使用重命名的方式解决。这就要求我们在一开始命名的时候,不同模块间的资源命名都不一样。这是代码编写规范的约束。
- Gradle的命名提示机制。使用resourcePrefix字段:android { resourcePrefix "组件名_" }
所有的资源名必须以指定的字符串作为前缀,否则会报错,而且resourcePrefix这个值只能限定XML中的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名。
组件化混淆
这里将固定的第三方库混淆放到Base module proguard-rule.pro中,每个module独有的引用库混淆放到各自的proguard-rule.pro中。最后在App module的proguard-rule.pro文件中放入Android基础属性混淆声明,例如四大组件和全局混淆(引用Base module基类)等一些配置。这样将可以最大限度地完成混淆解耦工作。
版本参数优化
//common_config.gradle
project.ext {
//设置App配置
setAppDefaultConfig = {
extension ->
//引用Application插件库
extension.apply plugin: 'com.android.application'
extension.description "app"
setAndroidConfig extension.android
setDependencies extension.dependencies
}
//设置Lib配置
setLibDefaultConfig = {
extension ->
//引用lib插件库
extension.apply plugin: 'com.android.library'
extension.description "lib"
setAndroidConfig extension.android
setDependencies extension.dependencies
}
//设置Android配置
setAndroidConfig = {
extension ->
extension.compileSdkVersion 26
extension.buildToolsVersion "26.0.0"
extension.defaultConfig {
minSdkVersion 15
targetSdkVersion 26
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// javaCompileOptions {
// annotationProcessorOptions { //路由每个模块的名称
// arguments = [moduleName: extension.project.getName()]
// }
// }
}
// extension.dataBinding { //开启dataBinding,可以选用
// enabled = true
// }
}
//设置依赖
setDependencies = {
extension ->
extension.compile fileTree(dir: 'libs', include: ['*.jar'])
//每个module都需要引用路由的apt插件库才能生成相应代码,这里无须重复编写每个module
// extension.annotationProcessor 'com.alibaba:arouter-compiler:1.1.1'
}
}
//build.gradle(module:app)
apply from: "${rootProject.rootDir}/common_config.gradle"
project.ext.setAppDefaultConfig project
android {
defaultConfig {
applicationId "com.tomorrow.newfeature"
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile 'com.android.support:appcompat-v7:26.0.0'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testCompile 'junit:junit:4.12'
compile project(':modulea')
}
//build.gradle(module:modulea)
apply from: "${rootProject.rootDir}/common_config.gradle"
project.ext.setLibDefaultConfig project
android {
defaultConfig {
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile 'com.android.support:appcompat-v7:26.0.0'
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testCompile 'junit:junit:4.12'
}
调试优化
业务模块调试,将单一模块做成App启动,然后用于调试测试,这样保证了单独模块可以分离调试。
步骤如下:
1.业务模块是Library module,独立调试需要将模块做成Application module才能引入App构建流程。
即:com.android.library -> com.android.application
2.每个Application都要配置ApplicationId。
setAppDefaultConfig = {
extension->
//引用Application插件库
extension.apply plugin:'com.andorid.application'
extension.description "app"
setAndroidConfig extension.android
setDependencies extension.dependencies
//为每个Module配置不同的Application ID
extension.android.defaultConfig{
applicationId applicationId+"."+extension.getName()
}
}
这里的extension.getName()相当于project.getName(),在默认的ApplicationId后添加module名字,用于区分不同module产生的单独的App。
3.在src中建立debug文件夹。
debug文件夹同main文件夹中的目录类似,用于放置调试需要的AndroidManifest.xml文件、Java文件、res资源文件。
AndroidManifest文件中需要设置默认启动的Activity文件:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
4.在common_config中需要声明单独模块调试变量。
例如,设置一个isNewsDebug变量:
project.ext {
isNewsDebug = false
}
在模块的build.gradle中将变量isNewsDebug作为开关:
if(project.ext.isNewsDebug){
project.ext.setLibDefaultConfig project //当前为lib
} else{
project.ext.setAppDefaultConfig project //当前为app
}
在sourceSets资源配置中,配置AndroidManifest的地址和res资源的地址:
sourceSets{
main{
if(project.ext.isNewsDebug){
manifest.srcFile 'src/debug/AndoridManifest.xml'
res.srcDirs =['src/debug/res','src/main/res']
} else {
manifest.srcFile 'src/main/AndoridManifest.xml'
resources {
//排除debug文件夹下的所有文件
exclude 'src/debug/*'
}
}
}
}
编译构建时Gradle会选取debug内的资源。
5.原App module需要移除已经单独调试的模块依赖。
dependencies {
if(!project.ext.isNewsDebug){
compile project(':news")
}
}
调试的模块已经从Library module变更为Application module,该Application module不能被其他Application module依赖,只能依赖Library module,这是Android Gradle设计的规则。通过isNewsDebug变量开关来判断引用。
资源引用配置
Gradle有多种资源引用的方式:
1.使用sourceSets的方式来指定文件的路径。sourceSets还可以指定更多的资源属性的路径。
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml' //AndoridManifest
java.srcDirs = ['src'] //Java文件路径
resources.srcDirs = ['src'] //全部资源文件路径
aidl.srcDirs = ['src'] //aidl文件路径
renderscript.srcDirs = ['src'] //renderscript文件路径
res.srcDirs = ['res'] //res资源文件路径
assets.srcDirs = ['assets'] //asset资源文件路径
}
}
sourceSets可以指定不同资源引用的文件夹。[]里面是列表,可以通过","来分隔资源地址。
2.可以动态添加res资源,在buildType和productFlavor中定义resValue变量。
resValue "string", "app_name", "serverGank"
resValue只能动态添加资源,无法替换资源。如果资源名称重复,Gradle会提示重复定义资源。
3.可以指定特定尺寸的资源,也可以在buildType和productFlavor中定义。
resConfigs "hdpi", "xhdpi", "xxhdpi"
4.可以通过build.gradle定义manifestPlaceholders的变量,再通过AndroidManifest的meta-data的方式间接让Java代码读取AndroidManifest的变量。
//build.gradle
productFlavors {
client {
manifestPlaceholders=[
channel:"10086",
verNum:"1",
app_name:"Gank"
]
}
}
//AndroidManifest.xml
<application
android:name=".MainApplication"
android:allowBackup="true"
android:label="${app_name}" //app名引用
<meta-data android:name="verNum" android:value="${verNum}"/> //版本号声明
<meta-data android:name="channel" android:value="${channel}"> //渠道名声明
...
5.通过build.gradle编译生成的BuildConfig文件,可以直接让代码读取到BuildConfig中的值。
//build.gradle
productFlavors {
client {
buildConfigField "String", "advice_url", "\"https://www.baidu.com\""
}
}
//编译时生成代码
public final class BuildConfig {
...
public static final String advice_url = "https://www.baidu.com";
...
}
Gradle 4.1依赖特性
当升级到Android Studio 3.0之后,Gradle版本也随之升级到4.1版本。新建一个工程时,build.gradle依赖方式默认变为implementation,而不是compile。
implementation的作用依然是提供依赖,但有别于compile,implementation并不能跨模块传递依赖。这样做能很好地隐蔽内部接口的实现,保持封装的隐蔽性。
Gradle 4.1依然提供同compile一样功能的引用接口,就是API引用,其使用方式和compile相同。
Gradle 4.1对依赖函数接口做了很多的变更。Gradle 4.1以下版本使用provided依赖,4.1版本改为compileOnly函数,使用方式也是一样的。
组件化中,因为App能随意使用Base module的接口方法,在Base module中建议使用API依赖第三方的库,而在Business业务模块中依赖时也使用API的方式。虽然这样牺牲了编译速度,但是省却一部分的调用封装,也能保证兼容Gradle 4.1以下的组件化项目。
如果要完全做到App和Base module之间的解耦,需要定制一个独立给App提供底层支持的接口module,其功能是封装基础的base接口给App module使用。
组件化SDK
制作jar文件
task clearJar(type: Delete) {
delete 'libs/testjar.jar' //SDK是jar包的名字,任意命名
}
task makeJar(type: org.gradle.api.tasks.bundling.Jar) {
//指定生成的jar名
baseName 'testjar'
//从哪里打包class文件
from('build/intermediates/classes/debug/')
//打包到jar后的目录结构
into('build/outputs/')
//去掉不需要打包的目录和文件
exclude('test/', 'BuildConfig.class', 'R.class')
//去掉R开头的文件
exclude{
it.name.startsWith('R');
}
}
makeJar.dependsOn(clearJar, build)
运行gradlew makeJar命令就可以生成jar文件:
testjar\build\outputs\aar\testjar-debug.aar
testjar\build\outputs\aar\testjar-release.aar
testjar\build\libs\testjar.jar ==> 生成的jar文件
制作aar文件
在Library module中只要使用assemble命令就可以生成工程的aar文件。
导入jar文件
导入libs中的jar文件:
dependencies {
compile fileTree(include: ['*.jar', dir: 'libs']) //导入文件夹中所有jar文件
compile files('libs/xxx.jar') //加载单一库
}
导入aar文件
导入libs中的aar文件:
repositories {
flatDir {
dirs 'libs' //指定module本地引用库的路径
}
}
dependencies {
compile(name: 'xxx', ext: 'aar')
}
三、思想
组件化需要解决的几大问题:
- 代码解耦。
- 组件单独运行。
- 数据传递。
- UI跳转。
- 组件的生命周期。
- 集成调试。
- 代码隔离。
四、架构
模块化模型
应用层:生成App和加载初始化操作。
模块层:每个模块相当于一个业务,通过module来分隔每个业务的逻辑,一个模块由多个不同的页面逻辑组成。
基础层:基础组件的整合,提供基础组件能力给业务层使用。
组件层:将图片加载、网络HTTP、Socket等基础功能划分为一层。
基础库层:更加基础的库类依赖,此层非必需,例如RxJava、EventBus等一些代码结构优化的库,还有自己编写的封装类。
基础库层可以转移到基础层和组件层中,这样可以减少层级。
基础层可以只是一个空壳,起到隔离模块层和组件层的入口的作用,可以作为中转,封装一些必须要使用的组件功能,隐蔽一些实现细节。
模块层的业务逻辑需要考虑业务之间的信息交互和转发的实现。
这个结构适合中型App的搭建,当业务需要重新细分重构时,可以考虑使用这种架构方式。这种架构要求组件独立复用,模块能够不依赖于其他模块实现。
从零开始搭建Android组件化框架
相关链接
《Android组件化架构》