转载请声明地址:https://blog.youkuaiyun.com/skyunicorn/article/details/88897492
浅谈组件化框架(底部有demo链接)
什么是组件化?组件化在我的理解就是:一个app是由一个壳工程包含着多个模块工程拼接而成,所有的模块工程都依赖于同一个common库,而每个子模块之间又相对独立,可以不依赖于其他模块独立运行,方便程序员分工负责各自模块开发。
懒得画图了,直接从网上找了个图(找不到图的具体出处,很多组件化讲解都用的这个图),大概就是图里的这个意思。
一个大的app使用组件化,可以拆分为多个子app,独立开发调试可以减少编译的成本,大大提高了开发效率。废话不多说,下面我仔细讲解如何搭建一个组件化的框架。
一、使用Gradle统一配置依赖版本
1.在项目根目录新建config.gradle(公共配置文件,管理三方库、版本号)文件
2.在根目录build里配置config.gradle
apply from: ‘config.gradle’
3.编写config.gradle
这个文件的主要目的是控制app作为集成模式还是组件模式,并统一管理所有依赖库的版本号,避免项目使用的库出现不同版本。
ext {
isAlone = false;//false:作为集成模式存在,true:作为组件模式存在
// 各个组件版本号的统一管理
android = [
compileSdkVersion : 28,
// buildToolsVersion : "28.0.2",
minSdkVersion : 23,
targetSdkVersion : 23,
versionCode : 1,
versionName : '1.0.0',
sourceCompatibilityVersion: JavaVersion.VERSION_1_8,
targetCompatibilityVersion: JavaVersion.VERSION_1_8
]
//
libsVersion = [
APPCOMPAT_V7_VERSION = "28.0.0",
SUPPORT_V4_VERSION = "28.0.0",
CONSTRAINT_LAYOUT_VERSION = "1.1.3",
JUNIT_VERSION = "4.12",
RUNNER_VERSION = "1.0.2",
ESPRESSO_VERSION = "3.0.2",
AROUTER_API_VERSION = "1.2.4",
AROUTER_COMPILER_VERSION = "1.1.4",
MULTIDEX_VERSION = "1.0.1",
GLIDE_VERSION = "4.8.0",
GLIDE_COMPILER_VERSION = "4.8.0",
BUTTERKINFE_VERSION = "8.4.0",
BUTTERKINFE_COMPILER_VERSION = "8.4.0",
COMPRESS_HELPER_VERSION = "1.0.5",
]
// 依赖库管理
supportDependencies = [
appcompat_v7 : "com.android.support:appcompat-v7:${rootProject.APPCOMPAT_V7_VERSION}",
support_v4 : "com.android.support:support-v4:${rootProject.SUPPORT_V4_VERSION}",
constraint_layout : "com.android.support.constraint:constraint-layout:${rootProject.CONSTRAINT_LAYOUT_VERSION}",
junit : "junit:junit:${rootProject.JUNIT_VERSION}",
runner : "com.android.support.test:runner:${rootProject.RUNNER_VERSION}",
espresso : "com.android.support.test.espresso:espresso-core:${rootProject.ESPRESSO_VERSION}",
// 路由通讯(用于组件间通信的实现)
arouter_api : "com.alibaba:arouter-api:${rootProject.AROUTER_API_VERSION}",
arouter_compiler : "com.alibaba:arouter-compiler:${rootProject.AROUTER_COMPILER_VERSION}",
// 分包
multidex : "com.android.support:multidex:${rootProject.MULTIDEX_VERSION}",
// glide
glide : "com.github.bumptech.glide:glide:${rootProject.GLIDE_VERSION}",
glide_compiler : "com.github.bumptech.glide:compiler:${rootProject.GLIDE_COMPILER_VERSION}",
// butterknife
butterknife : "com.jakewharton:butterknife:${rootProject.BUTTERKINFE_VERSION}",
butterknife_compiler: "com.jakewharton:butterknife-compiler:${rootProject.BUTTERKINFE_COMPILER_VERSION}",
// 压缩工具
compress_helper : "com.github.nanchen2251:CompressHelper:${rootProject.COMPRESS_HELPER_VERSION}",
]
}
4.编写壳app下的build文件
apply plugin: 'com.android.application'
// AS3.2不再支持apt
//apply plugin: 'com.neenbedankt.android-apt'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
defaultConfig {
applicationId "com.skyunicorn.demo.frame_rrd_mvp"
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility rootProject.ext.android.sourceCompatibilityVersion
targetCompatibility rootProject.ext.android.targetCompatibilityVersion
}
sourceSets {
main {
//控制两种模式下的资源和代码配置情况
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/module/java']
res.srcDirs = ['src/main/res', 'src/main/module/res']
}
}
}
def librarys = rootProject.ext.supportDependencies
dependencies {
// 公用的库我放到了lib_common里,所以这里不在引用
//implementation fileTree(dir: 'libs', include: ['*.jar'])
//implementation librarys["appcompat_v7"]
//implementation librarys["constraint_layout"]
testImplementation library["junit"]
androidTestImplementation library["runner"]
androidTestImplementation library["espresso"]
implementation project(":lib_common")
// implementation project(":mod_news")
// butterknife
implementation library["butterknife"]
annotationProcessor library["butterknife_compiler"]
}
二、新建组件模块
1.新建组件
生成的组件和app结构一致
2.公共库build
公共库的目的是对其他mod和壳工程提供统一管理的工具、基类等文件,这里使用的依赖库主要使用api方式。api和implementation的主要区别是:api依赖的库可以传递,即依赖lib_common的mod可以直接使用这些库,而implementation依赖的库,其他mod即便依赖了lib_common也无法使用。
//控制组件模式和集成模式(公共库不需要)
//if (rootProject.ext.isAlone) {
// apply plugin: 'com.android.application'
//} else {
apply plugin: 'com.android.library'
//}
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//集成模式下Arouter的配置,用于组件间通信的实现
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility rootProject.ext.android.sourceCompatibilityVersion
targetCompatibility rootProject.ext.android.targetCompatibilityVersion
}
sourceSets {
main {
//控制两种模式下的资源和代码配置情况(公共库不需要)
// if (rootProject.ext.isAlone) {
// manifest.srcFile 'src/main/module/AndroidManifest.xml'
// java.srcDirs = ['src/main/java', 'src/main/module/java']
// res.srcDirs = ['src/main/res', 'src/main/module/res']
// } else {
manifest.srcFile 'src/main/AndroidManifest.xml'
// }
}
}
}
def librarys = rootProject.ext.supportDependencies
dependencies {
// 公用库用api,单独使用的库用implementation
api fileTree(dir: 'libs', include: ['*.jar'])
api librarys["appcompat_v7"]
api librarys["constraint_layout"]
// testImplementation librarys["junit"]
androidTestImplementation librarys["runner"]
androidTestImplementation librarys["espresso"]
api librarys["multidex"]
// Arouter路由
api librarys["arouter_api"]
annotationProcessor librarys["arouter_compiler"]
// Glide
api librarys["glide"]
annotationProcessor librarys["glide_compiler"]
// butterknife
api librarys["butterknife"]
annotationProcessor librarys["butterknife_compiler"]
}
3.子mod的build
//控制组件模式和集成模式(公共库不需要)
if (rootProject.ext.isAlone) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
defaultConfig {
if (rootProject.ext.isAlone) {
applicationId "com.skyunicorn.demo.mod_news"
}
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
if (!rootProject.ext.isAlone) {
//集成模式下Arouter的配置,用于组件间通信的实现
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility rootProject.ext.android.sourceCompatibilityVersion
targetCompatibility rootProject.ext.android.targetCompatibilityVersion
}
// 配置资源约束
//不同模块对于资源的命名可能会有冲突,为了防止不同模块的资源应为命名冲突而被错误的覆盖,就需要一种机制能够检查、提示、修改冲突的资源。
resourcePrefix "${project.name}_"
sourceSets {
main {
//控制两种模式下的资源和代码配置情况(公共库不需要)
if (rootProject.ext.isAlone) {
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/module/java']
res.srcDirs = ['src/main/res', 'src/main/module/res']
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
}
}
}
}
def librarys = rootProject.ext.supportDependencies
dependencies {
testImplementation librarys["junit"]
androidTestImplementation librarys["runner"]
androidTestImplementation librarys["espresso"]
implementation project(":lib_common")
if (!rootProject.ext.isAlone) {
//集成模式下需要编译器生成路由通信的代码
annotationProcessor librarys["arouter_compiler"]
}
}
注意子model和lib_common的这里是不一样的:
在每个mod的src/main下建立独立打包清单文件,配置后独立打包使用的清单文件就是src/main/AndroidManifest.xml,而作为组件使用的就是src/main/release/AndroidManifest.xml
if (rootProject.ext.isAlone) {
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/module/java']
res.srcDirs = ['src/main/res', 'src/main/module/res']
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
}
三、配置ARouter(路由跳转)
ARouter是用来实现跨模块通信功能
详细内容可以参考https://blog.youkuaiyun.com/huangxiaoguo1/article/details/78753555 这篇文章
1.首先在lib_common里配置arouter
2.在BaseApplication(在lib_common库)中注册ARouter
public class BaseApplication extends MultiDexApplication {
private static BaseApplication instance;
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
}
@Override
public void onCreate() {
super.onCreate();
instance = this;
initARouter(this);
}
public static Context getInstance(){
return instance;
}
private void initARouter(BaseApplication baseApplication) {
if(AppUtils.isApkInDebug(instance)){
// 打印日志
ARouter.openLog();
// 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!)
// 线上版本需要关闭,否则有安全风险
ARouter.openDebug();
}
ARouter.init(this);
}
}
3.配置RouteUtils(在lib_common库)
public class RouteUtils {
public static final String News_Activity_Main = "/mod_news/main";
// 获取news模块的fragment
public static final String News_Fragment_Main = "/mod_news/main";
}
4.配置ActivityUtils和FragmentUtils(在app壳工程中)
public class ActivityUtils {
public static Activity getActivity(String path) {
Activity activity = (Activity) ARouter.getInstance().build(path).navigation();
return activity;
}
public static void getActivityForResult(String path, Activity activity, int requestCode) {
ARouter.getInstance().build(path).navigation(activity, requestCode);
}
public static Activity getNewsMainActivity() {
return getActivity(RouteUtils.News_Activity_Main);
}
}
public class FragmentUtils {
public static Fragment getNewsMainFragment() {
Fragment fragment = (Fragment) ARouter.getInstance().build(RouteUtils.News_Fragment_Main).navigation();
return fragment;
}
}
四、MainActivity使用
public class MainActivity extends BaseActivity {
@BindView(R.id.btn_to_news)
Button btnToNews;
@BindView(R.id.btn_to_imgs)
Button btnToImgs;
@Override
protected int getContentViewLayoutId() {
return R.layout.activity_main;
}
@OnClick({R.id.btn_to_news, R.id.btn_to_imgs})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.btn_to_news:
ActivityUtils.getNewsMainActivity();
break;
case R.id.btn_to_imgs:
ActivityUtils.getImageMainActivity();
break;
}
}
}
五、集成编译与组件模式
在config.gradle里控制app的模式
isAlone = true,是作为组件模式,这个时候mod可以单独打包成apk,注意设置组件模式的时候,要注释掉app下gradle中的对mod_news的引用,不然会报错,或者在引用mod的时候加个判断(我demo里没做处理)
if (!rootProject.ext.isAlone) {
implementation project(":mod_news")
}
isAlone = false,只能作为集成模式,这个时候mod不可以独立打包
六、注意事项
1.在lib里使用butterknife时直接用R文件会报错(原因可以百度查看),需要使用R2文件
需要在lib库的build里最上方增加以下代码
apply plugin: 'com.jakewharton.butterknife'//不加这个,model库没法用butterknife
另在项目build的dependencies下添加
classpath 'com.jakewharton:butterknife-gradle-plugin:8.4.0'
2.在公共lib库里使用api方式导入butterKnife后,在app壳工程使用butterKnife不会报错,但是注解无效,这个原因我还不是很清楚,目前我是在每个lib下单独使用implementation去导入butterKnife的,如果有人能解决这个问题,请告诉我,不胜感激。
3.关于butterKnife版本问题,如果使用最新的版本,butterKnife把support包修改为androidx的,我尝试了下,把项目里所有的support都该文androidx会有一些列问题,所以我还是使用以前的suppor包,另尝试了下8.8.1版本的butterKnife,第一条注意事项的插件会报错,所以降级为8.4.0版本的butterKnife。
4.找不到路由的几种情况
1)检查下AndroidManifest文件有没有配置
<meta-data
android:name="com.skyunicorn.demo.mod_news.app.NewsApplication"
android:value="IModuleConfig" />
2)检查下CRouteUtils里的路由配置
每个mod的一级路径不能相同
/* mod_news */
public static final String News_Activity_Main = "/mod_news/main";
// 获取news模块的fragment
public static final String News_Fragment_Main = "/mod_news/main";
/* mod_image */
public static final String Imgs_Activity_Main = "/mod_image/main";
目前demo只是一个空框架,后期我会在GitHub中逐渐维护,将其他控件、功能模块demo集成进去,作为一个交流、记录的app,如果只需要空框架的话,请在csdn下载。
吐槽下:
还是喜欢以前不用积分就能下载的优快云,开源的东西就是要用来分享的,现在下载动不动就要那么多积分。
demo链接:
1.组件化demo链接:
https://download.youkuaiyun.com/download/skyunicorn/11340949
2.GitHub地址:该框架目前还在整理中,暂不公开地址