一,介绍
Small插件化方案适用于将一个APK拆分为多个公共库插件、业务模块插件的场景。框架对比详见:https://github.com/wequick/Small/blob/master/Android/COMPARISION.md
官网:http://code.wequick.net/Small
GitHub:https://github.com/wequick/Small
二,接入
1.创建工程
打开Android Studio,点选 Start a new Android Studio project 创建一个Android工程。Application Name 本示例设置为 MySmall (您可以填写为自己的项目名称)。
设置最小支持 SDK (Small允许支持到 API 9):
添加一个 Empty Activity,这个Activity将作为启动画面。
2.创建与配置工程
在 buildscript > dependencies 下添加Small编译插件 gradle-small:
buildscript {
dependencies {
classpath 'net.wequick.tools.build:gradle-small:1.2.0-alpha6'
}
}
apply plugin: 'net.wequick.small'//引用 gradle-small 插件
small {
aarVersion = '1.2.0-alpha6'//设置Small运行库版本:
strictSplitResources=false
buildToAssets=false
}
aarVersion
Small AAR 库版本。用于指定maven依赖 net.wequick.small:small:$aarVersion,该依赖将被自动添加到宿主即各个插件模块中。
strictSplicResources
是否严格分离资源。默认为 true ,即不允许插件模块携带包含资源的第三方库。
buildToAssets
是否将插件作为 apk文件 打包到宿主apk的 assets 目录下。默认为 false ,即作为 so文件 打包到宿主apk的 lib 目录下。
配套的,你需要在宿主的 Application 里指定是否从 assets 读取插件:
@Override
public void onCreate() {
super.onCreate();
// ...
Small.setLoadFromAssets(BuildConfig.LOAD_FROM_ASSETS);
}
BuildConfig.LOAD_FROM_ASSETS 将会被 Small 自动赋值为你配置的 buildToAssets。
子配置:
small {
android {
}
}
用于统一各个模块的Android编译环境配置,包括以下子项:
compileSdkVersion
编译用的 Android SDK 版本。影响到各个子模块的
android {
compileSdkVersion $compileSdkVersion
}
buildToolsVersion
编译工具版本。影响到各个子模块的
android {
buildToolsVersion $buildToolsVersion
}
supportVersion
Support包版本(包括 support-v4,appcompat等等)。影响到各个子模块的
dependencies {
compile 'com.android.support:xx:$supportVersion'
}
bundles
插件类型配置:
bundles 类型, 模块名
或
bundles 类型, [模块1, 模块2]
允许的类型包括:
host,宿主模块
stub,宿主分身模块
lib,公共库插件模块
app,应用插件模块
配置好以上信息后,同步代码
在底部面板 Terminal 中输入以下命令来验证Small环境:
./gradlew small
如果一切正常,将成功输出:
### Compile-time
gradle-small plugin : 1.1.0-beta4 (maven)
small aar : 1.1.0-alpha1 (maven)
gradle core : 2.14.1
android plugin : 2.2.3
OS : Mac OS X 10.12.1 (x86_64)
### Bundles
| type | name | PP | sdk | aapt | support | file | size |
|------|------|----|-----|--------|---------|------|------|
| host | app | | 25 | 25.0.2 | 25.1.0 | | |
不同版本的输出内容可能会有细微差异
配置宿主
由于加载插件需要对Application注入一些方法,我们对包名目录 app > java > com.example.mysmall 右键 New > Java Class 来新建一个Application,如SmallApp:
添加构造方法来初始化Small:
public class SmallApp extends Application {
public SmallApp() {
Small.preSetUp(this);
}
}
由于ContentProvider在onCreate之前被调用,为支持在插件中使用该组件,我们需要提前到构造方法来对之进行懒加载。
如果不需要支持该组件,你也可以放到 onCreate 方法中。
这个方法在应用正常启动时只做一些简单的hook,不会影响性能;但在应用异常启动(后台被杀)时会同步加载插件以保证程序正常运行。
再在 AndroidManifest.xml 中指定这个 Application。
<application
android:name=".SmallApp"
...>
</application>
2.创建插件模块
右键 app 模块 > New > Module:
创建一个应用模块 Phone & Tablet Module,设置 Application/Library name 为 App.main,此时 Module name 自动为 app.main,Package name 自动为 com.example.appmain:
Module name 以 app.* 命名的模块将被 Small 在 编译时 识别为应用插件模块。 Package name 以 app*
结尾的插件将被 Small 在 运行时 识别为应用插件。
更多有关插件的命名规范与自定义,可以参考插件模块。
为了确认我们确实启动了插件,我们修改插件的布局文件 app.main > res > layout > activity_main.xml,将 TextView 的内容改为 Hello Small!:
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Hello Small!" />
3.编译插件
在 Terminal 面板,先编译公共库:
./gradlew buildLib -q
宿主是最基础的一个公共库
再编译 app.main 插件:
./gradlew buildBundle -q -Dbundle.arch=x86
为了方便模拟器运行,本示例指定生成插件到 x86 架构下,关于 bundle.arch 选项的细节可以参考编译选项。 如果不想打包成
*.so ,可以参考打包插件为apk。
查看编译情况:
./gradlew small
应看到生成的 appmain.so 插件:
PP即插件包的资源ID分段,0x77 是 Small 根据模块名哈希自动指派的,你也可以通过配置来自定义资源ID分段。
4.启动插件
现在我们已经生成了插件并内置到宿主包中,要启动插件,我们需要配置一个路由来指向它。
(1)右键 app 模块,New > Folder > Assets Folder 新建 assets 目录:
(2)再右键生成的 assets 目录,New > File 新建路由配置文件 bundle.json:
修改 bundle.json 添加路由:
{
"version": "1.0.0",//是 bundle.json 文件格式版本,目前始终为 1.0.0
"bundles": [//插件数组
{
"uri": "main",//插件唯一ID
"pkg": "com.example.appmain"//插件包名
}
]
}
通过这个配置,main 将被路由向 com.example.mysmall.appmain#MainActivity,更多配置的细节可以参考插件路由。
回到宿主的 app > java > com.example.mysmall > MainActivity,在 onStart 方法中我们通过上述配置的 uri 来启动 app.main 插件:
@Override
protected void onStart() {
super.onStart();
Small.setUp(this, new Small.OnCompleteListener() {
@Override
public void onComplete() {
Small.openUri("main", MainActivity.this);
}
});
}
5.运行宿主
成功运行后,将启动插件模块:
三,提取公共库插件
在我们创建了一个应用插件app.main后本身会存在一些资源,这些资源中有一些包括了主题,界面边距等样式,在多个插件中,这些资源应该是可以复用的,这个时候我们就需要通过提取公共插件模块来解决这个问题。
1.创建公共库插件模块
首先创建一个Android Library模块,名字定义为lib.style
将在app.main插件下的公用资源文件拷贝到lib.style插件的资源下
2.添加公共库引用
修改 app.main/build.gradle,增加对 lib.style 的依赖:
dependencies {
...
compile project(':lib.style')
}
3.添加插件路由
{
"pkg": "com.example.libstyle"
}
4.重新编译插件
清除公共库:
./gradlew cleanLib -q
编译公共库:
./gradlew buildLib -q -Dbundle.arch=x86
编译业务单元:
./gradlew buildBundle -q -Dbundle.arch=x86
5.重新运行后即可看到效果
四,插件模块
插件模块是 Small 特有的插件化与IDE完美结合的产物。做到了“模块即组件,组件即插件”。 组件是工程的角度,插件是应用的角度。只有做到了清晰的组件解耦,才能更好的拆分插件模块。
出于业务需求考虑,Small定义了两类插件:公共库插件与应用插件。 应用插件相对简单,就是用来把大应用拆分成一个个小的业务单元。而公共库插件则是为这些业务单元提供公共的代码与资源。
一,公共库插件模块
公共库插件模块在 开发时 可以通过 compile project(‘:插件模块名’)来被 应用插件模块 所引用。 同时在 编译时 (buildLib) 会被打包成为一个可独立更新的插件。
定义公共库插件模块有两种方式:
1.指定 Module name 为 lib.*
2/在 Small DSL 中显式指明 bundles lib your_module_name
要正确读取到打包的公共库插件也有两种方式:
1.指定 Package name 为 .lib.* 或 .lib*
2.在 bundle.json 中添加描述 “type”: “lib”
二,应用插件模块
应用插件模块在 开发时 可以独立运行。 同时在 编译时 (buildBundle 或 :模块:aR ) 会被打包成一个可独立更新的插件。
定义应用插件模块有两种方式:
1.指定 Module name 为 app.*
2.在 Small DSL 中显式指明 bundles app your_module_name
要正确读取到打包的公共库插件也有两种方式:
1.指定 Package name 为 .app.* 或 .app*
2.在 bundle.json 中添加描述 “type”: “app”
五,插件路由
为了方便插件之间的跨平台调用,Small 提供了 bundle.json 来完成插件路由。
bundle.json 的基本格式为:
{
"version": "1.0.0",
"bundles": [
{ bundle1 },
{ bundle2 },
{ ... },
{ bundleN }
]
}
其中 version 暂为当前文件格式版本,始终为 1.0.0。 bundles 为插件数组。每个插件遵循以下配置。
插件配置
pkg
当前插件的包名,比如 net.wequick.example.small.app.main。
uri
指定当前插件的唯一ID,比如 main。 同时这个 ID 也作为插件的主路由。在Android平台下主路由的表现为 Android:pkg 插件下的 LauncherActivity
type
插件类型:
lib - 公共库插件
app - 业务插件
默认从 pkg 中解析:
当 pkg 包含 .lib. 或以 .libxx 结尾时,取 lib
当 pkg 包含 .app. 或以 .appxx 结尾时,取 app
如果 pkg 不遵循上述格式,需要显式指明。
rules
插件的子路由表。假设在 uri 为 home 的插件下指定:
{
"uri": "home",
"rules": {
"abc": "Some"
}
}
“home/abc” 将被路由往 home 插件下的 Some 界面,在Android平台下的表现为:pkg.SomeActivity
插件调用
Small.openUri("home", context);
参数传递
// app.detail/MainActivity
Uri uri = Small.getUri(this);
if (uri != null) {
String id = uri.getQueryParameter("id");
// Do stuff by `id'
}
六,编译选项
在运行 ./gradlew xx 命令时,可以加入 -Dxx 选项来控制编译方式,包括:
bundle.arch
在 buildToAssets 为 false 的情况下,使用 -Dbundle.arch=xx 可以指定 so 形式插件生成到 [宿主目录]/smallLibs/xx 目录下。
通常在调试模拟器时,你可以使用 x86,在真机打包时使用 armeabi。
bundle.minify
用于指定是否混淆插件,如 ./gradlew buildLib -Dbundle.minify 将对所有公共库插件进行混淆。
org.gradle.parallel
这个是系统自带的选项,用于指定是否并行编译。之所以在这里提及是因为 buildLib 的时候不能打开这个选项,因为公共库插件依赖了宿主,需要宿主编译完成才能正确引用资源。
所以如果你使用持续集成的话,可以用以下的命令集合:
./gradlew cleanLib -q -Dorg.gradle.parallel=true
./gradlew buildLib -q -Dorg.gradle.parallel=false -Dbundle.minify=true
./gradlew cleanBundle -q -Dorg.gradle.parallel=true
./gradlew buildBundle -q -Dorg.gradle.parallel=true -Dbundle.minify=true