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 "/";
}
写这个主要是为了做笔记,出了啥问题别问候我哈。