1.框架的选择:本次选择的插件化框架在经过数次比较之后选择了国产大神开发的目前在市场上较为流行的small插件化框架。GITHUB地址:https://github.com/wequick/Small为什么选择了这款框架(省略多字)
2.small的插件化框架的优点:
[1] 独立插件:一个完整的apk包,可以独立运行。比如从你的程序跑起淘宝、QQ,但这加载起来是要闹哪样?
非独立插件:依赖于宿主,宿主是个壳,插件可使用其资源代码并分离之以最小化,这才是业务需要嘛。
--“所有不能加载非独立插件的插件化框架都是耍流氓”。
[2] ACDD加载.so用了Native方法(libdexopt.so),不是Java层,源码见dexopt.cpp。
[3] Service更新频度低,可预先注册在宿主的manifest中,如果没有很好的理由说服我,现不支持。
[4] 要实现宿主、各个插件资源可互相访问,需要对他们的资源进行分段处理以避免冲突。
[5] 这些框架修改aapt源码、重编、覆盖SDK Manager下载的aapt,我只想说_“杀(wan)鸡(de)焉(kai)用(xin)牛(jiu)刀(hao)”。
Small使用gradle-small-plugin,在后期修改二进制文件,实现了PP_段分区。
[6] 使用public-padding对资源id的_TT_段进行分区,分开了宿主和插件。但是插件之间无法分段。
[7] 除了宿主提供一些公共资源与代码外,我们仍需封装一些业务层面的公共库,这些库被其他插件所依赖。
公共插件打包的目的就是可以单独更新公共库插件,并且相关插件不需要动到。
[8] AppCompat: Android Studio默认添加的主题包,Google主推的Metrial Design包也依赖于此。大势所趋。
[9] 联调插件:使用Android Studio调试宿主时,可直接在插件代码中添加断点调试。
以上对比了目前主流的插件化框架的优缺点,small的优势一目了然。
3.small的结构:small官网教程http://code.wequick.net/Small/cn/quickstart
1.宿主(这个作为APP的壳子,作为一个可以容纳所有插件的容器以及App的最外部结构,一般来说里面只盛放启动页,和必须静态注册的一些东西以及不可用于更新的插件)
2.插件(插件可以是一个独立的业务模块,比如app里面的会员中心,订单模块,可以是Activity也可以是Fragment,所有的插件都有自己的命名规范 指定Module name为app.*
应用插件模块在开发时可以独立运行。 同时在编译时(buildBundle或:模块:aR) 会被打包成一个可独立更新的插件。)
3.插件-公共库 (公共库插件存在的意义在于各个moudle中一些values文件夹下的资源都是可被复用的,另外还有主题,公共的util方法,这些都是可以被任何业务模块公用的,这个时候我们需要将这些资源
放到公共库插件中,插件库的命名规范是lib.xxx 比如:com.example.small.lib.style
4.插件-宿主分身 (宿主分身,用通俗的意义来解释就是,本来在开发工程中是以一个插件的形式来分包,但是编译过程中会被打入到宿主之中)
引用官方解释:什么叫分身?分身是宿主的一部分,最终编译到宿主中,不可被独立更新。但是它提供了能力:可以让插件模块自由访问其中的资源与代码。
因为最终编译到宿主中,使它具备了占坑(stub)的功能,即在宿主中预留特定的(无法插件化的)资源、代码或manifest项目,使系统可以正常识别。 此外,任何稳定的资源、代码、第三方库也可使用分身下沉到宿主中,以减少插件体积。
ps:
必须在宿主Manifest注册的项目,包括:所有权限受限Activity包含了暂不支持的属性:process, configChanges 等可能使用FLAG_ACTIVITY_CLEAR_TOP标签来启动 (#415)
所有的Provider,Service,BroadcastReceiver
必须在宿主占坑的资源,包括:转场动画通知栏图标、自定义视图桌面快捷方式图标
5.插件路由:插件路由是写在宿主的assets文件夹下的一个json文件,这个文件的作用在于注册插件,任何一个插件都需要在bundle.json这个插件路由里注册方可生效,否则会出现编译失败的问题。插件路由里面的元素如下(宿主分身无需注册)
插件路由代码演示
{
"manifest": {
"version":"1.0.0",
"bundles": [
{
"uri":"main",
"pkg":"com.example.small.app.main"
},
{
"uri":"member",
"pkg":"com.example.small.app.member"
}
]
},
"updates": [
{
"pkg":"net.wequick.example.small.lib.utils",
"url":"http://7xoedj.dl1.z0.glb.clouddn.com//misc/com.example.small.app.main.apk"
}
]
}
manifest里面包括了所有插件的注册信息,每个插件都有对应的包名pkg,也有一个唯一的标识uri,所有插件之间跳转都需要使用唯一的标识进行跳转,例如 Small.openUri("main", LaunchActivity.this),这个方法就是跳到main插件,相当于插件之间的startActivity方法。
6.进行编译:目前small的编译命令常用的有四个,这四个也能完成几乎所有的操作(PS:大小写不能错,顺序也不能错):
编译命令
1.clean公共库(包括清理宿主和宿主分身以及各种以Lib开头的公共库):./gradlew cleanLib
2.clean插件(清理所有的app.开头的插件模块,业务模块)./gradlew cleanBundle
3.编译公共库 (编译宿主以及宿主分身和所有lib开头的公共库插件) ./gradlew buildLib
4.编译插件(编译所有app.开头的插件) ./gradlew buildBundle
5.指定插件编译 ./gradlew -p app.main assembleRelease//app.main是uri
(PPS:windows貌似可以直接gradlew不需要前面的符号,clean不是每次运行必须的,编译是必须的,而且编译的时候必须是buildLib在buildBundle前面否则会有问题,clean也一样cleanLib需要在cleanBundle之前执行,同时clean操作需要在build之前执行
7.插件更新:small支持的是插件的温更新,不支持实时热更新,在更新完插件之后系统会在下一次执行onResume方法的时候加载新的插件apk,(也就是按home退出在进入app的时候会生效)。更新的第一步是向服务器请求一个新版本的插件路由(bundle.json),更新掉本地宿主的插件路由,同时新版的插件路由里必须包括这次需要更新的插件的包名以及下载的url,根据比较判断要更新之后,再去执行插件的更新方法
=========================================================================== 华丽的分割线============================================================================================
快速入门(来自官网):
概要
本快速入门包含了使用Samll开发的基础步骤,并通过启动插件显示一条简单的文本:
开始创建工程!
准备工作: 安装Android Studio
Step 1: 创建与配置工程
Step 2: 创建一个插件模块
Step 3: 编译插件
Step 4: 启动插件
Step 5: 运行宿主
如果你的电脑还未安装Android Studio,安装它。本示例使用的版本为:
Android Studio 2.2.3
Build #AI-145.3537739, built on December 2, 2016
JRE: 1.8.0_112-release-b05 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
在本步骤你将:
打开Android Studio,点选Start a new Android Studio project创建一个Android工程。Application Name本示例设置为MySmall(您可以填写为自己的项目名称)。
设置最小支持 SDK (Small允许支持到 API 9):
添加一个Empty Activity,这个Activity将作为启动画面。
在IDE左侧面板双击工程 build.gradle (Project):
在buildscript > dependencies下添加Small编译插件gradle-small:
classpath'net.wequick.tools.build:gradle-small:1.1.0-alpha2'
在文件末尾引用gradle-small插件:
applyplugin:'net.wequick.small'
紧随其后,设置Small运行库版本:
small{aarVersion='1.1.0-alpha2'}
以上两个库的最新版本参见bintray当 gradle-small 在 1.1.0-alpha2 以上版本时可以不指定 aarVersion,默认为 gradle-small 版本。 有关更多 small { } 中的配置可以参考small DSL
由于我们更改了脚本,需要同步下工程:点击 Android Studio 顶部菜单栏的同步按钮,系统将开始下载上述插件库。
再在底部面板 Terminal 中输入以下命令来验证Small环境:
./gradlew small
Windows用户为gradlew small,Linux用户为gradle 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:
publicclassSmallAppextendsApplication{publicSmallApp(){Small.preSetUp(this);}}
由于ContentProvider在onCreate之前被调用,为支持在插件中使用该组件,我们需要提前到构造方法来对之进行懒加载。 如果不需要支持该组件,你也可以放到 onCreate 方法中。 这个方法在应用正常启动时只做一些简单的hook,不会影响性能;但在应用异常启动(后台被杀)时会同步加载插件以保证程序正常运行。
再在AndroidManifest.xml中指定这个 Application。
右键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!:
在 Terminal 面板,先编译公共库:
./gradlew buildLib -q
宿主是最基础的一个公共库
再编译 app.main 插件:
./gradlew buildBundle -q -Dbundle.arch=x86
为了方便模拟器运行,本示例指定生成插件到 x86 架构下,关于bundle.arch选项的细节可以参考编译选项。 如果不想打包成 *.so ,可以参考打包插件为apk。
查看编译情况:
./gradlew small
应看到生成的appmain.so插件:
type
name
PP
sdk
aapt
support
file(x86)
size
hostapp2525.0.225.1.0
appapp.main0x772525.0.225.1.0*_appmain.so5.3 KB
PP即插件包的资源ID分段,0x77 是 Small 根据模块名哈希自动指派的,你也可以通过配置来自定义资源ID分段。
现在我们已经生成了插件并内置到宿主包中,要启动插件,我们需要配置一个路由来指向它。
右键 app 模块,New > Folder > Assets Folder新建assets目录:
再右键生成的 assets 目录,New > File新建路由配置文件bundle.json:
修改bundle.json添加路由:
{"version":"1.0.0","bundles":[{"uri":"main","pkg":"com.example.appmain"}]}
这里的:
version,是bundle.json文件格式版本,目前始终为1.0.0
bundles,插件数组
uri,插件唯一ID
pkg,插件包名
通过这个配置,main将被路由向com.example.mysmall.appmain#MainActivity,更多配置的细节可以参考插件路由。
回到宿主的 app > java > com.example.mysmall > MainActivity,在onStart方法中我们通过上述配置的uri来启动app.main插件:
@OverrideprotectedvoidonStart(){super.onStart();Small.setUp(this,newSmall.OnCompleteListener(){@OverridepublicvoidonComplete(){Small.openUri("main",MainActivity.this);}});}
在顶部菜单栏,我们先选择宿主模块app,再点击旁边的运行按钮:
成功运行后,将启动插件模块:
================================================================================== 华丽的分割线 ============================================================================================
项目结构:(途中app是宿主 app+fr是demo中放置fresco的宿主分身 app.main 和app.member是业务插件 lib.style是公共库插件 所有的业务插件均可依赖宿主分身和公共库)
需要注意的地方:
1.主题需要统一放置到一个style公共库里面,供各个插件依赖,业务插件里面可以自定义主题,但是parent要继承style公共库里面的style,业务插件不允许依赖appcompt等android自带的包
2.同时任何的第三方框架全部放置到宿主分身里面,业务模块不允许出现第三方依赖包,否则会编译失败,(特殊情况下可通过关闭严格模式来实现在业务插件引入第三方包,不建议这样做,第三方框架放在分身里面,统一放置好处理),公共库也不建议放置任何第三方框架
深层次结构
可以看到宿主app里面包含了一个assets文件下,文件下包含了插件路由(bundle.json)以及所有插件apk,除了宿主的packagename是软件的pkg以外,其他的插件均是pkgname+这个module的name构成的,这些插件模块的名字也是标识他们的唯一身份,不可以弄错
相关用法:(这些方法仅供参考,后续会更新二次封装后的方法,不建议直接调用small 的api)
1. Small.openUri("main", LaunchActivity.this)//这是启动新插件的方法返回值是boolean类型的,true代表没问题,false代表出状况了
/**这个方法是用来传值的方法,把所有的值放到一个arraymap中进行传递,UriUtil是个人写的封装类 **/
ArrayMap arrayMap =newArrayMap();
arrayMap.put("msg","我接受到了");
String uri = UriUtil.getUriByParams("member",arrayMap);
Small.openUri(uri,this);
//第二种方法
(2). Intent intent = Small.getIntentOfUri("member", mContext);
intent.putExtra("msg","我接受到了");
startActivity(intent);
//参数的接收
Uri uri = Small.getUri(this);
String msg = uri.getQueryParameter("msg");
//跳转到插件中Fragment的方法(例如首页中会员中心的fragment其实是属于业务模块插件中的一部分)//同时需要在bundle.josn中的uri为main的对象里加上//"rules": {"abc": "TestFragment"}方可生效,否则默认指向名称为MainFragment
Fragment fragment = Small.createObject("fragment-v4","main", MainActivity.this);
//跳转到moudle的二级界面
Small.openUri("mian/sub",this);//其中sub也需要在插件路由中配置rules配置对应的uri
FAQ : 踩坑宝典
1.所有第三方框架必须放到宿主分身
2.宿主和lib.style里面必须同时注册design库和appcompt库
3.不要轻易升级androidStudio和gradle,除非官方宣布支持
4.正式签名包可能需要将key放在一个特定的文件夹,不知道是不是必须的,等测试到这一步的时候再去弄
5.build出现异常找不到原因请clean一下Lib和Bundle
6.clean解决不了问题的请删除Lib公共库插件下的public.txt文件
随时更新………………