原本自动生成的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中定义的服务路由:
/**
@Override
public CartInfo getProductCountInCart() {
//这里实际项目中 应该是 请求接口 或查询数据库
CartInfo cartInfo = new CartInfo();
cartInfo.productCount = 666;
return cartInfo;
}
@Override
public void init(Context context) {
//初始化工作,服务注入时会调用,可忽略
}
}
这里的实现是直接实例化了CartInfo,数量赋值666。然后发布一个ARR(“com.github.hufeiyang.Cart:module_cart:xxx”)。
module_home需要依赖export_cart:
//module_home的Build.gradle
dependencies {
…
annotationProcessor ‘com.alibaba:arouter-compiler:1.2.1’
implementation ‘com.github.hufeiyang:Common:1.0.0’
//注意这里只依赖export_cart(module_cart由壳工程引入)
implementation ‘com.github.hufeiyang.Cart:export_cart:1.0.5’
}
在HomeActivity中新增TextView,调用CartServiceUtil获取并展示购物车商品数量:
@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”,“param1”)//携带参数1
// .withString(“key2”,“param2”)//携带参数2
// .navigation();
CartServiceUtil.navigateCartPage(“param1”, “param1”);
}
});
//调用购物车组件服务:获取购物车商品数量
TextView tvCartProductCount = findViewById(R.id.tv_cart_product_count);
tvCartProductCount.setText(“购物车商品数量:”+ CartServiceUtil.getCartProductCount().productCount);
}
}
看到 使用CartServiceUtil.getCartProductCount()获取购物车信息并展示,跳转页面也改为了CartServiceUtil.navigateCartPage()方法。
到这里home组件的就可以独立调试了:页面跳转和服务调用,独立调试ok后 再集成到壳工程。 先让HomePage工程的app模块依赖Common组件、module_cart 以及本地的module_home
//HomePage工程,app模块的Build.gradle
dependencies {
…
//引入本地Common组件、module_cart、module_home,在app module中独立调试使用
implementation ‘com.github.hufeiyang:Common:1.0.0’
implementation ‘com.github.hufeiyang.Cart:module_cart:1.0.6’
implementation project(path: ‘:module_home’)
}
然后新建MyApplication初始化ARouter、在app的MainActivity中使用ARouter.getInstance().build("/homepage/homeActivity").navigation()打开首页,这样就可以调试了。
调试ok后接着就是集成到壳工程。
壳工程中的操作和独立调试类似,区别是对首页组件引入的是ARR:
dependencies {
…
//这里没有使用私有maven仓,而是发到JitPack仓,一样的意思~
// implementation ‘com.hfy.cart:cart:1.0.0’
implementation ‘com.github.hufeiyang.Cart:module_cart:1.0.6’
implementation ‘com.github.hufeiyang:HomePage:1.0.4’
//壳工程内 也需要依赖Common组件,因为需要初始化ARouter
implementation ‘com.github.hufeiyang:Common:1.0.0’
}
最后run壳工程来看下效果: 获取数量是666、跳转页面成功。
另外,除了export_xxx这种方式,还可以添加一个 ComponentBase 组件,这个组件被所有的Common组件依赖,在这个组件中分别添加定义了业务组件可以对外提供访问自身数据的抽象方法的 Service。相当于把各业务组件的export整合到ComponentBase中,这样就只添加了一个组件而已。但是这样就不好管理了,每个组件对外能力的变更都要改ComponentBase。
另外,除了组件间方法调用,使用EventBus在组件间传递信息也是ok的(注意Event实体类要定义在export_xxx中)。
好了,到这里组件间通信问题也解决了。
上面介绍了Activity 的跳转,我们也会经常使用 Fragment。例如常见的应用主页HomeActivity 中包含了多个属于不同组件的 Fragment、或者有一个Fragment多个组件都需要用到。通常我们直接访问具体 Fragment 类来new一个Fragment 实例,但这里组件间没有直接依赖,那咋办呢?答案依然是ARouter。
先在module_cart中创建CartFragment:
//添加注解@Route,指定路径
@Route(path = CartRouterTable.PATH_FRAGMENT_CART)
public class CartFragment extends Fragment {
…
public CartFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
…
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//显示“cart_fragment"
return inflater.inflate(R.layout.fragment_cart, container, false);
}
}
同时是fragment添加注解@Route,指定路由路径,路由还是定义在export_cart的CartRouterTable中,所以export_cart需要先发一个ARR,module_cart来依赖,然后module_cart发布ARR。
然后再module_home中依赖export_cart,使用ARouter获取Fragment实例:
@Route(path = “/homepage/homeActivity”)
public class HomeActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
…
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction= manager.beginTransaction();
//使用ARouter获取Fragment实例 并添加
Fragment userFragment = (Fragment) ARouter.getInstance().build(CartRouterTable.PATH
《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享
_FRAGMENT_CART).navigation();
transaction.add(R.id.fl_test_fragment, userFragment, “tag”);
transaction.commit();
}
}
可以先独立调试,然后集成到壳工程——依赖最新的module_cart 、HomePage,结果如下:
绿色部分就是引用自cart组件的fragment。
我们通常会在Application的onCreate中做一些初始化任务,例如前面提到的ARouter初始化。而业务组件有时也需要获取应用的Application,也要在应用启动时进行一些初始化任务。
你可能会说,直接在壳工程Application的onCreate操作就可以啊。但是这样做会带来问题:因为我们希望壳工程和业务组件 代码隔离(虽然有依赖),并且 我们希望组件内部的任务要在业务组件内部完成。
那么如何做到 各业务组件 无侵入地获取 Application生命周期 呢?——答案是 使用AppLifeCycle插件,它专门用于在Android组件化开发中,Application生命周期主动分发到组件。具体使用如下:
首先,common组件通过 api 添加 applifecycle-api 依赖 并发布ARR:
//common组件 build.gradle
dependencies {
…
//AppLifecycle
api ‘com.github.hufeiyang.Android-AppLifecycleMgr:applifecycle-api:1.0.4’
}
各业务组件都要 依赖最新common组件,并添加 applifecycle-compiler 的依赖:
//业务组件 build.gradle
…
//这里Common:1.0.2内依赖了applifecycle-api
implementation ‘com.github.hufeiyang:Common:1.0.2’
annotationProcessor ‘com.github.hufeiyang.Android-AppLifecycleMgr:applifecycle-compiler:1.0.4’
sync后,新建类来实现接口IApplicationLifecycleCallbacks用于接收Application生命周期,且添加@AppLifecycle注解。
例如 Cart组件的实现:
/**
public Context context;
/**
@Override
public void onCreate(Context context) {
//可在此处做初始化任务,相当于Application的onCreate方法
this.context = context;
Log.i(“CartApplication”, “onCreate”);
}
@Override
public void onTerminate() {
}
@Override
public void onLowMemory() {
}
@Override
public void onTrimMemory(int level) {
}
}
实现的方法 有onCreate、onTerminate、onLowMemory、onTrimMemory。最重要的就是onCreate方法了,相当于Application的onCreate方法,可在此处做初始化任务。 并且还可以通过getPriority()方法设置回调 多个组件onCreate方法调用的优先顺序,无特殊要求设置NORM_PRIORITY即可。
壳工程引入新的common组件、业务组件,以及 引入AppLifecycle插件:
//壳工程根目录的 build.gradle
buildscript {
repositories {
google()
jcenter()
//applifecycle插件仓也是jitpack
maven { url ‘https://jitpack.io’ }
}
dependencies {
classpath ‘com.android.tools.build:gradle:3.6.1’
//加载插件applifecycle
classpath ‘com.github.hufeiyang.Android-AppLifecycleMgr:applifecycle-plugin:1.0.3’
}
}
//app module 的build.gradle
apply plugin: ‘com.android.application’
//使用插件applifecycle
apply plugin: ‘com.hm.plugin.lifecycle’
…
dependencies {
…
//这里没有使用私有maven仓,而是发到JitPack仓,一样的意思~
// implementation ‘com.hfy.cart:cart:1.0.0’
implementation ‘com.github.hufeiyang.Cart:module_cart:1.0.11’
件onCreate方法调用的优先顺序,无特殊要求设置NORM_PRIORITY即可。
壳工程引入新的common组件、业务组件,以及 引入AppLifecycle插件:
//壳工程根目录的 build.gradle
buildscript {
repositories {
google()
jcenter()
//applifecycle插件仓也是jitpack
maven { url ‘https://jitpack.io’ }
}
dependencies {
classpath ‘com.android.tools.build:gradle:3.6.1’
//加载插件applifecycle
classpath ‘com.github.hufeiyang.Android-AppLifecycleMgr:applifecycle-plugin:1.0.3’
}
}
//app module 的build.gradle
apply plugin: ‘com.android.application’
//使用插件applifecycle
apply plugin: ‘com.hm.plugin.lifecycle’
…
dependencies {
…
//这里没有使用私有maven仓,而是发到JitPack仓,一样的意思~
// implementation ‘com.hfy.cart:cart:1.0.0’
implementation ‘com.github.hufeiyang.Cart:module_cart:1.0.11’