🔥 Hi,我是小余。本文已收录到 GitHub · Androider-Planet 中。这里有 Android 进阶成长知识体系,关注公众号 [小余的自习室] ,在成功的路上不迷路!
前言
前面几篇文章我们讲解了一个云音乐app的基础库搭建,今天我们就来对这个app进行组件化代码重构
组件化基础库封装系列文章如下:
Android组件化开发(七)--从零开始教你分析项目需求并实现
项目地址:https://github.com/ByteYuhb/anna_music_app
项目演示:
1.组件化重构效果
这里先看下我们重构前后的框架图比较:
重构前:
重构后
ft_xxx
表示业务层模块lib_xxx
表示基础库模块
重构后的架构图如下:
重构前的代码业务封装在宿主app中,业务耦合严重,如果修改一个业务模块,需要对整个app进行完整测试,测试工作量巨大
而重构后,我们只需要对单一app进行独立调试即可。
重构后的框架结构:所有的业务组件之间通讯都通过ft_base_service
进行通讯
2.组件化重构准则
- 1.单一业务可以单独调试,也可以作为lib提供给宿主app使用
- 2.同一级别的模块不允许直接调用,比如我们的ft_home组件不允许直接调用ft_login组件,不然组件化的意义就不存在了
- 3.组件间通讯不能直接使用显示的class文件跳转,可以考虑很用ARouter框架进行解耦
- 4.每个组件可打包为aar或者jar上传到maven私服,宿主使用的时候,直接引用私服中aar包即可
能做到以上几点,你的app就可以称为一个组件化框架的app了。
3.组件化重构思路
- 1.拆:拆代码,拆资源,拆构建
由于所有业务和资源都耦合在宿主app中,所以需要将代码和资源拆开到对应模块中
当然我们的构建build.gradle也需要拆分到不同模块中
- 2.接:对外提供接口
组件化之间不能直接通讯,需要使用暴露接口的方式对外通讯
- 3.测:反复测试
重构后代码,需要反复测试,防止出现意想不到的bug
4.组件化重构过程
这里我以登录业务ft_login
为例子:
1.步骤1:首先新建一个业务模块ft_login
,然后在宿主app中将登录功能相关联的代码和资源抽离到ft_login
中
2.步骤2:将和登录构建相关的依赖分配到ft_login
构建中。
3.步骤3:单独调试功能实现
- 3.1:在
gradle.properties
中创建一个全局变量:isRunAlone=true
- 3.2:在
build.gradle
中:
if(isRunAlone.toBoolean()){
apply plugin:'com.android.application'
}else{
apply plugin:'com.android.library'
}
android {
compileSdkVersion 33
buildToolsVersion "33.0.0"
defaultConfig {
if(isRunAlone.toBoolean()){
applicationId 'com.anna.ft_login'
}
...
}
sourceSets {
main {
java {
srcDirs = ['src/main/java']
}
resources {
srcDirs = ['src/main/res']
}
aidl {
srcDirs = ['src/main/aidl']
}
manifest {
if(isRunAlone.toBoolean()){
srcFile 'src/main/manifest/AndroidManifest.xml'
}else {
srcFile 'src/main/AndroidManifest.xml'
}
}
}
}
}
def dependList = [rootProject.depsLibs.okhttp,
rootProject.depsLibs.gson,
rootProject.depsLibs.appcompact,
rootProject.depsLibs.design,
rootProject.depsLibs.eventbus,
rootProject.depsLibs.arouterapi,
':lib_network',':lib_common_ui',':ft_base_service']
dependencies {
if(!isRunAlone.toBoolean()){
dependList.each { String depend ->
depend.startsWithAny(':lib',':ft')? compileOnly(project(depend)):compileOnly(depend){
switch (depend){
case rootProject.depsLibs.arouterapi:
exclude group: 'com.android.support'
break;
}
}
}
}else {
dependList.each { String depend ->
depend.startsWithAny(':lib',':ft')? implementation(project(depend)):implementation(depend) {
switch (depend) {
case rootProject.depsLibs.arouterapi:
exclude group: 'com.android.support'
break;
}
}
}
}
//arouter注解处理器
annotationProcessor rootProject.depsLibs.aroutercompiler
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
单独调试状态下注意四点:
- 1.引用application插件
- 2.引入applicationId
- 3.引入不同给的sourceSets构建路径
- 4.引入的库单独调试状态下需要使用
implementation
导入,不能使用compileOnly
实现上面四点,只要打开isRunAlone就可作为一个单独app运行了。
4.步骤4:组件间通讯
这里,我们引入一个ft_base_service
模块,这个模块用来实现组件间通讯用,需要调用别的业务模块都需要使用这个模块才能通讯、
业务模块与ft_base_service
之间通讯使用的是路由ARouter
:
关于ARouter
的使用可以参考这篇文章:
Android开源系列-组件化框架Arouter-(一)使用方式详解
- 1.创建
ft_base_service
,在这个模块中:创建一个LoginService
接口继承IProvider
引入ARouter依赖
:
android {
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName(), AROUTER_GENERATE_DOC: "enable"]
}
}
}
//arouter核心api
implementation rootProject.depsLibs.arouterapi
//arouter注解处理器
annotationProcessor rootProject.depsLibs.aroutercompiler
创建LoginService:
public interface LoginService extends IProvider {
boolean hasLogin();
void login(Context context);
}
- 2.在
ft_login
业务模块中实现LoginService接口
注意这里因为使用了ARouter注解,所以也需要引入ARouter
依赖
@Route(path = "/login/login_service")
public class LoginServiceImpl implements LoginService {
Context context;
@Override
public boolean hasLogin() {
return UserManager.getInstance().hasLogined();
}
@Override
public void login(Context context) {
LoginActivity.start(context);
}
@Override
public void init(Context context) {
Log.d("TAG","LoginServiceImpl is init");
}
}
- 3.在
ft_base_service
模块中对LoginService
接口进行依赖注入
public class LoginImpl {
@Autowired(name = "/login/login_service")
public LoginService mLoginService;
private static LoginImpl mLoginImpl = null;
public static LoginImpl getInstance() {
if (mLoginImpl == null) {
synchronized (LoginImpl.class) {
if (mLoginImpl == null) {
mLoginImpl = new LoginImpl();
}
return mLoginImpl;
}
}
return mLoginImpl;
}
private LoginImpl(){
ARouter.getInstance().inject(this);
}
public boolean hasLogin(){
return mLoginService.hasLogin();
}
public void login(Context context){
mLoginService.login(context);
}
}
笔者使用了一个单例类LoginImpl,在构造器中对LoginService
依赖注入
ARouter.getInstance().inject(this);
然后宿主app或者其他模块引用登录业务功能时,需要依赖ft_base_service
模块,并使用LoginImpl
的接口即可。
这里要说明下,平时我们使用的 四大组件跳转也可以使用这个方式来处理,在服务接口中定义跳转接口即可。当然也可以使用Arouter的Activity跳转方式或者Fragment实例获取方式
- 5.代码打包aar上传到
maven私服
:
关于这块maven私服更多内容可以参考这篇文章:
这里我们封装了一个通用组件发布库:
apply plugin: 'maven'
uploadArchives {
repositories {
mavenDeployer {
// 是否快照版本
def isSnapShot = Boolean.valueOf(MAVEN_IS_SNAPSHOT)
def versionName = MAVEN_VERSION
if (isSnapShot) {
versionName += "-SNAPSHOT"
}
// 组件信息
pom.groupId = MAVEN_GROUP_ID
pom.artifactId = MAVEN_ARTIFACTID
pom.version = versionName
// 快照仓库路径
snapshotRepository(url: uri(MAVEN_SNAPSHOT_URL)) {
authentication(userName: MAVEN_USERNAME, password: MAVEN_USERNAME)
}
// 发布仓库路径
repository(url: uri(MAVEN_RELEASE_URL)) {
authentication(userName: MAVEN_USERNAME, password: MAVEN_USERNAME)
}
println("###################################"
+ "\nuploadArchives = " + pom.groupId + ":" + pom.artifactId + ":" + pom.version + "." + pom.packaging
+ "\nrepository =" + (isSnapShot ? MAVEN_SNAPSHOT_URL : MAVEN_RELEASE_URL)
+ "\n###################################"
)
}
}
}
然后在对应的组件下面引用:
apply from:file('../maven.gradle')
发布的时候直接在Gradle
面板中点击uploadArchives
任务即可
**经过上面几个步骤就基本完成了login组件的封装并发布,且对外提供了login组件接口
其他组件也是按照上面的逻辑进行重构**
更多详细信息可以自己拿到项目源代码查看。
5.组件化重构总结
**组件化不仅是一种架构,更是一种思想,架构是可以变得,但是核心思想却是统一的,在拆分代码的时候,要注意模块的颗粒度,不是颗粒度越小就越好,模块分离的好,后期对组件改造会有很大帮助,
关于组件化的文章就讲到这里,组件化重构的项目已经上传到Github。
后面会出一期插件化
的项目改造。敬请期待。**