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 "/";}

写这个主要是为了做笔记,出了啥问题别问候我哈。

更多相关文章

  1. Android(安卓)简单计算器源码....
  2. Android(安卓)之 MediaPlayer类
  3. [置顶] android按键功能的拓展
  4. android 1.界面背景的性能优化[
  5. Android学习之Adapter(适配器)源代码分析与观察者模式的运用(一)
  6. Android(安卓)JNI和NDK学习(4)--编译与预编译
  7. 【Android您问我讲】如何使用选显卡 - Tabhost的使用
  8. 最全最好用的Android(安卓)Studio插件整理
  9. Android简明开发教程十八:自定义对话框 Transform

随机推荐

  1. [ IOS-Cocos2d-x (Box2d)游戏开发之四] -C
  2. android handler介绍
  3. Android(安卓)Launcher2源码分析主布局文
  4. Android(安卓)获取cpu序列号
  5. ubuntu下载android源代码
  6. APK 的自我保护
  7. android Wifi自动连接
  8. 【Android】Android应用开发案例教程
  9. 解决TextView中MaxLines与ellipsize=end
  10. Android之——AIDL深入