Android组件化开发,Small应用实践

插件化与组件化

插件化就是将一个app分为一个宿主和多个模块(插件),宿主是被真正安装到设备的apk,负责加载插件,每个插件都是一个独立的apk,最终打包发布时宿主和插件分开或者联合打包。

组件化也是将一个app分为一个宿主和多个模块(组件),每个组件可以是一个单独的模块,也可以相互依赖,最终打包发布时宿主和组件打包成一个apk。

组件化与插件化

关于插件化与组件化的解释,这里参考了这篇文章

为什么组件化

  • 模块解耦,业务模块组件更加独立。
  • 重用公共库模块,减少重复开发和维护的工作量。
  • 并行开发,模块组件支持热更新,加快版本迭代速度,解决用户需要频繁更新app问题。
  • 有效减少编译时间,可以单独编译和调试单个模块,提高开发效率。
  • 方便测试,可以针对单个模块进行测试。

注意:组件化/插件化只是针对一些重运营和大型app的需要而诞生的,如果你的app没有这方面的需求就没必要了,不然反而变得麻烦。

选哪个框架

框架 作者 描述
DroidPlugin 360 插件化框架,免安装运行apk
VirtualApp asLody 插件化框架,与DroidPlugin类似
Small 林光亮 一个轻量,跨平台,高度透明的组件化框架
Atlas 阿里巴巴 手机淘宝的容器化框架,目前了解不多,不评论
DynamicAPK 携程 组件化框架,目前已停止维护
Dynamic-load-apk 百度 组件化框架,使用代理的方式实现Activity生命周期,代码中需要用that代替this

这里只用过DroidPlugin和Small,而最终选择了Small,不用DroidPlugin的主要原因是它不支持插件间的代码和资源相互调用,和项目需求不符合。选择Small主要有以下原因:

  • 已经过商业应用的验证,目前本人已知使用Small的应用有酷狗和千米电商云
酷狗
  • 对项目代码改动不大
  • 支持组件模块间的依赖
  • 文档比较完善

关于Small与各框架的详细对比可以看这里

Small

1. 集成Small

关于如何集成Small可以查看文档这里

2. 项目结构说明

Small 将一个 APK 拆分为多个公共库插件、业务模块插件,它们都是 Android Studio 下的一个 Module。

  • 业务模块插件:Phone & Tablet Module,模块名称格式 app.*,包名格式 packageName.app.*
  • 公共库插件:Android Library,模块名称格式 lib.*packageName.lib.*

Small 通过特定的包名格式识别插件,所以包名需要符合规范。

这里以 Small 的 sample 项目为例子,对各模块做一个简单说明:

  • app:宿主模块,一般只加载和启动插件,不包含业务逻辑
  • app+stub:app模块的子模块,该模块的代码和资源为其他模块所共享,打包时将自动并入app模块,用于存放各模块共享的资源和代码。
  • app.detail:业务模块插件
  • app.home:业务模块插件
  • app.main:业务模块插件
  • app.mine:业务模块插件
  • app.ok-if-stub:访问 app.stub 模块资源测试
  • jni_plugin:jni库依赖测试
  • lib.analytics:数据统计库
  • lib.style:样式库
  • lib.utils:工具类库
  • web.about:本地web网页模块

关于业务模块插件的划分,在项目中我是以业务模块页面跳转为一个分界点,比如业务流程是: 登录注册 -> 主页 -> 直播室,那么就划分为 app.user, app.homeapp.live

3. 依赖关系

  • 宿主不能依赖任何插件
  • lib.* 之间不能相互依赖(代码可以,资源不可以,建议还是不要依赖)
  • app.* 可以依赖 lib.*

4. 打包发布

关于插件的编译打包流程可以查看Small的文档,下面是我在编译打包过程遇到的一些问题:

  • 如果 lib.* 中的资源有增减,先把 public.txt 删除,build 时会自动重新生成资源id,否则有可能遇到 Resources$NotFoundException
  • 正式打包发布时建议完整执行一遍 cleanLib -> cleanBundle -> buildLib -> buildBundle 命令,并确保每个步骤顺利编译。
  • 插件编译打包完成后就在 app\smallLibs 目录下,现在 app(宿主) 模块就是一个完整的项目了,对 app 模块打包签名就可以了。

5. 实际应用中遇到的问题

统一管理不同 Module 的依赖库版本

如果不同的插件中引用了同一个第三方库的不同版本,可能出现 pre-verified 异常。

所以,注意抽取公共库和 统一管理不同 Module 的依赖库版本

配置插件(so)生成目录

Small 默认情况下是把插件生成到 armeabi 目录下,如果想更改可以在 local.properties 添加如下配置:

bundle.arch=armeabi-v7a

或者在 project-level 下的 build.gradle 中添加如下配置(建议):

System.setProperty("bundle.arch", "armeabi-v7a")

或者以命令行参数方式设置

gradlew buildLib -Dbundle.arch=armeabi-v7a

small plugin 中通过读取 bundle.arch 属性设置插件输出目录,具体可以查看 RootExtension.getBundleOutput

app.A和app.B都依赖同样的第三方库(jar,aar)会不会冲突?

会的,公共库可以放在 app.stub 或者 lib.* 中,app.Aapp.B 通过依赖 lib.* 共享该库。

issues 318

解决集成 Bmob 时 okhttp 库冲突问题

这是原来的配置:

compile "cn.bmob.android:bmob-sdk:3.5.0"

错误日志如下:

Error:Execution failed for task ':demo:transformClassesWithJarMergingForDebug'.
> com.android.build.api.transform.TransformException: java.util.zip.ZipException: duplicate entry: okhttp3/Address.class

尝试过下面的方案:

compile ("cn.bmob.android:bmob-sdk:3.5.0") {
    exclude group: "com.squareup.okhttp3"
    exclude group: "com.squareup.okio"
}

理论上这样该结束了,但遇到的情况还要复杂一点,有 lib.a 和 lib.b,lib.b 依赖 lib.a(bmob-sdk在这里),app 依赖 lib.b,我在 lib.a 中添加如上配置发现并没有效果,用 everything 搜了一下,发现 lib.b 的 build 目录下也有一个 bmob-sdk

bmob-sdk

多个 Module 包含重复的库可以在 app 目录下的 build.gradle 添加如下配置过滤掉重复的库

android {
    configurations {
        all*.exclude group: "com.squareup.okio", module: "okio"
        all*.exclude group: "com.squareup.okhttp3"
        all*.exclude group: 'com.google.code.gson'
    }
}    

解决方法出自这里

编译是没问题了,但是后来打包插件 so 时发现 bmob-sdk 中的 okhttp, okio, gson 还是会被打进去,会导致启动失败...

最终的解决方案是把 bmob-sdk-3.5.0.aar(具体位置可以用 everything 搜索一下) 中的 res, jni 和 libs 中的 BmobSDK_3.5.0_20160630.jar 直接拷贝的自己的 lib 工程对应目录,然后去掉 gradle 中的依赖配置,这样就不存在冲突了。

AndroidManifest.xml

注意把第三方库或者SDK需要用到的权限和相关组件的配置添加到 app(宿主)或者 app+stub 模块下的 AndroidManifest.xml。

In strict mode, we do not allow vendor aars

Execution failed for task ':app.test:processReleaseResources'.
> In strict mode, we do not allow vendor aars, please declare them in host build
    - compile('com.android.support:recyclerview-v7:25.0.0')
    - compile('com.android.support:design:25.0.0')
or turn off the strict mode in root build.gradle:
    small {
        strictSplitResources = false
    }

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug

解决办法:

  • cleanLib,再buildLib试下
  • 添加 strictSplitResources = false 配置

参考 issues 175issues 201

怎样判断是否 Debug 模式

开始时想通过访问 lib 中的 BuildConfig.DEBUG 在各插件中判断是否 debug 模式,后来发现 lib 中的 BuildConfig.DEBUG 只会一直返回 false。最后是通过访问 application 节点的 android:debuggable 解决了该问题。解决方案出自这里

  /**
   * app 是否 debug 模式
   *
   * @param context
   */
  public static boolean isDebug(Context context) {
    if (isDebug == null) {
      isDebug = context.getApplicationInfo() != null &&
          (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
    }
    return isDebug;
  }

自定义 Application 放哪里

Small 支持每个插件有自己的 Application(支持 MultiDexApplication),在插件被加载时执行 Applicaiton 的生命周期方法,但一般情况下我们只需要一个 Application。

如果把自定义 Application 放 appapp+stub,无法访问 lib 模块下的代码,放在某个插件下其他插件又访问不了,所以放在一个 lib 下最合适,所有插件通过引用该 lib 并在 AndroidManifest.xml 的 application 节点配置自定义 Application。

由于插件被加载时都会执行一次 Application 的生命周期,所以为了防止重复初始化,这里通过一个静态的布尔值变量 isInited 记录是否已经初始化。示例代码如下:

public class MyApplication extends Application {
  
  private static boolean isInited = false;

  @Override public void onCreate() {
    super.onCreate();
    if (!isInited) {
      isInited = true;
      init();
    }
  }

  private void init(){

  }

}

这样,无论是整包运行,还是调试单个插件都能正常完成 Application 的初始化。

更多问题建议查看 Small 的 issues,因为很多问题都已经有人遇到并解决了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容