CC

Project Url: luckybilly/CC
Introduction: simple and powerful android componentized architecture framework 使用简单但功能强大的安卓组件化框架
More: Author   ReportBugs   OfficialWebsite   
Tags:
组件化-架构-组件-路由-解耦-

一句话介绍:一套基于组件总线的、支持渐进式改造的、支持跨进程调用的、完整的安卓组件化框架

  • 基于组件总线:
    • 不同于市面上种类繁多的路由框架,CC 采用了基于组件总线的架构,不依赖于路由
  • 支持渐进式改造:
    • 可能是目前业界唯一(如果不是,请告诉我)支持渐进式改造的组件化框架
    • 模块解耦不再是前提,将陡峭的组件化改造实施曲线拉平
  • 支持跨进程调用:
    • 支持应用内跨进程调用组件,支持跨 app 调用组件
    • 调用方式与同一个进程内的调用方式完全一致
    • 无需 bindService、无需自定义 AIDL,无需接口下沉
  • 完整:
    • CC 框架下组件提供的服务可以是几乎所有功能,包括但不限于页面跳转、提供服务、获取数据、数据存储等
    • CC 提供了配套插件 cc-register,完成了自定义的组件类、全局拦截器类及 json 转换工具类的自动注册,
    • cc-register 同时还提供了代码隔离、debug 代码分离、组件单独调试等各种组件化开发过程中需要的功能
模块 CC cc-register
最新版本 Download Download

技术原理: Wiki

升级指南:从 CC 1.x.x 升级到 CC 2.x.x

使用 CC 的理由

    从集成 CC 的那一刻起,你的项目就已经组件化成功了:新业务即可以组件的形式开发
    未解耦的模块通过创建一个 IComponent 接口的实现类即可暴露服务给其它组件调用(通过 CC 可支持跨 app 的组件调用)
    有闲暇时再将模块解耦出来,以使其可以单独编译运行
    解耦只是过程,而不是前提
    点击上方强烈推荐的文章链接⬆了解详细的渐进式组件化概念
  • 一静一动(开发时运行 2 个 app,功能完整)
    • 静:主 App (通过跨 App 的方式调用单组件 App 内的组件)
    • 动:单组件 App (通过跨 App 的方式调用主 App 内的所有组件)
    • 通过这种方式让组件之间完全无需依赖,从源头解决代码隔离的问题
  • 3 种 AOP 策略助你随心所欲进行 AOP 编程
    • 静态拦截器(全局拦截器)、动态拦截器、组件内部 onCall 方法中拦截
  • 对 Push 及 jsBridge 友好
    • 直接转发对组件的调用即可,与业务组件完全解耦,参考 demo参考文章:-%E8%AE%A9jsBridge%E6%9B%B4%E4%BC%98%E9%9B%85)
  • 支持跨进程调用组件
    • 包括在 app 内部跨进程调用组件及开发阶段跨 app 调用组件(一静一动两个 app 相互调用对方的组件)

了解业界开源的一些组件化方案:多个维度对比一些有代表性的开源 android 组件化开发方案

demo 演示

demo 下载(主工程,包含 ComponentB 之外的所有组件)

demo_component_b 组件单独运行的 App(Demo_B)下载

以上2 个 app用来演示组件打包在主 app 内和单独以 app 运行时的组件调用,都安装在手机上之后的运行效果如下图所示

image

CC 功能列表

    1. 支持组件间相互调用(不只是 Activity 跳转,支持任意指令的调用/回调)
    2. 支持组件调用与 Activity、Fragment 的生命周期关联
    3. 支持 app 间跨进程的组件调用(组件开发/调试时可单独作为 app 运行)
    4. 支持 app 间调用的开关(默认为关闭状态,调用 CC.enableRemoteCC(true)打开)
    5. 支持同步/异步方式调用
    6. 支持同步/异步方式实现组件(异步实现也叫做:延时回调)
    7. 调用方式不受实现方式的限制(例如:可以同步调用另一个组件的异步实现功能。注:不要在主线程同步调用耗时操作)
    8. 支持添加自定义拦截器【包括:静态拦截器(全局拦截器)和动态拦截器(局部拦截器)】
    9. 支持超时设置
    10. 支持手动取消
    11. 编译时自动注册组件(IComponent),无需手动维护组件注册表(使用 ASM 修改字节码的方式实现)
    12. 支持动态注册/反注册组件(IDynamicComponent)
    13. 支持组件间传递 Fragment、自定义 View 等对象
        13.1 不仅仅是获取 Fragment、自定义 View 的对象,并支持后续的通信。
    14. 支持跨进程组件调用
        14.1 给组件类添加一个注解标明组件所在进程名称即可,无需 bindService,无需创建 AIDL
        14.2 添加注解的名称需要是 AndroidManifest.xml 中已存在的进程名称(即通过 android:process 标记了进程的四大组件),否则无效
        14.3 调用跨进程组件的方式与调用当前进程中的组件相同,无需关注被调用者所在的进程
    15. 尽可能的解决了使用姿势不正确导致的 crash,降低产品线上 crash 率: 
        15.1 组件调用处、回调处、组件实现处的 crash 全部在框架内部 catch 住
        15.2 同步返回或异步回调的 CCResult 对象一定不为 null,避免空指针

目录结构

    - cc                            组件化框架基础库(主要)
    - cc-register                   CC 框架配套的 gradle 插件(主要)
    - cc-settings-2.gradle          组件化开发构建脚本(主要)
    - demo                          demo 主程序(调用其它组件,并演示了动态组件的使用)
    - demo_base                     demo 公共库(base 类、util 类、公共 Bean 等)
    - demo_component_a              demo 组件 A
    - demo_component_b              demo 组件 B(上方提供下载的 apk 在打包时 local.properties 中添加了 demo_component_b=true)
    - demo_component_jsbridge       demo 组件(面向组件封装的 jsBridge,并演示了如何进行跨进程组件调用)
    - demo_component_kt             demo 组件(kotlin)
    - demo_interceptors             demo 全局拦截器(如果有多个 app 并且拦截器不同,可以创建多个 module 给不同 app 使用)
    - cc-settings-demo.gradle       演示如何自定义配置文件,如:添加 actionProcessor 自动注册的配置
    - demo-debug.apk                demo 安装包(包含 demo/demo_component_a/demo_component_kt)
    - demo_component_b-debug.apk    demo 组件 B 单独运行安装包

集成(基本用法,共 4 步)

下面介绍在 Android Studio 中进行集成的详细步骤

1. 添加引用

1.1 在工程根目录的 build.gradle 中添加 CC 自动注册插件的 classpath,最新版本:Download

buildscript {
    dependencies {
        classpath 'com.billy.android:cc-register:x.x.x'
    }
}

1.2 在每个组件 module(包括主 app)的 build.gradle 中添加对 CC 框架的依赖并应用 CC 自动注册插件,以下两种方式任选其一

  • 直接修改 build.gradle 方式
apply plugin: 'com.android.library'
//或
apply plugin: 'com.android.application'

//替换成
//ext.mainApp = true //如果此 module 为主 app module,一直以 application 方式编译,则启用这一行
//ext.alwaysLib = true //如果此 module 为基础库,一直以 library 方式编译,则启用这一行
project.apply plugin: 'cc-register' //所有需要单独编译运行的组件 module 及主 app module 需要添加这一行
project.dependencies.add('api', "com.billy.android:cc:x.x.x") //CC 框架核心包,使用最新版
//注意:最好放在 build.gradle 中代码的第一行
  • 使用公共文件的方式

    下载或复制cc-settings-2.gradle文件放到工程的根目录下,并按如下方式添加修改 module 的 build.gradle

    这样做的好处是:以后可以在此文件中添加的配置可对所有组件 module 都生效 ```groovy apply plugin: 'com.android.library' //或 apply plugin: 'com.android.application'

//替换成 //ext.mainApp = true //如果此 module 为主 app module,一直以 application 方式编译,则启用这一行 //ext.alwaysLib = true //如果此 module 为基础库,一直以 library 方式编译,则启用这一行 apply from: rootProject.file(cc-settings-2.gradle) //注意:最好放在 build.gradle 中代码的第一行


1.3 修改组件 module 的 build.gradle,将 applicationId 去除或者按以下方式修改,否则在集成打包时会报错
 ```groovy
android {
    defaultConfig {
        //仅在以 application 方式编译时才添加 applicationId 属性
        if (project.ext.runAsApp) { 
            applicationId 'com.billy.cc.demo.component.a'
        }
        //...
    }
    //...
}

2. 实现 IComponent 接口创建组件

创建组件(实现IComponent接口,需要保留无参构造方法)

public class ComponentA implements IComponent {
    //需保留无参构造方法

    @Override
    public String getName() {
        //组件的名称,调用此组件的方式:
        // CC.obtainBuilder("ComponentA").build().callAsync()
        return "ComponentA";
    }

    @Override
    public boolean onCall(CC cc) {
        Context context = cc.getContext();
        Intent intent = new Intent(context, ActivityComponentA.class);
        if (!(context instanceof Activity)) {
            //调用方没有设置 context 或 app 间组件跳转,context 为 application
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }
        context.startActivity(intent);
        //发送组件调用的结果(返回信息)
        CC.sendCCResult(cc.getCallId(), CCResult.success());
        //返回值说明
        // false: 组件同步实现(onCall 方法执行完之前会将执行结果 CCResult 发送给 CC)
        // true: 组件异步实现(onCall 方法执行完之后再将 CCResult 发送给 CC,CC 会持续等待组件调用 CC.sendCCResult 发送的结果,直至超时)
        return false;
    }
}

3. 调用组件

//同步调用,直接返回结果
CCResult result = CC.obtainBuilder("ComponentA").build().call();
//或 异步调用,不需要回调结果
String callId = CC.obtainBuilder("ComponentA").build().callAsync();
//或 异步调用,在子线程执行回调
String callId = CC.obtainBuilder("ComponentA").build().callAsync(new IComponentCallback(){...});
//或 异步调用,在主线程执行回调
String callId = CC.obtainBuilder("ComponentA").build().callAsyncCallbackOnMainThread(new IComponentCallback(){...});

更多使用方式请戳CC 进阶用法

4. 在主 app module 中按如下方式添加对所有组件 module 的依赖

注意:组件之间不要互相依赖

ext.mainApp = true
apply from: rootProject.file(cc-settings-2.gradle)

//...

dependencies {
    addComponent 'demo_component_a' //会默认添加依赖:project(':demo_component_a')
    addComponent 'demo_component_kt', project(':demo_component_kt') //module 方式
    addComponent 'demo_component_b', 'com.billy.demo:demo_b:1.1.0'  //maven 方式
}

按照此方式添加的依赖有以下特点:

  • 方便:组件切换 library 和 application 方式编译时,只需在 local.properties 中进行设置,不需要修改 app module 中的依赖列表
    • 运行主 app module 时会自动将【设置为以 app 方式编译的组件 module】从依赖列表中排除
  • 安全:避免调试时切换 library 和 application 方式修改主 app 中的依赖项被误提交到代码仓库,导致 jenkins 集成打包时功能缺失
  • 隔离:避免直接调用组件中的代码及资源

注意:

  1. CC 会优先调用 app 内部的组件,只有在内部找不到对应组件且设置了CC.enableRemoteCC(true)时才会尝试进行跨 app 组件调用。 所以,单组件以 app 运行调试时,如果主 app 要主动与此组件进行通信,请确保主 app 中没有包含此组件,做法为: 在工程根目录的local.properties中添加如下配置,并重新打包运行主 app

    module_name=true #module_name 为具体每个 module 的名称,设置为 true 代表以 application 方式编译调试,从主 app 中排除
    
  2. 组件 module 可以直接点击 android studio 上的绿色 Run 按钮将组件作为 app 安装到手机上运行进行调试,可通过跨 app 调用组件的方式调用到主 app 内的所有组件

可参考主 app module: demo/build.gradle 的配置组件 module: demo_component_a/build.gradle 的配置

状态码清单

状态码 说明
0 CC 调用成功
1 CC 调用成功,但业务逻辑判定为失败
-1 保留状态码:默认的请求错误 code
-2 没有指定组件名称
-3 result 不该为 null。例如:组件回调时使用 CC.sendCCResult(callId, null) 或 interceptor 返回 null
-4 调用过程中出现 exception,请查看 logcat
-5 指定的 ComponentName 没有找到
-6 context 为 null,获取 application 失败,出现这种情况可以用 CC.init(application)来初始化
-7 (@Deprecated 从 2.0.0 版本开始废弃)跨 app 调用组件时,LocalSocket 连接出错
-8 已取消
-9 已超时
-10 component.onCall(cc) return false, 未调用 CC.sendCCResult(callId, ccResult)方法
-11 跨 app 组件调用时对象传输出错,可能是自定义类型没有共用,请查看 Logcat

进阶用法

请点击:推荐阅读:CC 进阶用法

混淆配置

不需要额外的混淆配置

自动注册插件

CC 专用版:cc-register,fork 自AutoRegister,在自动注册的基础上添加了一些 CC 专用的业务

通用版:

源码:AutoRegister 原理:android 扫描接口实现类并通过修改字节码自动生成注册表

版本更新日志

请点击:更新日志

遇到问题怎么办?

  • 先打开 CC 的日志开关,看完整的调用过程日志,这往往能帮助我们找到问题
    CC.enableDebug(true);  //普通调试日志,会提示一些错误信息
    CC.enableVerboseLog(true);  //组件调用的详细过程日志,用于跟踪整个调用过程
    
  • Wiki了解 CC 的实现原理
  • 常见问题了解大家在使用过程中常见的一些问题,也许就能解答你心中的疑问
  • CC 进阶用法了解 README 文档中未提及的用法
  • 看 issue了解开源社区上其它小伙伴提出的问题及解答过程,很可能就有你现在遇到的问题
  • 提 issue,如果以上还没有解决你的问题,请提一个 issue,这很可能是个新的问题,提 issue 能帮助到后面遇到相同问题的朋友
  • 加下方的 QQ 群提问

QQ 群

QQ 群号:686844583

CC 交流群

或者扫描下方二维码加群聊

image

打赏

Support Me
Apps
About Me
Google+: Trinea trinea
GitHub: Trinea