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_。否则编译时资源合并会覆盖,你找都找不到原因。

隔离的终极目标:任何一个组件,拉下来就能独立编译运行。这样每个团队可以并行开发,互不干扰。

五、知识体系总览

下面这张图,是我对组件化优化核心逻辑的总结。你看一眼,心里就有谱了。

组件化优化核心逻辑 组件化架构 组件通信 路由框架 (ARouter) 组件生命周期 组件隔离 独立编译运行 核心目标:解耦 → 复用 → 并行开发 → 独立测试 接口下沉 + 路由解耦 + 生命周期统一管理 + 物理/编译隔离

六、避坑指南

最后,分享几个我踩过的坑,你们当个参考:

  • 资源冲突:不同组件的资源文件(布局、图片、字符串)一定要加前缀。我曾经因为两个组件都有activity_main.xml,结果编译时合并覆盖,界面全乱了。
  • 依赖传递:组件A依赖了基础库B,组件C也依赖了基础库B。如果版本不一致,会出现ClassNotFoundException。我建议所有公共依赖统一版本号,放在根项目的ext里。
  • 混淆配置:ARouter的注解类、路由表、Autowired字段都不能混淆。我一般会在proguard-rules.pro里统一加上-keep规则。
  • 组件间数据共享:别用静态变量!别用静态变量!别用静态变量!用SharedPreferences或者数据库,或者通过接口回调。静态变量在多进程下就是定时炸弹。

我曾经犯过的错:在组件A中直接new了组件B的类,结果组件B被删掉后,编译直接报错。从那以后,我强制团队所有跨组件调用必须走接口或路由,谁违反谁请全组喝奶茶。

组件化优化,说到底就是定规矩、守规矩。规矩定好了,团队协作效率翻倍;规矩乱了,那就是灾难。希望今天的分享对你有用。


公众号:蓝海资料掘金营,微信deep3321

专注资料整理