最近公司需要做Android自动化脚本打包的操作,随手写了个demo,已支持功能替换logo文件并自动生成包。
前言:最早以前大概是在14年左右,当时也有做过自动打包的脚本,当时采用的是ant来进行脚本编写,ant采用的xml格式,自我感觉,编写起来麻烦一些,但是可读性比较强。当时采用的eclipse开发,需要自己去设定源代码目录地址,资源文件地址等,脚本代码也很容易理解。后面转战到AndroidStudio(亲儿子)开发后,项目使用gradle进行编译,后面接触ant来打包就变得少了。
gradle确实很方便,脚本开发起来代码量也少很多,但是groovy这个以前没接触过,从代码可读性上来说确实感觉要逊色于以XML编写的ANT,但是熟悉了之后又会发现脚本编写起来很简洁优雅,有点像以前学语文的时候文言文和白话文之间的差别,哈哈,好了废话不多说了,下面进入正题。
一.开发环境:
因为是用python写的,所以只要支持python,都可以运行。笔者开发过程中因为自己本身这里是windows环境,所以最初是在windows上开发测试,但是在实际过程中可能这种需求更多的出现在服务器打包的情境下,所以后面也在linux系统中测试通过。
二.实现原理:
1.在开发过程中,标准化的Android原生开发中,我们的logo图片文件会存放在mipmap文件夹中(早起是放在drawable中),logo图片的配置是写到了AndroidManifest.xml文件中通过application节点中的android:icon和android:roundIcon(这个不一定设置了)来进行配置,所以我们要做的事情就是遍历app/src/main下的android清单文件(这里多一句,如果是其他moudle请修改app为moulle名称,若在构建文件中特殊配置了清单文件的地址,请修改目录,这里只举例通用的一种),找到我们logo的名称,logo所在的文件夹。
2.准备好不同分辨率的logo图片文件,并顺着文件夹和logo名称替换掉res文件中的logo文件,然后通过工程目录中的gradlew脚本进行打包(本文不涉及android构建脚本中的脚本配置)
三.代码实现
测试代码处设定的源码为一个压缩文件,利用python 的zipfile模块进行解压文件,下面是代码文件android.py具体内容:
import zipfile #导入文件解压模块
import os,sys #导入os模块
import xml.dom.minidom #导入XML相关模块
from xml.dom.minidom import parse #导入XML相关模块
import shutil #导入文件处理模块
iconAttrName = "android:icon"#android的icon
roundIconAttrName = "android:roundIcon"#高版本上会有一个圆形的icon
outPath = sys.path[0]"/packUtil/"#解压目录
zipFileName = "yuanyuan.zip"#源文件的压缩文件
fileName = "yuanyuan"#解压后的文件目录
manifestPath = "/app/src/main/AndroidManifest.xml"#android配置文件目录
resPath = "/app/src/main/res/"#资源文件的目录
replaceLogoName = "ic_launcher.png"#替换的logo名称
replaceRoundLogoName = "ic_launcher_round.png"#替换的圆形logo名称
androidManifestTag = "application"#Android配置清单文件
cmd_gen_apk = "gradlew assembleRelease"#生成android包的命令,生成目录/app/build/outputs/apk/release/app-release.apk,
#找到application里面的根节点,获取到icon和roundicon的对应的图片名称
def parseXmlAndReplaceLogo(xmlPath):
domTree = parse(xmlPath)
rootNode = domTree.documentElement
applicationInfo = rootNode.getElementsByTagName(androidManifestTag)
app = applicationInfo[0]
if app.hasAttribute(iconAttrName):
print(app.getAttribute(iconAttrName))
replaceIcon(app.getAttribute(iconAttrName),1)
if app.hasAttribute(roundIconAttrName):
print(app.getAttribute(roundIconAttrName))
replaceIcon(app.getAttribute(roundIconAttrName),2)
#判断图片的位置,一般是在mipmap中,但是不排除部分开发者的不规范,放在drawable下面
#获取到路径和图片名称
def replaceIcon(dirAndName,type):
if "drawable" in dirAndName:
replaceMipMap(dirAndName.split("/")[1],type,"drawable")
elif "mipmap" in dirAndName:
replaceMipMap(dirAndName.split("/")[1],type,"mipmap")
else:
print("未找到相对应的图片")
#开始替换图片,logo文件只支持png
def replaceMipMap(logoFileName,type,dirstart):
print("开始替换文件..."+logoFileName)
resAllPath = outPath+fileName+resPath
resDirs = os.listdir(resAllPath)
newFileName = replaceLogoName
if type==2:
newFileName = replaceRoundLogoName
for f in resDirs:
if(f.startswith(dirstart)):
print("获取到文件目录:"+f)
print("移除文件:"+resAllPath+f+"/"+logoFileName+".png")
logoFilePath = resAllPath+f+"/"+logoFileName+".png"
if(os.path.exists(logoFilePath)):
os.remove(logoFilePath)
else:
continue
root = os.getcwd()
if(f.endswith("hdpi")):
shutil.copy(root+"/icon/icon-hdpi/"+newFileName,resAllPath+f+"/"+logoFileName+".png")
elif(f.endswith("ldpi")):
shutil.copy(root+"/icon/icon-ldpi/"+newFileName,resAllPath+f+"/"+logoFileName+".png")
elif(f.endswith("mdpi")):
shutil.copy(root+"/icon/icon-mdpi/"+newFileName,resAllPath+f+"/"+logoFileName+".png")
elif(f.endswith("xhdpi")):
shutil.copy(root+"/icon/icon-xhdpi/"+newFileName,resAllPath+f+"/"+logoFileName+".png")
elif(f.endswith("xxhdpi")):
shutil.copy(root+"/icon/icon-xxhdpi/"+newFileName,resAllPath+f+"/"+logoFileName+".png")
elif(f.endswith("xxxhdpi")):
shutil.copy(root+"/icon/icon-xxxhdpi/"+newFileName,resAllPath+f+"/"+logoFileName+".png")
else:
print("未找到对应尺寸的图片替换")
print("替换完成。。。")
#检查java环境配置
def configJavaEnv():
print("检查java环境")
conDomTree = parse("config.xml")
rootNode = conDomTree.documentElement
javaPath = rootNode.getElementsByTagName("javaPath")[0].childNodes[0].data
print("找到java环境位置:"+javaPath)
return javaPath
#写入环境配置
def writeJavaEnvToProperties(value):
file = open("gradle.properties",mode="r+")
testline = "org.gradle.java.home="+value
isConfig = False
for line in file:
if(testline==line.strip()):
isConfig = True
break;
if(not isConfig):
file.writelines("\n"+testline)
file.flush()
file.close()
print("已写入配置文件")
#改变工作目录到android Project目录
def changeDir(workPath):
os.chdir(workPath)
#生成安装包
def genReleaseApk():
os.system(cmd_gen_apk)
print("开始解压压缩包文件...")
file = zipfile.ZipFile(zipFileName,"r")
file.extractall(path=outPath)
file.close()
print("文件解压完毕,查找配置文件AndroidManifest文件...")
xmlFilePath = outPath+fileName+manifestPath
parseXmlAndReplaceLogo(xmlFilePath)#解析xml并替换图标
#配置javahome
gradleJdkPath = configJavaEnv()
print("切换工作目录...")
workPath = sys.path[0]+"/"+fileName
changeDir(workPath)
print("准备写入配置文件...")
writeJavaEnvToProperties(gradleJdkPath)
#生成安装包
genReleaseApk()
最后通过python android.py执行命令即可自动生成安装包啦。
值得注意的几个地方:
1.注册demo中使用的是python3,jdk1.8
2.代码中为了方便测试目录我统一在一个目录,如果有路径问题,请自行调整
3.此处仅为demo参考,实际工作中还需要去做一些异常的处理,如文件路径判断,环境检查等等
4.windows中若遇到以下错误
Execution failed for task ':app:compileReleaseKotlin'.
> Kotlin could not find the required JDK tools in the Java installation 'D:\Program Files\java\jvm' used by Gradle. Make sure Gradle is running on a JDK, not JRE.
解决方式:修改工程目录下gradle.properties,在末尾换行添加org.gradle.java.home=D:\\Program Files\\Java,手动指定以下java路径(上述代码中自动添加了这块内容,无需手动设置)
5.linux中在使用openjdk1.8的时候没有编译成功,后笔者重新下载了jdk1.8linux版本。编译成功
6.实际应用中可自行扩展,将一些反复的工作拿出来做一些自动化的操作,是提高我们工作效率的一种明显改善
7.若有问题欢迎沟通,以前看得比较多写的比较少,在写的过程中,发现其实是加深了自己对这部分知识的一个理解,同时也可能自己会有问题的地方被其他人发现,能做一个改进。当我们在学习的过程中,能够通过文字表达出来,说明我们可能掌握了这部分知识,如果能够通过一些更加巧妙的语言讲述与他人 ,说明我们对整个知识点达到了融汇贯通,以后会多多记录和写一些自己在工作中遇到的问题或者自己对一些技术点的一些理解与认识,做到温故而知新。
网友评论