-
Notifications
You must be signed in to change notification settings - Fork 116
组件化架构的搭建
一个理想的组件化项目架构是各个业务模块之间相互独立,没有依赖关系。且在开发时单个模块能够独立运行,打包时各个模块模块集成打包为一个APP。如下图所示:
app壳工程是一个空模块,没有任何业务逻辑。在开发时,各个业务组件能够作为一个APP独立运行,而在打包时,APP壳工程模块依赖所有业务组件,最终打包成一个完整的apk。可以通过一个参数配置来表示各个模块是集成打包,还是独立运行。
在项目跟目录的build.gradle下添加一个isBundleMode的boolean类型的参数,true表示各个模块集成打包,false表示各个模块可以独立运行。
ext {
isBundleMode = true
}
APP壳工程模块只有在所有模块集成打包时才有用,因此,只有当isBundleMode为true时才会依赖所有子模块。相关配置如下:
dependencies {
if (rootProject.ext.isBundleMode) { // 集成模式依赖所有子模块
implementation project(':home')
implementation project(':publish')
implementation project(':find')
implementation project(':user')
implementation project(':main')
} else { // 子模块独立运行模式,无需依赖子模块
implementation project(':common')
}
}
子模块即可以作为library集成打包到APP模块,也可以作为一个单独的APP独立运行。即可以通过isBundleMode参数来决定子模块是library还是application。在子模块的build.gradle中添加如下配置:
if (Boolean.valueOf(rootProject.ext.isBundleMode)) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
除此之外,由于application与library模式下AndroidManifest文件配置也是不同的,因此每个模块都需要两个AndroidManifest文件,一个作为单独运行的配置文件,一个作为集成运行的模块。
可以在src/main下边新建一个module目录,然后添加一个AndroidManifest文件作为独立运行的配置,如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zhpan.module_me" >
<!--android:name属性——是用来设置所有activity属于哪个application的,默认是android.app.Application。-->
<application
android:name="com.zhpan.library.base.BaseApp"
android:allowBackup="true"
android:label="Me"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="com.zhpan.module_me.MeActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
原本AndroidManifest文件作为集成环境下的配置,如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zhpan.module_me">
<application>
<activity android:name=".MeActivity"/>
</application>
</manifest>
接下来在build.gradle中根据isBundleMode参数加载不同的AndroidManifest清单文件,如下:
sourceSets {
main {
if (rootProject.ext.isBundleMode) {
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
//排除java/debug文件夹下的所有文件
exclude '*module'
}
} else {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
}
}
}
这样便完成了组件化模块独立运行与集成运行的配置,通过修改isBundleMode参数即可实现。
不同模块间Activity的跳转、加载其他模块的Fragment以及模块间的通信都可以通过ARouter来实现。关于ARouter的配置这里不再赘述,详情参考ARouter主页。
为“发现”模块中的Activity设置Route路径,如下:
@Route(path = ACTIVITY_FIND)
public class FindActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_find);
}
}
在其他模块通过ARouter以及对应的Path即可跳转到FindActivity页面。如下:
ARouter.getInstance()
.build(RoutingTable.ACTIVITY_FIND)
.navigation();
在“发现”模块为Fragment添加Route路径,如下:
@Route(path = RoutingTable.FRAGMENT_FIND)
public class FindFragment extends BaseFragment {
@Override
protected int getLayout() {
return R.layout.fragment_find;
}
@Override
protected void initTitle() {
}
@Override
protected void initView(Bundle savedInstanceState) {
}
public static FindFragment getInstance() {
return new FindFragment();
}
}
接着在其他模块可以通过ARouter与对应的path获取这个Fragment的实例,代码如下:
BaseFragment findFragment = (BaseFragment) ARouter.getInstance().build(RoutingTable.FRAGMENT_FIND).navigation();
子模块之间避免不了的需要进行数据交换,比如用户模块提供用户信息,其他模块需要调用用户模块来获取用户信息。子模块之间没有相互依赖,因此需要通过其他方法来实现模块间的数据交换。
ARouter也提供了组件间数据交换的能力,实现方式如下。
首先在common模块定义一个提供用户信息的接口,并继承IProvider。如下:
import com.alibaba.android.arouter.facade.template.IProvider;
public interface IARouterUserService extends IProvider {
String getUserId();
String getUserName();
int getUserAge();
}
然后在用户模块添加上述接口的实现,如下:
@Route(path = RoutingTable.USER_DATA)
public class IARouterUserServiceImpl implements IARouterUserService {
@Override public String getUserId() {
return UserHolder.getUserId();
}
@Override public String getUserName() {
return UserHolder.getUserName();
}
@Override public int getUserAge() {
return UserHolder.getUserAge();
}
@Override public void init(Context context) {
}
}
注意这个实现类需要使用Route注解,并设置path。在这个类的各个方法中返回用户的信息。
接着,在其他模块的Activity或者Fragment中获取IARouterUserServiceImpl的实例。需要注意的是在使用时一定要通过 ARouter.getInstance().inject(this);
进行注入。
public class HomeFragment extends BaseFragment {
IARouterUserService userInfo;
// 可以通过Autowired进行自动初始化
@Autowired(name = RoutingTable.USER_DATA)
IARouterUserService userInfo2;
@Override
protected void initView(Bundle savedInstanceState) {
ARouter.getInstance().inject(this);
// 手动初始化
userInfo = (IARouterUserService) ARouter.getInstance()
.build(RoutingTable.USER_DATA)
.navigation();;
userInfo.getUserName();
userInfo2.getUserAge();
}
}
首先在common组件中定义一个获取用户信息的接口,如下:
public interface ISPIUserService {
String getUserId();
String getUserName();
int getUserAge();
}
然后在user模块实现这个接口,并返回用户信息。如下:
public class ISPIUserServiceImpl implements ISPIUserService {
@Override public String getUserId() {
return UserHolder.getUserId();
}
@Override public String getUserName() {
return UserHolder.getUserName();
}
@Override public int getUserAge() {
return UserHolder.getUserAge();
}
}
接着需要在APP模块下注册服务。在src/main目录下添加resources/META-INF/services目录,并且添加一个文件,这个文件的名字必须为com.zhpan.library.ISPIUserService
,即ISPIUserService的全路径名,文件中添加 com.zhpan.module_me.ISPIUserServiceImpl
一行配置,即ISPIUserServiceImpl这个类的全路径名。
完成配置后便可以通过JDK提供的API ServiceLoader 来获取ISPIUserServiceImpl的实例了,对ServiceLoader进行如下封装:
public class ServiceLoaderManager {
public static <T> T provide(Class<T> tClass) {
// 返回一个ISPIUserService实例的集合
Iterator<T> iterator = ServiceLoader.load(tClass).iterator();
if(iterator.hasNext()) {
// 这里只取第一个实例
return iterator.next();
}
throw new IllegalStateException(
"Can not find the implement class of " + tClass.getCanonicalName());
}
}
最后,便可以在common模块通过ServiceLoaderManager获取到ISPIUserServiceImpl的实例了。添加一个UserInfoTools的工具类,如下:
public class UserInfoTools {
// 获取用户名称
public static String getUserName() {
return getUserService().getUserName();
}
// 获取用户ID
public static String getUserId() {
return getUserService().getUserId();
}
// 获取用户年龄
public static int getUserAge() {
return getUserService().getUserAge();
}
// 获取ISPIUserServiceImpl实例
private static ISPIUserService getUserService() {
return ServiceLoaderManager.provide(ISPIUserService.class);
}
}
由于UserInfoTools是位于common模块中,所有依赖了common模块的子模块都可以通过UserInfoTools获取到用户信息。
例如APPJoint也可以实现类似的功能
- JMM与volatile关键字
- synchronized的实现原理
- synchronized等待与唤醒机制
- AQS的实现原理
- ReentrantLock的实现原理
- ReentrantLock等待与唤醒机制
- CAS、Unsafe类以及Automic并发包
- ThreadLocal的实现原理
- 线程池的实现原理
- Java线程中断机制
- 多线程与并发常见面试题
- Android基础知识汇总
- MVC、MVP与MVVM
- SparseArray实现原理
- ArrayMap的实现原理
- SharedPreferences
- Bitmap
- Activity的启动模式
- Fragment核心原理
- 组件化项目架构搭建
- 组件化WebView架构搭建
- 为什么 Activity.finish() 之后 10s 才 onDestroy ?
- Binder与AIDL
- Binder实现原理
- Android系统启动流程
- InputManagerService
- WindowManagerService
- Choreographer详解
- SurfaceFlinger
- ViewRootImpl
- ActivityManagerService
- APP启动流程
- PMS安装与签名校验
- Dalvik与ART
- 内存优化策略
- UI界面及卡顿优化
- App启动优化
- ANR问题
- 包体积优化
- APK打包流程
- 电池电量优化
- Android屏幕适配
- 线上性能监控1--线上监控切入点
- 线上性能监控2--Matrix实现原理
- Glide实现原理
- OkHttp实现原理
- Retrofit实现原理
- RxJava实现原理
- RxJava中的线程切换与线程池
- LeakCanary实现原理
- ButterKnife实现原理
- ARouter实现原理
- Tinker实现原理
- 2. 两数相加
- 19.删除链表的倒数第 N 个结点
- 21. 合并两个有序链表
- 24. 两两交换链表中的节点
- 61. 旋转链表
- 86. 分隔链表
- 92. 反转链表 II
- 141. 环形链表
- 160. 相交链表
- 206. 反转链表
- 206 反转链表 扩展
- 234. 回文链表
- 237. 删除链表中的节点
- 445. 两数相加 II
- 面试题 02.02. 返回倒数第 k 个节点
- 面试题 02.08. 环路检测
- 剑指 Offer 06. 从尾到头打印链表
- 剑指 Offer 18. 删除链表的节点
- 剑指 Offer 22. 链表中倒数第k个节点
- 剑指 Offer 35. 复杂链表的复制
- 1. 两数之和
- 11. 盛最多水的容器
- 53. 最大子序和
- 75. 颜色分类
- 124.验证回文串
- 167. 两数之和 II - 输入有序数组 -169. 多数元素
- 189.旋转数组
- 209. 长度最小的子数组
- 283.移动0
- 303.区域和检索 - 数组不可变
- 338. 比特位计数
- 448. 找到所有数组中消失的数字
- 643.有序数组的平方
- 977. 有序数组的平方