Android插件化与其构建流程

来源:互联网 发布:centos git ssh配置 编辑:程序博客网 时间:2024/06/05 08:07

最近在调研Android插件化的实现方式,并准备用在公司的项目当中。最后调研发现,公司内部有一套插件化的工具。但是这套工具缺少自动化的构建方式。于是我的工作就变成了开发这么一个自动化构建脚本。本篇主要总结一个简版的插件化的形式。


插件化

插件化是将代码按照不同的业务进行拆分的一个过程。插件可以理解是一个业务模块。一般它需要依附于宿主,并以apk的形式存在于宿主的assets目录下。宿主是整个程序的基础,也是整个程序的框架。最终发布到应用商城的apk就是宿主。

程序的安装和启动就是宿主apk的安装和启动。宿主在首次启动过程中,会从assets目录下加载插件,加载成功之后会检查插件是否有更新,如果有更新,会直接下载。整个过程都是新开线程处理的。插件的最终更新是在下次启动宿主时完成的。下次启动的时候,最新的插件就会被加载,旧的会被删除掉。然后循环上面的过程。

其实也可以做到不重启更新插件,但是更新的条件是:在插件apk下载好之后,宿主当前没有展示插件activity时。目前我们还很难做到完美的实时更新。最主要的是这功能似乎没那么必要。

为什么要插件化?插件化不是因为可以实时更新,修复bug而存在的。它可以让我们更好的管理,维护代码。以业务为维度划分插件可以使得人员分工明确,代码的构建的速度得到大幅度提高。这也是它和热修复最大的区别。

在拆分代码的过程中,必然也会有公共库的存在。我们将插件以业务划分,公共库以功能划分。下面的图展示了整个的划分。
宿主和插件依赖图

图中的provided 指:插件对公共库的引用,在构建过程中是只编译不打包到插件的apk。

构建流程

插件化之后的代码构建,将之前直接构建出一个apk,变成了要构建多个apk。但是这多个apk的构建并不是简单的一个接着一个构建。

首先插件需要依附宿主的公共库,宿主构建完成之后,公共库会被混淆。混淆的代码会使插件里的引用找不到。这个问题的解决是mapping文件。mapping文件是混淆映射文件,可以在宿主的progard的混淆规则文件中定义,比如:

-verbose-applymapping mapping.txt

这么配置之后,构建完的apk之后,会在build/outputs/mapping/目录下有对映flavor的mapping.txt文件生成。
对于插件,在其progard的混淆规则文件中添加配置:

-applymapping [mapping.txt]

中括号里的文件对应宿主生成的mapping.txt文件。

找到了解决方案,我们就可以按照先构建宿主,然后构建插件的顺序来就行构建了。

但是还有一个问题,插件构建完成之后, 我们需要将插件放入宿主的assets目录下。这么做宿主的签名就会失效,我们需要对宿主重新签名才能正确安装。

对于一个正常的apk的构建,签名是最后一道流程。我们也可以在宿主混淆任务执行完成之后,开始构建插件,然后将插件放入宿主的assets目录下。

我们选择上面的流程方案:
启动宿主构建–>寻找依赖–>打包–>混淆–>构建插件–>将插件apk放入宿主assets目录下–>签名apk。

构建插件

构建插件是指完成上面构建流程需要开发的gradle插件。这里简单介绍一下这个插件的实现,具体可以自己查看源码

在改变原有构建流程之前,我们还需要两个依赖配置:
provided,pluginCompile.

provided: 提供给插件引用公共组件,主要实现编译不打包功能:

provided project(path: ':publiclib')

android build tool已经提供了provided功能,但是不支持android library。在这里有详细的修改方式,使得其可以支持android library(只做了2.1.0版本)

pluginCompile: 提供给宿主引用插件:

pluginCompile project(path: ':pluginTest')

具体实现详见代码

有了配置之后,下面介绍一下构建流程的修改:

混淆的任务对应:transformclassesandresourceswithproguardfor。

我们只要拿到该task(下面对应injectTask),然后给它添加依赖:

injectTask.doLast(new Action<Task>() {                                            @Override                                            public void execute(Task task) {                                                //混淆任务完成之后,将mapping文件copy到app项目的更目录                                                copyMapping(variant);                                            }                                        });                                        //注入plugin的assemble任务                                        injectTask.finalizedBy(assembleTask);                                        //plugin任务执行完之后,将资源                                        assembleTask.doLast(new Action<Task>() {                                            @Override                                            public void execute(Task task) {                                                //这里copy一次,原因aapt add 没找到能指定添加路径的参数。                                                String pluginDistPath =copyPluginApkToAssets(pluginVariantOutput);                                                //这是签名之前的apk的路径                                                String packagedProcessAp = appVariantOutput.getProcessResources().getPackageOutputFile().getAbsolutePath();                                                pluginDistPath = pluginDistPath.replaceAll(project.getProjectDir().getAbsolutePath()+"/","");                                                AaptUtls.aaptAddRes(project,pluginDistPath,packagedProcessAp,project.getProjectDir().getAbsolutePath());                                            }                                        });

往未签名的apk里添加文件,需要用到aapt,具体见AaptUtls.java文件。

备注:菜鸟一枚,还请大神们多多指点

原创粉丝点击