因为公司的APP需要向多平台发布,并且同一份APP源码会切换厂商发多种版本的APP。然而,虽然我有做厂商的区分文件,但每次源码切换厂商总是耗时,而且手动切换(进行文本覆盖),总有时候会有些漏的和错的。特别向AndroidManifest.xml文件,大部分的内容是相同的,但因为有接入第三方的sdk,有些差异的地方需要手动改,多版本维护,会存在错漏。
为此,为了减轻工作量,特意做了个快速替换厂商文件,及部署APK的脚本。
先说下我的思路,gradle命令中
gradle assembleDebug //打包Debug包
gradle assembleRelease //打包Release包
可以进行APK的打包,当然需要部署gradle的环境。
gradle的命令需要jdk8的版本进行支持。
我当前的gradle版本是gradle-3.3
http://download.csdn.net/download/feiniyan4944/10178513
然后通过jarsigner命令可以对APK进行签名
jarsigner -digestalg SHA1 -sigalg MD5withRSA -keystore 签名文件 -storepass
密码 -signedjar 打包后的APK路径 未打包的APK路径 别名
又因为上面的命令都是在cmd跑的,然后脚本我选择了bat脚本处理。
剩下的就是对存在差异数据厂商的文件进行处理。但用bat脚本我不会处理这么大量的数据,因为我是做java开发的,于是选择java进行处理了。
先附上效果图一张
好了附上我的bat源码
appbuild.bat
:: 不显示后续命令行及当前命令行
@echo off
::选择厂商
set platform=0
::获取当前脚本的路径
set bulidPath=%cd%
::取得上一级工程目录
cd..
set iremotePath=%cd%
::差异厂商的apk存放路径
set folderName=jwzh
:: 版本号
set version=
goto ChoosePlatform
:ChoosePlatform
@echo *******************************
@echo.
@echo 版本打包
@echo.
@echo *******************************
@echo.
@echo.
@echo 请选择厂家版本
@echo 1.百度 baidu
@echo 2.谷歌 google
:: /c按键列表 /m提示内容 /d默认选择 /t等待秒数 /d 必须和 /t同时出现
choice /c:123 /m:"" /d:1 /t:20
if %errorlevel%==1 goto baidu
if %errorlevel%==2 goto google
:baidu
set platform=1
set folderName=baidu
@echo 百度版本
goto platformReplace
:google
set platform=2
set folderName=google
@echo 谷歌版本
goto platformReplace
::java代码厂商文件替换
:platformReplace
@echo 正在编译厂商处理文件...
cd %bulidPath%\java
javac -cp AXmlResourceParser.jar;jdom-2.0.2.jar PlatformChange.java
@echo 正在进行厂商文件处理...
@echo.
java -cp AXmlResourceParser.jar;jdom-2.0.2.jar; PlatformChange %platform% %iremotePath%
@echo.
::获取配置文件的版本号,这里的配置文件不是apk本身的文件,是做的厂商数据存储文件
::开启变量延迟
setlocal enabledelayedexpansion
@echo 读取版本号
set platformPath=%iremotePath%\app\platformres\%folderName%\platform.xml
@echo %platformPath%
for /f "delims=" %%i in (%platformPath%) do (
echo "%%i"|findstr "version" >nul
if !errorlevel! equ 0 (
set "version=%%i"
goto version_goto
)
)
goto assemble
:version_goto
::关闭变量延迟
setlocal disabledelayedexpansion
for /f tokens^=2^ delims^=^" %%a in ("%version%") do set "version=%%a"
echo 当前版本号为:%version%
@echo.
:assemble
::打包前清空其文件夹下所有文件,防止打包不成功签名成旧版本的apk
del /q /s %iremotePath%\app\build\outputs\apk
set assemble=
cd %iremotePath%
@echo 请选择打包类型
@echo 1.Debug
@echo 2.Release
@echo 3.取消打包
choice /c 123 /m "" /d 2 /t 20
if %errorlevel%==1 goto debug
if %errorlevel%==2 goto release
if %errorlevel%==3 goto end
::gradle需要jdk8支持
:debug
@echo.
set assemble=1
@echo 开始进行debug打包
CALL %bulidPath%\assemble.bat
md %bulidPath%\apk\%folderName%
copy %iremotePath%\app\build\outputs\apk\debug\app-debug.apk %bulidPath%\apk\%folderName%\app_Debug_%folderName%_%version%.apk
@echo.
@echo debug版本app_Debug_%folderName%_%version%.apk打包完毕
goto end
:release
@echo.
set assemble=2
@echo 开始进行release打包
CALL %bulidPath%\assemble.bat
@echo.
@echo release版本打包完毕
goto key
::签名
:key
@echo.
md %bulidPath%\apk\%folderName%
::签名后的apk地址
set apkname=%bulidPath%\apk\%folderName%\app_%folderName%_%version%.apk
::未签名APK路径
set apkSigned=%iremotePath%\app\build\outputs\release\apk\app-release-unsigned.apk
::密钥
set key=%iremotePath%\app\AndroidappKey.jks
::密码
set password=123456
::别名
set keystore=app
echo 签名信息
echo 签名后的apk地址:%apkname%
echo 未签名APK路径:%apkSigned%
echo 密钥文件地址:%key%
echo 密钥密码:%password%
echo 别名:%keystore%
echo.
echo 正在进行apk签名...
::签名
::jarsigner打包需要jdk7支持
::jarsigner -digestalg SHA1 -sigalg MD5withRSA -keystore [你的keystore] -storepass [keystore的密码] -signedjar [签名后的apk] [未签名的apk] [keystore的别名]
jarsigner -digestalg SHA1 -sigalg MD5withRSA -keystore %key% -storepass %password% -signedjar %apkname% %apkSigned% %keystore%
echo.
echo 签名完成
goto end
:end
echo.
if %platform%==1 echo 百度版本打包完毕
if %platform%==2 echo 谷歌版本打包完毕
echo.
pause
:: 冒号后紧跟一个非字母数字的一个特殊符号,goto无法识别的标号,可以起到注释作用
:: UTF-8格式会出现英文乱码,要ANSI 格式
assemble.bat
@echo off
if "%assemble%"=="1" goto debug
if "%assemble%"=="2" goto release
:debug
gradle assembleDebug
:release
gradle assembleRelease
处理厂商文件的java源码
PlatformChange.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.List;
import java.util.Scanner;
import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.xml.sax.InputSource;
public class PlatformChange {
static String platform;//厂商
static String iremotePath;//工程地址
static String version;//版本
static String platforPackage;//厂商包名
static String jpushkey;//推送的appkey
static String folderName;
public static void main(String[] args) {
platform = args[0];//厂商
iremotePath = args[1];//工程地址
System.out.println("厂商" + platform);
System.out.println("工程地址:" + iremotePath);
System.out.println("读取版本信息文件");
readPlatformXml();
System.out.println("处理AndroidManifest版本信息");
changeAndroidManifest();
String oldPath = iremotePath + "\\app\\platformres\\" + folderName + "\\res";
String newPath = iremotePath + "\\app\\src\\main\\res";
System.out.println("厂家文件夹替换");
copyFolder(oldPath,newPath);
//修改build.gradle包名
System.out.println("处理build.gradle包名");
changeBuild();
}
/**
* 读取版本信息
*/
public static void readPlatformXml(){
if("1".equals(platform)){
folderName = "baidu";
}else if("2".equals(platform)){
folderName = "google";
}
boolean isChange = false;
String platformXmlPath = iremotePath + "\\app\\platformres\\" + folderName + "\\platform.xml";//厂商文件
SAXBuilder builder = new SAXBuilder();
Document document = null;
try{
InputStreamReader isr = new InputStreamReader(new FileInputStream(platformXmlPath), "UTF-8");
InputSource is = new InputSource(isr);
document = builder.build(is);
}catch (Exception e) {
e.printStackTrace();
}
Scanner scanner = new Scanner(System.in);
Element data = document.getRootElement();//根节点-->manifest
Attribute platforPackageAttr = data.getAttribute("package");
Attribute versionAttr = data.getAttribute("version");
if(platforPackageAttr == null){
System.out.println("厂商" + folderName + "包名为空,请输入包名");
platforPackage = scanner.next();
data.setAttribute("package",platforPackage);
isChange = true;
}else{
platforPackage = platforPackageAttr.getValue();
}
if(versionAttr == null){
System.out.println("厂商" + folderName + "版本号为空,请输入版本号,格式:(1.0.00)");
version = scanner.next();
data.setAttribute("version",version);
isChange = true;
}else{
version = versionAttr.getValue();
System.out.println();
System.out.println("当前app版本的版本号:" + version + ",是否需要修改,y为修改,任意键不修改");
String str = scanner.next();
if("y".equalsIgnoreCase(str)){
while (true) {
System.out.println("请输入新的版本号,格式:(1.0.00)");
String versionStr = scanner.next();
if(versionStr.matches("[0-9].[0].[0-9]{2}")){
version = versionStr;
data.setAttribute("version",version);
isChange = true;
break;
}else if("n".equalsIgnoreCase(versionStr)){
break;
}
System.out.println("格式有误,请重新输入,输入n退出");
}
isChange = true;
}
}
if(isChange){
try {
System.out.println("readPlatformXml:" + platformXmlPath);
doc2XML(document,platformXmlPath);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void changeAndroidManifest(){
String AndroidManifestPath = iremotePath + "\\app\\src\\main\\AndroidManifest.xml";//AndroidManifest文件地址
SAXBuilder builder = new SAXBuilder();
Document document = null;
try{
InputStreamReader isr = new InputStreamReader(new FileInputStream(AndroidManifestPath), "UTF-8");
InputSource is = new InputSource(isr);
document = builder.build(is);
}catch (Exception e) {
e.printStackTrace();
}
Element manifestElem = document.getRootElement();//根节点-->manifest
manifestElem.setAttribute("android:versionName",version);//APP版本
manifestElem.setAttribute("android:versionCode",version.replace(".", ""));//APP版本
Element permissionElem = manifestElem.getChild("permission");
permissionElem.setAttribute("android:name",platforPackage + ".permission.JPUSH_MESSAGE");
List<Element> usesPermissions = manifestElem.getChildren("uses-permission");//子节点集合
for(Element elem : usesPermissions){
String nameEle = elem.getAttribute("android:name").getValue();
if(nameEle.indexOf(".permission.JPUSH_MESSAGE") > 0){
elem.setAttribute("android:name",platforPackage + ".permission.JPUSH_MESSAGE");
}
}
try {
doc2XML(document,AndroidManifestPath);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* DOCUMENT输出XML
*
* @param doc JDOM
* @param filePath 输出的地址
* @throws Exception
*/
public static void doc2XML(Document doc, String filePath) throws Exception{
Format format = Format.getCompactFormat();
format.setEncoding("UTF-8"); //字符编码UTF-8
format.setIndent(" ");//setIndent是设置分隔附的意思,一般都是用空格,就是当你新节点后,自动换行并缩进,有层次感,如果这样写setIndent(""),就只有换行功能,而不会缩进了,如果写成setIndent(null),这样就即不换行也不缩进,全部以一行显示了,默认的就是这样的效果,不好看。
format.setLineSeparator("\n");
format.setEncodingOut(true);
format.setAttributeLineSeparator(true);
format.setAttributeSeparatorStr("\n");
XMLOutputter outputter = new XMLOutputter(format);
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(filePath), "UTF-8");
outputter.output(doc, writer);
writer.close();
}
private static void changeBuild(){
String buildName = iremotePath + "\\app\\build.gradle";//build.gradle文件地址
try {
// read file content from file
StringBuffer content= new StringBuffer("");
InputStreamReader isr = new InputStreamReader(new FileInputStream(buildName), "UTF-8");
BufferedReader br = new BufferedReader(isr);
String str = null;
while((str = br.readLine()) != null) {
if (str.trim().startsWith("applicationId")) {
content.append(" applicationId \"" + platforPackage + "\"");
}else{
content.append(str);
}
content.append("\r\n");
}
br.close();
isr.close();
Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(buildName), "UTF-8"));
writer.write(content.toString());
writer.close();
}
catch(FileNotFoundException e) {
e.printStackTrace();
}
catch(IOException e) {
e.printStackTrace();
}
}
/**
* 复制整个文件夹内容
* @param oldPath String 原文件路径 如:c:/fqf
* @param newPath String 复制后路径 如:f:/fqf/ff
* @return boolean
*/
public static void copyFolder(String oldPath, String newPath) {
try {
(new File(newPath)).mkdirs(); //如果文件夹不存在 则建立新文件夹
File a=new File(oldPath);
String[] file=a.list();
File temp=null;
for (int i = 0; i < file.length; i++) {
if(oldPath.endsWith(File.separator)){
temp=new File(oldPath+file[i]);
}
else{
temp=new File(oldPath+File.separator+file[i]);
}
if(temp.isFile()){
FileInputStream input = new FileInputStream(temp);
FileOutputStream output = new FileOutputStream(newPath + "/" +
(temp.getName()).toString());
byte[] b = new byte[1024 * 5];
int len;
while ( (len = input.read(b)) != -1) {
output.write(b, 0, len);
}
output.flush();
output.close();
input.close();
}
if(temp.isDirectory()){//如果是子文件夹
copyFolder(oldPath+"/"+file[i],newPath+"/"+file[i]);
}
}
}
catch (Exception e) {
System.out.println("复制整个文件夹内容操作出错");
e.printStackTrace();
}
}
}
这里面对AndroidManifest.xml的处理我用的是jdom.jar的包。但该jar包写入”android:xxx“结构的节点内容无法修改只能新增。因此,我对jdom的源码进行修改。
jdom.jar源码及Demo地址在文章末尾。
特比说明下,gradle的环境要与项目环境一致,否则会打包apk失败。
jdom源码
http://download.csdn.net/download/feiniyan4944/10178610
Demo源码
↓↓↓↓
http://download.csdn.net/download/feiniyan4944/10178500