Android-用apktool实现多渠道自动打包

因为项目当中需要对apk的AndroidManifest.xml文件当中的meta-data中的数据进行更新跟替换,如果用其他方式打包的话非常麻烦,然后在网上找了一个教程实现一段代码就可以自动打包,简单而且粗暴。这个是原文:http://blog.csdn.net/h3c4lenovo/article/details/10007039。我做了一些修改跟补充。

需要用到的环境:jdk,sdk,还有apktool。因为我的电脑上已经有配置jdk跟sdk了。如果没有配置的话自行搜索。然后apktool这个工具的话可以可以点击这里下载

有了工具就可以开始写代码了,实现自动打包的原理是这样的:

    1.先得到apk文件(可以打签名包和未签名包,只要有apk就行)

    2.用apktool 解包  (java -jar apktool.jar d  xxx.apk),通过这个指令就会在apktool目录下生成一个apk同名的文件夹,其中文件夹里面就包括我们要修改的AndroidManifest.xml

    3.写代码去修改AndroidManifest.xml中对应Channel_Id的地方

    4.用apktool 打包 (java -jar apktool.jar b xxx.ap xxx_us.apk),通过这个指令会生成一个未签名的apk,注意,此指令需要依赖aapt,请在系统环境变量中引入aapt!

    5.用jdk的jarsigner工具给apk签名(指令有很多,我用的是jarsigner -digestalg SHA1 -sigalg MD5withRSA -verbose -keystore abc.keystore -signedjar xxx_s.apk xxx_us.apk abc.keystore -storepass)

  知道了这个步骤后我们先验证一下反编译,打包,签名这些会不会出现问题。如果没有出现问题再开始写代码去实现自动打包。

apktool工具解压后里面有三个文件:


反编译:把你需要反编译的apk文件跟这三个文件放在一起,然后运行cmd,进入当前这个文件夹,在控制台输入:java -jar apktool.jar d XXX.apk(xxx.apk你放入的apk文件名)然后会在你的当前文件夹下生成一个以apk的文件名。




打包:在控制台中输入java -jar apktool.jar b MyAndroidTest -o XXX.apk(打包后要命名的名称) -o表示新生成的apk文件放在当前文件夹。



签名:把签名的.keystore文件放到当前的文件夹当中,然后在控制台中输入jarsigner -digestalg SHA1 -sigalg MD5withRSA -tsa https://timestamp.geotrust.com/tsa -verbose -keystore XXX.keystore(你放入的.keystore文件) -signedjar XXXsign.apk(签名成功后apk文件名) XXX.apk(签名前的apk名称) XXX.keystore(你放入的.keystore文件) -storepass XXX(你放入的.keystore对应的密码)



这三个步骤如果都能顺利完成的话那就没有什么问题了。就可以写代码来完成这些反编译,打包还有签名的这些操作了。

打包的代码如下:
public class AutoPack {

	public static void main(String[] args) {
		System.out.println("====**====By H3c=====**======");  
		  
        if (args.length != 3) {// 传入3个参数 apk报名、签名文件、签名密码  
            System.out  
                    .println("==ERROR==usage:java -jar rePack.jar apkName keyFile keyPasswd======");  
            System.out  
                    .println("==INFO==Example: java -jar rePack.jar test.apk android.keystore 123456======");  
            return;  
        }  
  
        String apk = args[0];
        String keyFile = args[1];  
        String keyPasswd = args[2];  
  
        System.out.println("apk名称:"+apk);
        SplitApk sp = new SplitApk(apk, keyFile, keyPasswd);  
        sp.mySplit();  
	}
}

SplitApk.java文件如下:
import java.io.BufferedReader;  
import java.io.File;  
import java.io.FileReader;  
import java.io.FileWriter;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.util.ArrayList;

public class SplitApk {  
	ArrayList<String> channel = new ArrayList<String>();	//渠道号
	String curPath;// 当前文件夹路径  
	String apkName;  //包名
	String keyFile;  //签名文件
	String keyPasswd;  //签名文件密码

	public SplitApk(String apkName, String keyFile, String keyPasswd) {// 构造函数接受参数  
		this.curPath = new File("").getAbsolutePath();  
		this.apkName = apkName;  
		this.keyFile = keyFile;  
		this.keyPasswd = keyPasswd;  
	}  

	public void mySplit() {  
		getCannelFile();// 获得自定义的渠道号  
		modifyXudao();// 解包 - 打包 - 签名  
	}  

	/** 
	 * 获得渠道号 
	 */  
	private void getCannelFile() {  
		File f = new File("channel.txt");// 读取当前文件夹下的channel.txt  
		if (f.exists() && f.isFile()) {  
			BufferedReader br = null;  
			FileReader fr = null;  
			try {  
				fr = new FileReader(f);  
				br = new BufferedReader(fr);  
				String line = null;  
				while ((line = br.readLine()) != null) {  
					String[] array = line.split("\t");// 这里是Tab分割  
					channel.add(array[0]);
				}  
			} catch (Exception e) {  
				e.printStackTrace();  
			} finally {  
				try {  
					if (fr != null) {  
						fr.close();  
					}  
					if (br != null) {  
						br.close();  
					}  
				} catch (IOException e) {  
					e.printStackTrace();  
				}  
			}  
			System.out.println("==INFO 1.==获取渠道成功,一共有" + channel.size()  
					+ "个渠道======");  
		} else {  
			System.out.println("==ERROR==channel.txt文件不存在,请添加渠道文件======");  
		}  
	}  

	/** 
	 * apktool解压apk,替换渠道值 
	 *  
	 * @throws Exception 
	 */  
	private void modifyXudao() {  
		// 解压 /C 执行字符串指定的命令然后终断  
		String cmdUnpack = "cmd.exe /C java -jar apktool.jar d -f -s "  
				+ apkName;  
		runCmd(cmdUnpack);  		//执行指令 cmd指令
		System.out.println("==INFO 2.==解压apk成功,准备移动======");  

		// 备份AndroidManifest.xml  
		// 获取解压的apk文件名  
		String[] apkFilePath = apkName.split("\\\\");  
		String shortApkName = apkFilePath[apkFilePath.length - 1];  
		System.out.println("shortApkName = "+shortApkName);
		String dir = shortApkName.split(".apk")[0];  
		System.err.println("dir = "+dir);
		File packDir = new File(dir);	//获得解压的apk目录  

		String f_mani = packDir.getAbsolutePath() + "\\AndroidManifest.xml";  
		String f_mani_bak = curPath + "\\AndroidManifest.xml";  
		//在当前文件夹下新建一个AndroidManifest.xml文件并把解压的apk文件里面的AndroidManifest.xml文件内容复制进来
		File manifest = new File(f_mani);  
		File manifest_bak = new File(f_mani_bak);  
		// 拷贝文件 -- 此方法慎用,详见http://xiaoych.iteye.com/blog/149328  
		manifest.renameTo(manifest_bak); 

		for (int i = 0; i < 10; i++) {  //当文件还没有创建成功的时候暂停等待
			if (manifest_bak.exists()) {  
				break;  
			}  
			try {  
				Thread.sleep(1000);  
			} catch (InterruptedException e) {  
				e.printStackTrace();  
			}  
		}  

		if (manifest_bak.exists()) {  
			System.out.println("==INFO 3.==移动文件成功======");  
		} else {  
			System.out.println("==ERROR==移动文件失败======");  
		}  

		// 创建生成结果的目录  
		File f = new File("apk");  
		if (!f.exists()) {  
			f.mkdir();  
		}  

		/* 
		 * 遍历map,复制manifese进来,修改后打包,签名,存储在对应文件夹中 
		 */  
		for (int i = 0; i < channel.size(); i++) {  
			String id = channel.get(i);  
			System.out.println("==INFO 4.1. == 正在生成包: " + id  
					+ " ======");  
			BufferedReader br = null;  
			FileReader fr = null;  
			FileWriter fw = null;  
			try {  
				fr = new FileReader(manifest_bak);  
				br = new BufferedReader(fr);  
				String line = null;  
				StringBuffer sb = new StringBuffer();  
				while ((line = br.readLine()) != null) {  //修改AndroidManifest.xml的meta-data字段
					if (line.contains("deacon_id")) {  
						line = line.replaceAll("deacon_id", id);  
						System.out.println("替换为渠道号"+id+"成功");
					}  
					sb.append(line + "\n");  
				}  

				// 写回文件  
				fw = new FileWriter(f_mani);  
				fw.write(sb.toString());  
			} catch (Exception e) {  
				e.printStackTrace();  
			} finally {  
				try {  
					if (fr != null) {  
						fr.close();  
					}  
					if (br != null) {  
						br.close();  
					}  
					if (fw != null) {  
						fw.close();  
					}  
				} catch (IOException e) {  
					e.printStackTrace();  
				}  
			} 

			System.out.println("==INFO 4.2. == 准备打包: " + id  
					+ " ======");  

			// 打包 - 生成未签名的包  
			String unsignApk = id + "_" + dir + "_un.apk";  
			String cmdPack = String.format(  
					"cmd.exe /C java -jar apktool.jar b %s -o %s", dir, unsignApk);  
			runCmd(cmdPack);  

			System.out.println("==INFO 4.3. == 开始签名: " + id  
					+ " ======");  
			// 签名  
			String signApk = "./apk/" + id + "_" + dir + ".apk";  
			String cmdKey = String  
					.format("cmd.exe /C jarsigner -digestalg SHA1 -sigalg MD5withRSA -tsa https://timestamp.geotrust.com/tsa -verbose -keystore %s -signedjar %s %s %s -storepass  %s",  
							keyFile, signApk, unsignApk, keyFile, keyPasswd);  
			runCmd(cmdKey);  
			System.out.println("==INFO 4.4. == 签名成功: " + id  
					+ " ======");  
			// 删除未签名的包  
			File unApk = new File(unsignApk);  
			unApk.delete();  
		}  

		//删除中途文件  
		String cmdKey = String.format("cmd.exe /C rd /s/q %s", dir);  
		runCmd(cmdKey);  
		manifest_bak.delete();  

		System.out.println("==INFO 5 == 完成 ======");  
	}  

	/** 
	 * 执行指令 cmd指令
	 *  
	 * @param cmd 指令内容
	 */  
	public void runCmd(String cmd) {  
		Runtime rt = Runtime.getRuntime();  
		BufferedReader br = null;  
		InputStreamReader isr = null;  
		try {  
			Process p = rt.exec(cmd);  
			// p.waitFor();  
			isr = new InputStreamReader(p.getInputStream());  
			br = new BufferedReader(isr);  
			String msg = null;  
			while ((msg = br.readLine()) != null) {  
				System.out.println(msg);  
			}  
		} catch (Exception e) {  
			e.printStackTrace();  
			System.out.println("执行cmd命令出错");
		} finally {  
			try {  
				if (isr != null) {  
					isr.close();  
				}  
				if (br != null) {  
					br.close();  
				}  
			} catch (IOException e) {  
				e.printStackTrace();  
			}  
		}  
	}  
}

1、然后生成jar包,右击工程选择菜单中的Export - Java - Runnable JAR file,选择导出路径后就可以输出jar了。
2、把输出的jar包也放到当前文件夹下,并解压打开jar中META-INF文件夹下的MANIFEST.MF文件,在这个MANIFEST.MF中增加入口函数,也就是你的main函数的类名。这个类名要写全路径。(蓝色的那一行是自己添加进去的)
3、在当前文件夹新建一个名为channel.txt的文件,在里面填上你需要打包的渠道号,然后用换行符隔开。 (这里面有1-5,5个渠道号)
完整的文件夹内容如下:

4、然后打开cmd进入到当前的文件夹当中,在控制台中输入:java -jar XXX.jar(你自己导出的jar包) XXX.apk(你要打包的apk) XXX.keystore(你的签名文件) XXX(签名文件的密码)结果如下:

打包成功的话会在当前文件夹中生成一个apk文件夹,打包成功的签名文件都会放在这个文件夹里面:

(看过上面的代码就知道,你可以修改获取渠道号的文件名,可以修改mate-data中的替换的字段,反正可以改成满足自己需求的样子)


然后为了可以更简单省事,可以写一个批处理文件,这样就点击一下批处理文件就可以自动实现打包。而且一行代码都不用写。。

批处理文件:1、在当前文件夹下新建一个txt文件,然在文件中添加如下代码:

@echo off  
set /p var=请拖入apk:  
java -jar AutoPack.jar %var% game.keystore 123456
  
echo.&echo 请按任意键退出...&pause>nul  
exit  
2、把txt文件的后缀名改成bat,然后在打包的时候只要替换文件当中的apk文件,再点击这个.bat文件。这个时候会出现一个控制台,你只要把你要打包的apk文件拖到控制台上然后确定,就可以帮你完成所有的操作了。结果如下:




  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值