Android 组件化开发实践

231 阅读4分钟

以 LiveApp 直播项目为示例,直接从代码出发介绍 Android 组件化的落地实现。

核心思路一句话:业务模块互不依赖、可独立运行,壳工程负责集成打包。下面全部围绕直播业务场景下的关键代码展开。

一、项目结构

LiveApp/
├── lib_common          # 公共库:网络、图片加载、路由、Base 类、直播基础 SDK 封装
├── module_app          # 空壳工程:集成所有业务组件,配置签名/混淆/多 Dex,输出 APK
├── module_live         # 业务组件:直播间、推流、拉流、房间列表
├── module_gift         # 业务组件:礼物面板、礼物特效、礼物商城
├── module_im           # 业务组件:弹幕、公屏聊天、私信
├── module_user         # 业务组件:登录、个人主页、关注/粉丝
├── build.gradle        # 根构建脚本
├── settings.gradle     # 子模块声明
├── versions.gradle     # 统一依赖版本
└── gradle.properties   # isModule 模式开关

依赖方向:module_app → 业务组件 → lib_common,业务组件之间零依赖。


二、模式切换:isModule 开关

组件化的第一个关键点:业务模块在集成模式下是 library,开发调试时切换为 application 独立运行,整个切换由一个 Gradle 开关控制。

2.1 定义开关

# gradle.properties
# false = 集成模式,业务模块作为 library 被壳工程引用
# true  = 组件模式,业务模块作为 application 独立运行
isModule=false

2.2 业务模块按开关切换插件

// module_live/build.gradle
if (isModule.toBoolean()) {
    apply plugin: 'com.android.application'   // 组件模式:独立 App
} else {
    apply plugin: 'com.android.library'       // 集成模式:普通依赖库
}

2.3 组件模式下配置独立 applicationId

// module_live/build.gradle
defaultConfig {
    if (isModule.toBoolean()) {
        applicationId "com.live.module_live"   // 按各模块实际包名配置
    }
}

2.4 壳工程只在集成模式下引入业务组件

// module_app/build.gradle
dependencies {
    implementation project(':lib_common')
    if (!isModule.toBoolean()) {
        implementation project(':module_live')
        implementation project(':module_gift')
        implementation project(':module_im')
        implementation project(':module_user')
    }
}

三、双份 AndroidManifest 适配

application 插件要求模块声明 LAUNCHER 入口,library 则不允许。解决方案:每个业务模块准备两份清单文件,用 sourceSets 按模式切换。

3.1 组件模式清单:src/main/module/AndroidManifest.xml

自带图标和应用名,声明 LAUNCHER 入口:

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme">
    <activity android:name=".LiveRoomActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

3.2 集成模式清单:src/main/AndroidManifest.xml

只注册页面,不带入口,图标/主题交给壳工程:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <!-- 只注册页面,不含 LAUNCHER 入口 -->
        <activity android:name=".LiveRoomActivity" />
        <activity android:name=".RoomListActivity" />
    </application>
</manifest>

3.3 sourceSets 按模式加载,同时排除调试代码

// module_live/build.gradle
sourceSets {
    main {
        if (isModule.toBoolean()) {
            manifest.srcFile 'src/main/module/AndroidManifest.xml'
        } else {
            manifest.srcFile 'src/main/AndroidManifest.xml'
            java {
                exclude 'debug/**'   // 集成模式下不编译模块内调试代码
            }
        }
    }
}

四、统一依赖管理:versions.gradle + lib_common

4.1 所有版本集中声明

// versions.gradle
ext.deps = [:]

def versions = [:]
versions.android_gradle_plugin = '3.5.2'
versions.kotlin = "1.1.51"
versions.support = "27.0.2"
versions.arouter_api = "1.3.1"
versions.arouter_compiler = "1.1.4"
versions.live_sdk = "x.y.z"          // 直播推拉流 SDK
versions.im_sdk = "x.y.z"            // IM / 弹幕 SDK
versions.room = "x.y.z"              // Room 数据库
// ...

4.2 lib_common 用 api 下发基础能力

// lib_common/build.gradle
dependencies {
    api fileTree(include: ['*.jar'], dir: 'libs')
    api deps.support.app_compat
    api deps.retrofit.runtime              // 网络:Retrofit + RxJava
    api deps.glide                         // 图片:Glide
    api deps.arouter_api                   // 路由:ARouter
    api deps.live_sdk                      // 直播:推流/拉流 SDK
    api deps.im_sdk                        // IM:弹幕/聊天 SDK
    // ...
}

api 会向下游传递依赖,业务模块只需依赖 lib_common 一行,即可直接使用网络、图片、路由、直播、IM 等基础能力。


五、跨模块通信:ARouter

业务组件之间不能引用对方的类,页面跳转统一走 ARouter 路由。

5.1 壳工程 Application 初始化

public class LiveApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        if (BuildConfig.DEBUG) {
            ARouter.openDebug();
            ARouter.openLog();
        }
        ARouter.init(this);
    }
}

5.2 目标页面用 @Route 声明路径

// module_live
@Route(path = "/live/roomList")
public class RoomListActivity extends AppCompatActivity { }

@Route(path = "/live/room")
public class LiveRoomActivity extends AppCompatActivity { }

// module_gift
@Route(path = "/gift/panel")
public class GiftPanelActivity extends AppCompatActivity { }

@Route(path = "/gift/store")
public class GiftStoreActivity extends AppCompatActivity { }

// module_im
@Route(path = "/im/chat")
public class ChatActivity extends AppCompatActivity { }

// module_user
@Route(path = "/user/profile")
public class UserProfileActivity extends AppCompatActivity { }

5.3 调用方只认 path,不依赖目标模块

// module_live/LiveRoomActivity.java —— 与 gift / im / user 模块零耦合
ARouter.getInstance().build("/gift/panel").navigation();
ARouter.getInstance().build("/im/chat")
       .withString("roomId", roomId)
       .navigation();
ARouter.getInstance().build("/user/profile")
       .withLong("userId", anchorId)
       .navigation();

5.4 各业务模块配置注解处理器

// 各业务模块 build.gradle
defaultConfig {
    javaCompileOptions {
        annotationProcessorOptions {
            arguments = [ moduleName : project.getName() ]
        }
    }
}

dependencies {
    annotationProcessor deps.arouter_compiler
}

六、日常开发流程

  1. 独立调试:gradle.properties 设 isModule=true → Sync → 直接运行 module_live / module_gift / module_im / module_user 等单个组件;
  2. 集成联调:改回 isModule=false → Sync → 运行壳工程 module_app,验证完整直播流程。

七、注意事项

  • 路由 path 全局唯一:按模块加前缀约定(/live/、/gift/、/im/、/user/);
  • 资源命名:加模块前缀或配置 resourcePrefix,避免合并时资源冲突(如直播相关 layout 命名 live_xxx,礼物 gift_xxx);
  • 版本一致:SDK 与三方库版本统一走 versions.gradle;
  • 权限声明:直播推拉流、录音、摄像头权限在壳工程统一声明,也可按模块分清单文件由壳工程合并;
  • 生命周期:直播后台保活、音视频生命周期跨模块时注意统一在壳工程 Application 初始化并暴露接口给业务模块调用;
  • 混淆规则:各模块各自维护,壳工程统一引用。

参考

  • 项目源码:LiveApp
  • 阿里 ARouter 路由框架