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

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

  • 1
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值