Android 利用apktool工具实现apk二次打包功能的java小程序

前言

前面博文写了如何通过apktool工具实现apk二次打包,二次打包是实现了,但终究感觉一行一行的输入命令也是比较麻烦,当然我们可以使用bat脚本来简化操作。不过本人对脚本代码不太熟悉,对bat脚本的批处理和文件操作等也不太擅长,所以最终决定还是回归java,用我最擅长java代码来实现apk的二次打包,修改apk文件功能。

小程序(不是微信小程序)编写要点
  • 小程序实现二次打包目的是给apk添加或修改渠道号
  • 使用apktool工具进行拆包、打包
  • 小程序编写思路:拆包-添加渠道号-打包-签名

按照上面要点,下面来一一实现我们的小程序
实现小程序前,我们首先需要生成一个原始apk,这里我通过AS随便新建一个项目运行生成一个apk,这个apk要在运行后在Application获取里获取apk的渠道号,并打印出来。这里我的思路是在Application里获取assets目录里的渠道文件,而这个渠道文件是在实现二次打包后添加的,这样我们就可以通过apk母包然后用自己的小程序二次打包进而实现apk的多渠道打包。

先看我们apk的Application代码:

public class AppApplication extends Application {
    private static final String TAG = "AppApplication";
    private ApplicationInfo appInfo;
    private String channelName;
    private int channelNum;

    @Override
    public void onCreate() {
        super.onCreate();
        String channelInfo = getChannelInfo(this);
        Log.d("channelInfo",channelInfo);
    }

    private String getChannelInfo(Context context){
        try {
            InputStream in = context.getAssets().open("channel.txt");//获取渠道文件流
            int size = in.available();//获取文件内容大小
            byte[] buffer = new byte[size];
            in.read(buffer);
            in.close();
            String channelInfo = new String(buffer,"utf-8");
            String[] channelInfos = channelInfo.split("\n");
            for (String info :channelInfos){
                if (info.contains("channelName")){
                    channelName = info.substring("channelName=".length());
                    Log.d(TAG,"channelName:"+channelName);
                }else if (info.contains("channelNum")){
                    channelNum = Integer.parseInt(info.substring("channelNum=".length()));
                    Log.d(TAG,"channelNum:"+channelNum);
                }
            }
            return channelInfo;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "unKnow channel";
    }
}

接下来在Eclipse上正式编写我们的二次打包功能java小程序

准备工作
  1. 新建java项目
  2. 在项目根目录下创建apkDir目录存放原始apk以及渠道文件(channel.txt)
  3. 准备好apktool工具(需要设置apktool环境变量,这里不做详解)
  4. 准备好签名文件,将签名文件放到项目根目录(可查阅俺上篇博文)

接下来是具体代码
1.拆包

	/**
	 * 拆包apk
	 * @param apkPath apk所在目录
	 * @return 拆包成功,返回拆包路径,否则为null
	 */
	private static String unZipApkFile(String apkPath) {
		File apkPathFile = new File(apkPath);
		String childAPKPath = "";
		String unZipPath = null;//拆包所在目录
		if (!apkPathFile.exists()) {
			System.out.println(apkPath+":指定目录不存在");
			return null;
		}
		String[] childrenFile = apkPathFile.list();
		for (String childFile : childrenFile) {
			if (childFile.endsWith(".apk")) {
				System.out.println("apkFile:"+childFile);
				originApkName = childFile;
				childAPKPath = apkPath + childFile;
				unZipPath = apkPathFile.getParent()+"\\"+childFile.split("\\.")[0];
			}
		}
		String unZipApkCmd = "cmd.exe /c "+"apktool d "+childAPKPath;	//apktool拆包命令
		try {
            Process ps = Runtime.getRuntime().exec(unZipApkCmd);
//            int status = ps.waitFor();
//            System.out.println(status);
            InputStream in = ps.getInputStream();
             
            BufferedReader br = new BufferedReader(new InputStreamReader(in,"GBK"));
            String line = br.readLine();
            while(line!=null) {
                System.out.println(line);
                line = br.readLine();
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
		return unZipPath;
	}

2.给拆后的包添加渠道文件

/**
	 * 添加或修改渠道文件
	 * @param dirPath 原始apk拆包后目录
	 * @param channelFile 渠道路径
	 */
	private static boolean changeChannel(String dirPath,String channelFile) {
		File unZipFile = new File(dirPath);
		if (!unZipFile.exists()) {
			System.out.println("File path("+dirPath+")is not existed!!");
			return false;
		}
		if (unZipFile.isDirectory()) {
			File[] childFiles = unZipFile.listFiles();
			for(File childFile:childFiles) {//遍历拆包目录
				if (childFile.getName().equals("assets")) {
					System.out.println(childFile.getPath());
					File[] files = childFile.listFiles();
					for(File file:files) {//遍历assets目录
						if (file.getName().contains("channel.txt")) {
							file.delete();
							break;
						}
					}
					break;
				}
			}
			File assetFile = new File(unZipFile.getPath()+"\\"+"assets");
			if (!assetFile.exists()) {
				assetFile.mkdirs();
			}
			copyfile(assetFile.getAbsoluteFile(), channelFile);
		}
		return true;
	}
	/**
	 * 拷贝文件
	 * @param targetDir 拷贝到的目标目录
	 * @param channelFile 拷贝文件路径
	 */
	private static void copyfile(File targetDir,String channelFile) {
		// TODO Auto-generated method stub
		if (targetDir.exists() && targetDir.isDirectory()) {
			File channel = new File(channelFile);
			if (channel.exists()) {
				try {
					Files.copy(channel.toPath(), new FileOutputStream(new File(targetDir.getAbsolutePath()+"\\"+channel.getName())));
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

3.重新打包

/**
	 * 重新打包apk
	 * @param buildPath 拆包目录
	 * @return 返回重新打包的apk所在目录
	 */
	private static String reBuildApk(String buildPath) {
		File buildDir = new File(buildPath);
		if (!buildDir.exists()) {
			System.out.println(buildPath+":文件不存在");
			return null;
		}
		String buildApkCmd = "cmd.exe /c "+"apktool b "+buildPath;		//打包命令
		try {
            Process ps = Runtime.getRuntime().exec(buildApkCmd);
//            int status = ps.waitFor();
//            System.out.println(status);
            InputStream in = ps.getInputStream();
             
            BufferedReader br = new BufferedReader(new InputStreamReader(in,"GBK"));
            String line = br.readLine();
            while(line!=null) {
                System.out.println(line);
                line = br.readLine();
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
		return buildPath+"\\dist\\";
	}

4.给新包签名

	/**
	 * 执行签名命令,给apk签名
	 * @param signCMD
	 */
	private static void signApk(String apkPath) {
		//签名命令,无需再输入签名口令
		//注意,这里需对应各自的签名文件信息,比如,这里的keystore是签名文件名,123456是签名文件密码,keyalias是签名文件别名
		String signCMD ="jarsigner -verbose -keystore "+keystore+" -storepass 123456 -keypass 123456"+" -signedjar "+ apkPath+ originApkName.split("\\.")[0]+"_signed.apk" +" "+apkPath+originApkName +" "+keyalias;
		String cmd = "cmd.exe /c "+ signCMD;// pass
        try {
            Process ps = Runtime.getRuntime().exec(cmd);
//            int status = ps.waitFor();
//            System.out.println(status);
            InputStream in = ps.getInputStream();
             
            BufferedReader br = new BufferedReader(new InputStreamReader(in,"GBK"));
            String line = br.readLine();
            while(line!=null) {
                System.out.println(line);
                line = br.readLine();
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        System.out.println("child thread donn");
	}

5.最终main函数里调用

	private static final String PATH_ZIP_DIR = "E:\\AndroidProject\\PersonalProject\\ApkQuDao\\apkDir\\";//原始包路径
	private static final String PATH_CHANNEL_FILE = "E:\\AndroidProject\\PersonalProject\\ApkQuDao\\apkDir\\channel.txt";
	private static final String keystore = "mykey.keystore";
	private static final String keyalias = "mykeystore";
	private static String originApkName = ""; 
	public static void main(String[] args) {
		String unZipFilePath = unZipApkFile(PATH_ZIP_DIR);//apktool拆包
		if (null == unZipFilePath) return;
		System.out.println("拆包路径:"+ unZipFilePath);
		boolean changeResult = changeChannel(unZipFilePath, PATH_CHANNEL_FILE);
		if (changeResult) {
			String reBuildApkPath = reBuildApk(unZipFilePath);
			if (null == reBuildApkPath) return;
			
			System.out.println("新包路径:"+ reBuildApkPath);
			signApk(reBuildApkPath);
		}
	}

运行,执行小程序,然后安装我们二次打包后的apk,下面来安装这个apk验证(如果安装失败,可参考俺上篇博文查找原因)
先看渠道文件信息(channel.txt)
Android 利用apktool工具实现apk二次打包功能的java小程序_第1张图片
再点击运行apk打开AS查看log信息:
在这里插入图片描述
OK,成功添加并获取渠道信息,没有问题。

最后

我们这里实现了通过java小程序对apk进行二次功能,现实项目中我们可能需要对我们开发的apk进行多渠道打包或者其他相关需求,这是我们就可以利用我们的小程序进行快速二次打包以提高工作效率,举一反三,这里我只是实现了一个最简单实用的功能。

markdown真好玩,俺依然是个菜鸟

你可能感兴趣的:(android笔记)