Android安卓架构Android知识

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

2017-05-29  本文已影响1725人  CYRUS_STUDIO

插件化与组件化

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

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

组件化与插件化

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

为什么组件化

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

选哪个框架

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

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

酷狗

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

Small

1. 集成Small

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

2. 项目结构说明

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

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

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

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

3. 依赖关系

4. 打包发布

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

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

解决办法:

参考 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,因为很多问题都已经有人遇到并解决了。

上一篇下一篇

猜你喜欢

热点阅读