47、组件化优化:组件通信、路由框架(ARouter)、组件生命周期、组件隔离
组件化,说白了就是“分而治之”。
一个大型App,如果所有代码都堆在一个Module里,那维护成本简直不敢想。我经历过那种改一行代码要编译半小时的噩梦,嗯,从那以后我就铁了心要搞组件化。
今天咱们聊聊组件化的四个核心痛点:通信、路由、生命周期、隔离。这四个点搞定了,你的项目架构才算真正立住了。
一、组件通信:别让它们“裸聊”
组件之间怎么说话?直接依赖?那还叫组件化吗?
我个人习惯用接口下沉 + 依赖注入的方式。说白了,就是定义一个公共的接口层,让组件A和组件B都依赖这个接口,而不是互相依赖。
核心原则:组件之间只能通过抽象接口通信,绝不能直接引用对方的实现类。
举个例子,登录组件需要通知订单组件刷新状态:
// 公共接口层(放在基础库中)
public interface IOrderRefreshService {
void refreshOrderList();
}
// 订单组件实现
public class OrderRefreshServiceImpl implements IOrderRefreshService {
@Override
public void refreshOrderList() {
// 刷新逻辑
}
}
// 登录组件调用
public class LoginManager {
@Inject
IOrderRefreshService orderRefreshService;
public void onLoginSuccess() {
orderRefreshService.refreshOrderList();
}
}
我在项目中遇到过有人直接用EventBus做组件通信,结果事件满天飞,调试起来跟破案一样。EventBus可以用,但只适合一对多、无返回值
我的建议:组件通信优先用接口下沉,其次用EventBus(但要定义好事件规范),最后才考虑用广播或ContentProvider——那玩意儿太重了。
二、路由框架:ARouter 的正确打开方式
组件化之后,页面跳转成了大问题。你想想看,组件A怎么知道组件B里有个Activity?总不能直接new Intent吧,那又耦合了。
ARouter就是干这个的。它通过注解 + 编译期APT生成路由表,实现解耦跳转。
// 在目标Activity上标注路径
@Route(path = "/order/orderDetail")
public class OrderDetailActivity extends AppCompatActivity {
@Autowired
String orderId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ARouter.getInstance().inject(this); // 自动注入参数
}
}
// 在组件A中跳转
ARouter.getInstance()
.build("/order/orderDetail")
.withString("orderId", "123456")
.navigation();
这里有几个坑,我踩过,你们别踩:
- 路径冲突:不同组件用了相同的路径。我建议统一用
/模块名/功能名的格式,比如/user/login、/order/list。 - 参数类型:ARouter的with系列方法只支持基本类型和Serializable/Parcelable。传复杂对象?要么序列化,要么用JSON字符串传。
- 拦截器:登录拦截、埋点拦截都可以用ARouter的Interceptor实现。但注意,拦截器是全局的,别在里面做耗时操作。
注意:ARouter在混淆时一定要保留注解类和路由表。我曾经因为混淆配置漏了,导致线上版本跳转全崩了……那叫一个惨。
三、组件生命周期:谁生谁死,得有个规矩
组件不是Activity,它没有onCreate、onDestroy。但组件需要初始化、需要释放资源,怎么办?
我一般会定义一个组件生命周期接口,然后在Application中统一管理。
public interface IComponentLifecycle {
void onCreate(Context context);
void onDestroy();
}
// 登录组件的实现
public class LoginComponent implements IComponentLifecycle {
@Override
public void onCreate(Context context) {
// 初始化数据库、注册广播等
Log.d("LoginComponent", "初始化完成");
}
@Override
public void onDestroy() {
// 释放资源
}
}
// Application中管理
public class MyApp extends Application {
private List<IComponentLifecycle> components = new ArrayList<>();
@Override
public void onCreate() {
super.onCreate();
// 按依赖顺序初始化
components.add(new BaseComponent());
components.add(new LoginComponent());
components.add(new OrderComponent());
for (IComponentLifecycle component : components) {
component.onCreate(this);
}
}
}
为什么要按顺序?因为登录组件可能依赖基础组件的数据。如果乱序初始化,会出现空指针。我曾经在项目里用反射自动扫描组件,结果初始化顺序不可控,修了一周的bug……后来老老实实手动排序。
小技巧:可以用@Component(priority = 1)这样的注解来标记优先级,然后在初始化时排序。既灵活又可控。
四、组件隔离:物理隔离 + 编译隔离
组件隔离,说白了就是不让组件之间产生直接依赖。这分两层:
- 物理隔离:每个组件一个独立的Module,有自己的build.gradle。组件之间不能互相引用implementation。
- 编译隔离:在开发阶段,组件可以独立编译、独立运行。这需要用到AndroidManifest合并和Application替换。
举个例子,订单组件在独立运行时,需要有自己的Application和启动Activity:
// 订单组件的build.gradle
if (isRunAlone.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
sourceSets {
main {
if (isRunAlone.toBoolean()) {
manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}
这里有个关键点:资源文件隔离。不同组件的资源名不能冲突,我建议统一加前缀,比如order_、login_。否则编译时资源合并会覆盖,你找都找不到原因。
隔离的终极目标:任何一个组件,拉下来就能独立编译运行。这样每个团队可以并行开发,互不干扰。
五、知识体系总览
下面这张图,是我对组件化优化核心逻辑的总结。你看一眼,心里就有谱了。
六、避坑指南
最后,分享几个我踩过的坑,你们当个参考:
- 资源冲突:不同组件的资源文件(布局、图片、字符串)一定要加前缀。我曾经因为两个组件都有
activity_main.xml,结果编译时合并覆盖,界面全乱了。 - 依赖传递:组件A依赖了基础库B,组件C也依赖了基础库B。如果版本不一致,会出现
ClassNotFoundException。我建议所有公共依赖统一版本号,放在根项目的ext里。 - 混淆配置:ARouter的注解类、路由表、Autowired字段都不能混淆。我一般会在
proguard-rules.pro里统一加上-keep规则。 - 组件间数据共享:别用静态变量!别用静态变量!别用静态变量!用
SharedPreferences或者数据库,或者通过接口回调。静态变量在多进程下就是定时炸弹。
我曾经犯过的错:在组件A中直接new了组件B的类,结果组件B被删掉后,编译直接报错。从那以后,我强制团队所有跨组件调用必须走接口或路由,谁违反谁请全组喝奶茶。
组件化优化,说到底就是定规矩、守规矩。规矩定好了,团队协作效率翻倍;规矩乱了,那就是灾难。希望今天的分享对你有用。
公众号:蓝海资料掘金营,微信deep3321