Android多Module合并aar打包
Android多Module合并aar打包
最近项目需要这方面的东西,折磨了我好些时间,做下笔记免得忘记
fat-aar
开发Android几年了,没有百度解决不了的问题,如果有那就谷歌(水平菜,接触不到高深项目)。在网上找到了 com.kezong:fat-aar:1.2.12(https://github.com/kezong/fat-aar-android),这个算是维护得比较好的,其它不少都没维护了。加上这些配置就行:
根目录:
buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.6.2' classpath 'com.kezong:fat-aar:1.2.12' }}
主module:
apply plugin: 'com.kezong.fat-aar'dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) embed project(path: ':moduleName', configuration: 'default') ...}
再就是com.android.tools.build:gradle和gradle-wrapper.properties的配置,这个要按照fat-aar的文档来,这个很重要,gradle更新换代,intermediates变得很不一样,对不上就GG。我配的是,3.6.2和5.6.4,是可行的,同步代码会报错,问题不大,编译能过就行。
打aar肯定要混淆,这就是折磨我两天的东西了,文档没写,我挨个module都写混淆规则,结果混淆的类都没打进包(原因不明,水平太菜),各个模块下的aar也没有相应的混淆类,-keep的倒是在,找了好多资料突然想起之前看过的一篇文章说fat-aar是通过复制各module编译后的一些资源来合并的,那混淆规则是不是只要写到主module就行了,OK,搞定了。
编译前资源复制合并
这个是使用fat-aar之前使用的方式,效果不是很好,而且很麻烦容易犯错。
思路:很简单,复制Java、资源文件(清单文件等一些奇奇怪怪的资源就得手动合并了,不是不能写,但那可太恶心了)到一个新的module,定义一个核心module,如果资源冲突,以核心module为准,纯体力活。这里贴下代码,也许以后用得上:
String[] modules = ["moduleNamexxxx", "moduleNamexxxx"];String[] manifestAddOrIgnore = ["uses-permission", "permission"];String centerModule = "centermodule";task mergeModules (type: Exec){ mergeManifest(centerModule, modules, manifestAddOrIgnore) mergeOnlyCopyContent(modules) mergeOnlyCopy(centerModule, modules)}/** * 清单文件整合 */def mergeManifest(String centerModule, String[] modules, String[] manifestAddOrIgnore) { println("===============mergeManifest===============") String manifestPath = "src/main/AndroidManifest.xml"; for (String aoi : manifestAddOrIgnore) { for (String module : modules) { handleAddOrIgnoreManifest("../${module}/${manifestPath}", manifestPath, aoi) } } copyApplication(centerModule, manifestPath) for (String module : modules) { if (centerModule != module) { copyApplicationValue("../${module}/${manifestPath}", manifestPath); } }}def copyApplicationValue(String moduleXmlPath, String xmlPath) { File original = file(moduleXmlPath) File target = file(xmlPath) XmlParser targetParser = new XmlParser() Node targetNode = targetParser.parse(target) XmlParser originalParser = new XmlParser() Node origNode = originalParser.parse(original) List oApplicationList = origNode.get("application") if (oApplicationList.isEmpty()) { return } Node oApplication = oApplicationList.get(0) Node tApplication = targetNode.get("application").get(0) tApplication.value().addAll(completeComponentName(oApplication, origNode.@package)) new XmlNodePrinter(new PrintWriter(new FileWriter(target))).print(targetNode)}def completeComponentName(Node oApplication, String packageName) { List list = oApplication.value() String nameValue; for (Node n : list) { for (Map.Entry entry : n.attributes().entrySet()) { if (entry.getKey().getLocalPart() == "name") { nameValue = entry.getValue(); if (nameValue.startsWith(".")) { entry.value = packageName + nameValue } break } } } return oApplication.value()}def copyApplication(String centerModule, String manifestPath) { String moduleXmlPath = "../${centerModule}/${manifestPath}"; File original = file(moduleXmlPath) File target = file(manifestPath) XmlParser targetParser = new XmlParser() Node targetNode = targetParser.parse(target) XmlParser originalParser = new XmlParser() Node origNode = originalParser.parse(original) List tApplication = targetNode.get("application") if (!tApplication.isEmpty()) { targetNode.remove(targetNode.get("application")) } targetNode.append(origNode.get("application").get(0)) new XmlNodePrinter(new PrintWriter(new FileWriter(target))).print(targetNode)}def handleAddOrIgnoreManifest(String moduleXmlPath, String xmlPath, String eleName) { File original = file(moduleXmlPath) File target = file(xmlPath) XmlParser targetParser = new XmlParser() Node targetNode = targetParser.parse(target) XmlParser originalParser = new XmlParser() Node origNode = originalParser.parse(original) List targetElements = targetNode.get(eleName) List originalElements = origNode.get(eleName) if (originalElements.isEmpty()) { return; } if (targetElements.isEmpty()) { for (Node n : originalElements) { targetNode.append(n) } } else { addOrIgnore(targetElements, originalElements, targetNode); } new XmlNodePrinter(new PrintWriter(new FileWriter(target))).print(targetNode)}static def addOrIgnore(List targetElements, List originalElements, Node target) { for (Node oNode : originalElements) { if (!isRepeat(oNode, targetElements)) { target.append(oNode) } }}static def isRepeat(Node checkNode, List targetElements) { Object nameValue = ""; for (Map.Entry entry : checkNode.attributes().entrySet()) { if (entry.getKey().getLocalPart() == "name") { nameValue = entry.getValue(); break } } for (Node tNode : targetElements) { for (Map.Entry<String, Object> en : tNode.attributes().entrySet()) { if (en.getKey().getLocalPart() == "name") { if (nameValue == en.getValue()) { return true } } } } return false}/** * values文件整合 */def mergeOnlyCopyContent(String[] modules) { println("===============mergeOnlyCopyContent===============") String colorPath = "src/main/res/values/colors.xml"; for (String module : modules) { mergeValueByName("../${module}/${colorPath}", colorPath) } String stringPath = "src/main/res/values/strings.xml"; for (String module : modules) { mergeValueByName("../${module}/${stringPath}", stringPath) } String stylePath = "src/main/res/values/styles.xml"; for (String module : modules) { mergeValueByName("../${module}/${stylePath}", stylePath) }}def mergeValueByName(String moduleXmlPath, String xmlPath) { File original = file(moduleXmlPath) if (!original.exists()) { println("${moduleXmlPath} is not exist") return } File target = file(xmlPath) if (!target.exists()) { println(xmlPath) println("${xmlPath} is not exist") copy { from moduleXmlPath into xmlPath.substring(0, xmlPath.lastIndexOf('/')) } return } XmlParser targetParser = new XmlParser() Node targetNode = targetParser.parse(target) XmlParser originalParser = new XmlParser() Node origNode = originalParser.parse(original) for (Node child : origNode.iterator()) { if (!updateNode(targetNode, child)) { targetNode.appendNode(child.name(), child.attributes(), child.value()) } } new XmlNodePrinter(new PrintWriter(new FileWriter(target))).print(targetNode)}static def updateNode(Node targetNode, Node child) { for (Node it : targetNode.iterator()) { if (it.attributes().get("name") == child.attributes().get("name")) { it.setValue(child.value()) it.attributes().clear(); it.attributes().putAll(child.attributes()) return true } } return false;}/** * 仅仅需要copy的文件整合 */def mergeOnlyCopy(String centerModule, String[] modules) { println("===============mergeOnlyCopy===============") String javaPath = "src/main/java/"; for (String module : modules) { copyJavaDirFile(module, javaPath) } String resPath = "src/main/res/"; ArrayList<String> filter = new ArrayList<>() filter.add("values"); for (String module : modules) { copyJavaDirFile(module, resPath, filter) } String assetsPath = "src/main/assets/"; for (String module : modules) { copyJavaDirFile(module, assetsPath) } String libsPath = "libs/"; for (String module : modules) { copyJavaDirFile(module, libsPath) }// String sdkPath = "sdk/"; for (String module : modules) { copyJavaDirFile(module, sdkPath) }}def copyJavaDirFile(String moduleFileName, String targetDir) { copyJavaDirFile(moduleFileName, targetDir, new ArrayList<String>())}def copyJavaDirFile(String moduleFileName, String targetDir, ArrayList<String> dirFilter) { String origJavaPath = "../${moduleFileName}/${targetDir}" FileTree tree = fileTree(dir: origJavaPath) tree.visit { file -> if (!file.isDirectory()) { String path = file.path; println("=======path======" + path) if (!filterCheck(path, dirFilter)) { String originPath = origJavaPath + path; String targetPath = targetDir + getParentDir(path); copy { from originPath into targetPath } } } }}static def getParentDir(String path) { if (path.contains("/")) { return path.substring(0, path.lastIndexOf("/")) } return "/";}static def filterCheck(String target, ArrayList<String> dirFilter) { for (String dir : dirFilter) { if (target.contains(dir)) { return true } } return false}
sourceSets配置资源
项目最后采用的这种方式,很适合我们的项目,因为res里面的文件非常的少,不用考虑这里面的合并问题,混淆也没啥问题。同事的前同事提供的方案,老实说没想到这种方案。
思路:配置一个打包核心module,在module里面配置sourceSets来达到目的,具体就不写了,sourceSets的配置CSDN一大堆,肯定比我强。
不过就算这样仍然遇到了问题,项目针对一些渠道需要在其它module中改代码,但是又不希望改这些代码,模块内处理最好,sourceSets研究了老半天也没整出个所以然,最后干起了老本行——复制代码。
思路:module中开个渠道,更改的代码写在渠道文件夹的Java下面,然后将其它module的代码复制过来,排除被重新了的代码,通过sourceSets配置资源。贴下代码:
String[] javaSources = [ '../module-name/src/main/java', ...]String[] covers = [ 'com/xxx/xxx/xxx/xxxxx.java',];task syncCode(type: Exec) { for (String source : javaSources) { copyFrom(source, covers) }}def copyFrom(String sourceDir, String[] covers) { FileTree tree = fileTree(dir: sourceDir) tree.visit {FileTreeElement file -> if (!file.isDirectory()) { String path = file.path; if (!isCover(path, covers)) { String sourceFilePath = sourceDir + "/" + path String targetPath = getTargetPath(sourceDir, path) copy { from sourceFilePath into targetPath } } } }}static def getTargetPath(String sourceDir, String path) { // 这里只处理src,有啥奇怪的目录自己处理 String[] spl = sourceDir.split("/") int length = spl.length; boolean isJavaDirGet = false; List<String> dirs = new ArrayList<>() for (int i = length - 1; i >= 0; i--) { if (!isJavaDirGet) { isJavaDirGet = "java" == spl[i] } dirs.add(spl[i]) if ("src" == spl[i] && isJavaDirGet) { break } } int size = dirs.size() StringBuilder sb = new StringBuilder(); for (int i = size - 1; i >= 0; i--) { sb.append(dirs.get(i)).append("/") } sb.append(getParentDir(path)) return sb.toString()}def isCover(String path, String[] covers) { boolean isCover = false for (String c : covers) { if (c == path) { println "file ruled out, [path]: ${path}, [c]: ${c}" isCover = true; break } } return isCover}static def getParentDir(String path) { if (path.contains("/")) { return path.substring(0, path.lastIndexOf("/")) } return "/";}
写这个主要是为了做笔记,出了啥问题别问候我哈。
更多相关文章
- Android(安卓)简单计算器源码....
- Android(安卓)之 MediaPlayer类
- [置顶] android按键功能的拓展
- android 1.界面背景的性能优化[
- Android学习之Adapter(适配器)源代码分析与观察者模式的运用(一)
- Android(安卓)JNI和NDK学习(4)--编译与预编译
- 【Android您问我讲】如何使用选显卡 - Tabhost的使用
- 最全最好用的Android(安卓)Studio插件整理
- Android简明开发教程十八:自定义对话框 Transform