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)
在这里插入图片描述
再点击运行apk打开AS查看log信息:
在这里插入图片描述
OK,成功添加并获取渠道信息,没有问题。

最后

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

markdown真好玩,俺依然是个菜鸟
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Android apktool是一款广泛使用的工具,用于反编译Android应用程序包(APK)。APK是一种Android应用程序的打包格式,包含了应用的所有代码、资源和配置信息。而反编译则是指将APK文件还原成可读性较高且易于理解的源代码形式。 使用apktool进行APK反编译时,首先需要将APK文件传递给apktool工具工具会根据APK文件的结构,解析并提取其中的所有文件和资源。然后,apktool会自动将其中的DEX文件(Dalvik Executable文件,包含了Java源代码经过编译后的字节码)转换为Java源码。此时,开发者可以通过查看反编译后的Java源码来了解应用的工作原理和实现细节。 同时,apktool还会还原APK中的XML文件,包括清单文件、布局文件以及其他配置文件。这些XML文件记录了应用的各种配置信息,如权限、活动(Activity)、服务(Service)等。反编译后的XML文件可以帮助开发者了解应用的结构和设计。 尽管apktool的反编译功能可以帮助开发者深入了解和分析应用,但需要注意的是,由于反编译是将编译后的字节码转化为源代码,所以反编译后的代码可能会存在格式混乱、变量命名不一致等问题。因此,反编译的源码仅供参考和学习使用,不能完全复原原始代码。 总而言之,android apktool是一款强大的工具,通过它可以帮助开发者反编译APK文件,提取其中的代码和资源,从而更好地分析应用和进行代码审查。 ### 回答2: Android apktool是一种常用的工具,用于反编译APK文件。反编译APK可以帮助开发者分析和修改应用程序,并研究其他应用程序的功能实现原理。 通过使用apktool工具,可以将APK文件解压成源码和资源文件。这样,开发者可以查看应用程序的源代码,理解其逻辑和结构,并根据需要进行修改。apktool还可以还原资源文件,比如图片、音频和布局文件,以供进一步分析和修改。 使用apktool反编译APK文件并不复杂。首先,需要在电脑上安装Java开发环境,然后下载并配置好apktool工具的环境变量。接下来,在命令行中运行apktool命令,指定要反编译的APK文件路径。反编译完成后,可以在指定的输出目录中找到反编译的源码和资源文件。 然而,需要注意的是,反编译APK只能得到大致的源码和资源文件,并不完全等同于开发者的原始代码。因为在编译APK之前,原始代码会经过一系列的编译和混淆过程,这些过程会对源码进行处理,使得反编译后的代码可读性较差。此外,反编译APK可能存在法律和道德问题,因此应该遵守相关法律规定,并尊重开发者的劳动成果。 综上所述,Android apktool是一种有用的工具,可以帮助开发者反编译APK文件,分析和修改应用程序。合法合规地使用该工具,可以加深对Android应用程序的理解,并提升开发能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值