单工程模式,整个项目只有一个工程,它包含:App module 加上各个业务组件module,就是所有的代码,这就是单工程模式。 如何做到组件单独调试呢?
我们知道,在 AndroidStudio 开发 Android 项目时,使用的是 Gradle 来构建,Android Gradle 中提供了三种插件,在开发中可以通过配置不同的插件来配置不同的module类型。
区别比较简单, App 插件来配置一个 Android App 工程,项目构建后输出一个 APK 安装包,Library 插件来配置一个 Android Library 工程,构建后输出 ARR 包。
显然我们的 App module配置的就是Application 插件,业务组件module 配置的是 Library 插件。想要实现 业务组件的独立调试,这就需要把配置改为 Application 插件;而独立开发调试完成后,又需要变回Library 插件进行集成调试。
如何让组件在这两种调试模式之间自动转换呢? 手动修改组件的 gralde 文件,切换 Application 和 library ?如果项目只有两三个组件那么是可行的,但在大型项目中可能会有十几个业务组件,一个个手动修改显得费力笨拙。
我们知道用AndroidStudio创建一个Android项目后,会在根目录中生成一个gradle.properties文件。在这个文件定义的常量,可以被任何一个build.gradle读取。 所以我们可以在gradle.properties中定义一个常量值 isModule,true为即独立调试;false为集成调试。然后在业务组件的build.gradle中读取 isModule,设置成对应的插件即可。代码如下:
//gradle.properties
#组件独立调试开关, 每次更改值后要同步工程
isModule = false
//build.gradle
//注意gradle.properties中的数据类型都是String类型,使用其他数据类型需要自行转换
if (isModule.toBoolean()){
apply plugin: ‘com.android.application’
}else {
apply plugin: ‘com.android.library’
}
我们知道一个 App 是需要一个 ApplicationId的 ,而组件在独立调试时也是一个App,所以也需要一个 ApplicationId,集成调试时组件是不需要ApplicationId的;另外一个 APP 也只有一个启动页, 而组件在独立调试时也需要一个启动页,在集成调试时就不需要了。所以ApplicationId、AndroidManifest也是需要 isModule 来进行配置的。
//build.gradle (module_cart)
android {
…
defaultConfig {
…
if (isModule.toBoolean()) {
// 独立调试时添加 applicationId ,集成调试时移除
applicationId “com.hfy.componentlearning.cart”
}
…
}
sourceSets {
main {
// 独立调试与集成调试时使用不同的 AndroidManifest.xml 文件
if (isModule.toBoolean()) {
manifest.srcFile ‘src/main/moduleManifest/AndroidManifest.xml’
} else {
manifest.srcFile ‘src/main/AndroidManifest.xml’
}
}
}
…
}
可见也是使用isModule分别设置applicationId、AndroidManifest。其中独立调试的AndroidManifest是新建于目录moduleManifest,使用 manifest.srcFile 即可指定两种调试模式的AndroidManifest文件路径。 moduleManifest中新建的manifest文件 指定了Application、启动activity:
//moduleManifest/AndroidManifest.xml
原本自动生成的manifest,未指定Application、启动activity:
独立调试、集成调试 ,分别使用“assembleDebug”构建结果如下:
多工程方案,业务组件以library module形式存在于独立的工程。独立工程 自然就可以独立调试了,不再需要进行上面那些配置了。
例如,购物车组件 就是 新建的工程Cart 的 module_cart模块,业务代码就写在module_cart中即可。app模块是依赖module_cart。app模块只是一个组件的入口,或者是一些demo测试代码。 那么当所有业务组件都拆分成独立组件时,原本的工程就变成一个只有app模块的壳工程了,壳工程就是用来集成所有业务组件的。
那么如何进行集成调试呢?使用maven引用组件:1、发布组件的arr包 到公司的maven仓库,2、然后在壳工程中就使用implemention依赖就可以了,和使用第三方库一毛一样。另外arr包 分为 快照版本(SNAPSHOT) 和 正(Realease)式版本,快照版本是开发阶段调试使用,正式版本是正式发版使用。具体如下:
首先,在module_cart模块中新建maven_push.gradle文件,和build.gradle同级目录
apply plugin: ‘maven’
configurations {
deployerJars
}
repositories {
mavenCentral()
}
//上传到Maven仓库的task
uploadArchives {
repositories {
mavenDeployer {
pom.version = ‘1.0.0’ // 版本号
pom.artifactId = ‘cart’ // 项目名称(通常为类库模块名称,也可以任意)
pom.groupId = ‘com.hfy.cart’ // 唯一标识(通常为模块包名,也可以任意)
//指定快照版本 maven仓库url, todo 请改为自己的maven服务器地址、账号密码
snapshotRepository(url: ‘http://xxx/maven-snapshots/’) {
authentication(userName: ‘***’, password: ‘***’)
}
//指定正式版本 maven仓库url, todo 请改为自己的maven服务器地址、账号密码
repository(url: ‘http://xxx/maven-releases/’) {
authentication(userName: ‘***’, password: ‘***’)
}
}
}
}
// type显示指定任务类型或任务, 这里指定要执行Javadoc这个task,这个task在gradle中已经定义
task androidJavadocs(type: Javadoc) {
// 设置源码所在的位置
source = android.sourceSets.main.java.sourceFiles
}
// 生成javadoc.jar
task androidJavadocsJar(type: Jar) {
// 指定文档名称
classifier = ‘javadoc’
from androidJavadocs.destinationDir
}
// 打包main目录下代码和资源的task,生成sources.jar
task androidSourcesJar(type: Jar) {
classifier = ‘sources’
from android.sourceSets.main.java.sourceFiles
}
//配置需要上传到maven仓库的文件
artifacts {
archives androidSourcesJar
archives androidJavadocsJar
}
maven_push.gradle主要就是发布组件ARR的配置:ARR的版本号、名称、maven仓地址账号等。
然后,再build.gradle中引用:
//build.gradle
apply from: ‘maven_push.gradle’
接着,点击Sync后,点击Gradle任务uploadArchives,即可打包并发布arr到maven仓。
最后,壳工程要引用组件ARR,需要先在壳工程的根目录下build.gradle中添加maven仓库地址:
allprojects {
repositories {
google()
jcenter()
//私有服务器仓库地址
maven {
url ‘http://xxx’
}
}
}
接着在app的build.gradle中添加依赖即可:
dependencies {
…
implementation ‘com.hfy.cart:cart:1.0.0’
//以及其他业务组件
}
可见,多工程方案 和我们平时使用第三方库是一样的,只是我们把组件ARR发布到公司的私有maven仓而已。
实际上,我个人比较建议 使用多工程方案的。
注意,我在Demo里 使用的是多工程方案,并且是 把ARR发到JitPack仓,这样是为了演示方便,和发到公司私有maven仓是一个意思。 1、需要根目录下build.gradle中添加JitPack仓地址:maven { url ‘jitpack.io’ } ; 2、JitPack是自定义的Maven仓库,不过它的流程极度简化,只需要输入Github项目地址就可发布项目。
前面说到,组件化的核心就是解耦,所以组件间是不能有依赖的,那么如何实现组件间的页面跳转呢?
例如 在首页模块 点击 购物车按钮 需要跳转到 购物车模块的购物车页面,两个模块之间没有依赖,也就说不能直接使用 显示启动 来打开购物车Activity,那么隐式启动呢? 隐式启动是可以实现跳转的,但是隐式 Intent 需要通过 AndroidManifest 配置和管理,协作开发显得比较麻烦。这里我们采用业界通用的方式—路由。
比较著名的路由框架 有阿里的ARouter、美团的WMRouter,它们原理基本是一致的。
这里我们采用使用更广泛的ARouter:“一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦”。
前面提到,所有的业务组件都依赖了 Common 组件,所以我们在 Common 组件中使用关键字**“api”**添加的依赖,业务组件都能访问。 我们要使用 ARouter 进行界面跳转,需要Common 组件添加 Arouter 的依赖(另外,其它组件共同依赖的库也要都放到 Common 中统一依赖)。
因为ARouter比较特殊,“arouter-compiler ” 的annotationProcessor依赖 需要所有使用到 ARouter 的组件中都单独添加,不然无法在 apt 中生成索引文件,就无法跳转成功。并且在每个使用到 ARouter 的组件的 build.gradle 文件中,其 android{} 中的 javaCompileOptions 中也需要添加特定配置。然后壳工程需要依赖业务组件。如下所示:
//common组件的build.gradle
dependencies {
…
api ‘com.alibaba:arouter-api:1.4.0’
annotationProcessor ‘com.alibaba:arouter-compiler:1.2.1’
//业务组件、业务基础组件 共同依赖的库(网络库、图片库等)都写在这里~
}
//业务组件的build.gradle
android {
…
defaultConfig {
…
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
…
}
dependencies {
…
annotationProcessor ‘com.alibaba:arouter-compiler:1.2.1’
implementation ‘com.github.hufeiyang:Common:1.0.0’//业务组件依赖common组件
}
//壳工程app module的build.gradle
dependencies {
…
//这里没有使用私有maven仓,而是发到JitPack仓,一样的意思~
// implementation ‘com.hfy.cart:cart:1.0.0’
implementation ‘com.github.hufeiyang:Cart:1.0.1’ //依赖购物车组件
implementation ‘com.github.hufeiyang:HomePage:1.0.2’ //依赖首页组件
//壳工程内 也需要依赖Common组件,因为需要初始化ARouter
implementation ‘com.github.hufeiyang:Common:1.0.0’
}
依赖完了,先要对ARouter初始化,需要在Application内完成:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 这两行必须写在init之前,否则这些配置在init过程中将无效
if (BuildConfig.DEBUG) {
// 打印日志
ARouter.openLog();
// 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
ARouter.openDebug();
}
// 尽可能早,推荐在Application中初始化
ARouter.init(this);
}
}
好了,准备工作都完成了。并且知道 首页组件是没有依赖购物车组件的,下面就来实现前面提到的 首页组件 无依赖 跳转到 购物车组件页面。
而使用ARouter进行简单路由跳转,只有两步:添加注解路径、通过路径路由跳转。
1、在支持路由的页面上添加注解@Route(path = “/xx/xx”),路径需要注意的是至少需要有两级,/xx/xx。这里就是购物车组件的CartActivity:
@Route(path = “/cart/cartActivity”)
public class CartActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cart);
}
}
2、然后在首页组件的HomeActivity 发起路由操作—点击按钮跳转到购物车,调用ARouter.getInstance().build("/xx/xx").navigation()即可:
@Route(path = “/homepage/homeActivity”)
public class HomeActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
findViewById(R.id.btn_go_cart).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//通过路由跳转到 购物车组件的购物车页面(但没有依赖购物车组件)
ARouter.getInstance()
.build("/cart/cartActivity")
.withString(“key1”,“value1”)//携带参数1
.withString(“key2”,“value2”)//携带参数2
.navigation();
}
});
}
}
另外,注意在HomeActivity上添加了注解和路径,这是为了壳工程的启动页中直接打开首页。还看到路由跳转可以像startActivity一样待参数。
最后,壳工程的启动页中 通过路由打开首页(当然这里也可以用startActivity(),毕竟壳工程依赖了首页组件):
//启动页
public class SplashActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//通过路由直接打开home组件的HomeActivity,
ARouter.getInstance().build("/homepage/homeActivity").navigation();
finish();
}
}
我们run壳工程 最后看下效果: 到这里,组件间页面跳转的问题也解决了。
组件间没有依赖,又如何进行通信呢?
例如,首页需要展示购物车中商品的数量,而查询购物车中商品数量 这个能力是购物车组件内部的,这咋办呢?
平时开发中 我们常用 接口 进行解耦,对接口的实现不用关心,避免接口调用与业务逻辑实现紧密关联。这里组件间的解耦也是相同的思路,仅依赖和调用服务接口,不会依赖接口的实现。
可能你会有疑问了:既然首页组件可以访问购物车组件接口了,那就需要依赖购物车组件啊,这俩组件还是耦合了啊,那咋办啊?答案是组件拆分出可暴露服务。见下图: 左侧是组件间可以调用对方服务 但是有依赖耦合。右侧,发现多了export_home、export_cart,这是对应拆分出来的专门用于提供服务的暴露组件。操作说明如下:
下面按照此方案 来实施 首页调用购物车服务 来获取商品数量,更好地说明和理解。
首先,在购物车工程中新建module即export_cart,在其中新建接口类ICartService并定义获取购物车商品数量方法,注意接口必须继承IProvider,是为了使用ARouter的实现注入:
/**
/**
CartInfo是购物车信息,包含商品数量:
/**
购物车信息
@author hufeiyang
*/
public class CartInfo {
/**
接着,创建路由表信息,存放购物车组件对外提供跳转的页面、服务的路由地址:
/**
/**
/**
}
前面说页面跳转时是直接使用 路径字符串 进行路由跳转,这里是和服务路由都放在这里统一管理。
然后,为了外部组件使用方便新建CartServiceUtil:
/**
/**
/**
/**
注意到,这里使用静态方法 分别提供了页面跳转、服务获取、服务具体方法获取。 其中服务获取 和页面跳转 同样是使用路由,并且服务接口实现类 也是需要添加@Route注解指定路径的。
到这里,export_cart就已经准备完毕,我们同样发布一个export_cart的ARR(“com.github.hufeiyang.Cart:export_cart:xxx”)。
再来看看module_cart对服务接口的实现。
首先,module_cart需要依赖export_cart:
//module_cart的Build.gradle
dependencies {
…
annotationProcessor ‘com.alibaba:arouter-compiler:1.2.1’
implementation ‘com.github.hufeiyang:Common:1.0.0’
//依赖export_cart
implementation ‘com.github.hufeiyang.Cart:export_cart:1.0.5’
}
点击sync后,接着CartActivity的path改为路由表提供:
@Route(path = CartRouterTable.PATH_PAGE_CART)
public class CartActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cart);
}
}
然后,新建服务接口的实现类来实现ICartService,添加@Route注解指定CartRouterTable中定义的服务路由:
/**
最后,针对上面谈的内容,给大家推荐一个Android资料,应该对大家有用。
首先是一个知识清单:(对于现在的Android及移动互联网来说,我们需要掌握的技术)
泛型原理丶反射原理丶Java虚拟机原理丶线程池原理丶
注解原理丶注解原理丶序列化
Activity知识体系(Activity的生命周期丶Activity的任务栈丶Activity的启动模式丶View源码丶Fragment内核相关丶service原理等)
代码框架结构优化(数据结构丶排序算法丶设计模式)
APP性能优化(用户体验优化丶适配丶代码调优)
热修复丶热升级丶Hook技术丶IOC架构设计
NDK(c编程丶C++丶JNI丶LINUX)
如何提高开发效率?
MVC丶MVP丶MVVM
微信小程序
Hybrid
Flutter
接下来是资料清单:(敲黑板!!!)
领取通道在这里给你们摆上了~
1.数据结构和算法
2.设计模式
3.全套体系化高级架构视频;七大主流技术模块,视频+源码+笔记
4.面试专题资料包(怎么能少了一份全面的面试题总结呢~)
不论遇到什么困难,都不应该成为我们放弃的理由!共勉~
如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。
P7/blob/master/Android%E5%BC%80%E5%8F%91%E4%B8%8D%E4%BC%9A%E8%BF%99%E4%BA%9B%EF%BC%9F%E5%A6%82%E4%BD%95%E9%9D%A2%E8%AF%95%E6%8B%BF%E9%AB%98%E8%96%AA%EF%BC%81.md)**
1.数据结构和算法
[外链图片转存中…(img-GljP7iMw-1643962693713)]
2.设计模式
[外链图片转存中…(img-ttqS60jx-1643962693714)]
3.全套体系化高级架构视频;七大主流技术模块,视频+源码+笔记
[外链图片转存中…(img-O4qdv6NN-1643962693714)]
4.面试专题资料包(怎么能少了一份全面的面试题总结呢~)
[外链图片转存中…(img-HHyUBINk-1643962693715)]
不论遇到什么困难,都不应该成为我们放弃的理由!共勉~
如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。
[外链图片转存中…(img-oOMj9FeM-1643962693715)]