Android加固在Android11上的问题

由于工作需求,近来要研究下apk加固。昨天在网上找了个加固代码,一顿操作下来,加固ok,在Android5和Android10上的机器上跑起来ok,到了Android11上安装出错。

错误信息如下:

adb: failed to install Xxxx.apk: Failure [-124: Failed parse during installPackageLI: Targeting R+ (version 30 and above) requires the resources.arsc of installed APKs to be stored uncompressed and aligned on a 4-byte boundary]

 看来是android11的行为变更,官方说明:

如果以 Android 11(API 级别 30)或更高版本为目标平台的应用包含压缩的 resources.arsc 文件或者如果此文件未按 4 字节边界对齐,应用将无法安装。如果存在其中任意一种情况,系统将无法对此文件进行内存映射。无法进行内存映射的资源表必须读入 RAM 中的缓冲区,从而给系统造成不必要的内存压力,并大大增加设备的 RAM 使用量。

原来在Android11上为了减少ram压力,需要resources.arsc不要压缩,并且4字节对齐。

本以为还要看源码鼓捣下,没想到文档已经说得很清楚了,还是要多看文档啊!接下来就简单了,按照以下方式打包签名即可:

1.打包APK时,不要对resources.arsc进行压缩

2.将上一步打包好的apk做V1签名

3.将V1签名的apk做zipalign四字节对齐

4.对齐后的apk进行V2签名

上代码,需要的自取,记得把zipalign、apksigner(SDK下的build-tools/30.0.2)添加到环境变量。

// 打包
System.out.println("打包APK");
File unsignedApk = new File("output/unsigned.apk");
ZipUtil.zip(apkUnzipDir, unsignedApk);
			
// 删除解压目录
FileUtils.delete("output/unzip/");
			
// 签名
System.out.println("签名APK");
File signedApk = new File("output/signed.apk");
SignUtils.apkSignature(unsignedApk, signedApk, "keystore/test.jks", "test123", "test", "test123");
System.out.println("Finished!!!");
public class ZipUtil {

	public static void unZip(File zip, File dir) {
		try {
			dir.delete();
			ZipFile zipFile = new ZipFile(zip);
			Enumeration<? extends ZipEntry> entries = zipFile.entries();
			while (entries.hasMoreElements()) {
				ZipEntry zipEntry = entries.nextElement();
				String name = zipEntry.getName();
				if (name.equals("META-INF/CERT.RSA") || name.equals("META-INF/CERT.SF")
						|| name.equals("META-INF/MANIFEST.MF")) {
					continue;
				}
				if (!zipEntry.isDirectory()) {
					File file = new File(dir, name);
					if (!file.getParentFile().exists())
						file.getParentFile().mkdirs();
					FileOutputStream fos = new FileOutputStream(file);
					InputStream is = zipFile.getInputStream(zipEntry);
					byte[] buffer = new byte[1024];
					int len;
					while ((len = is.read(buffer)) != -1) {
						fos.write(buffer, 0, len);
					}
					is.close();
					fos.close();
				}
			}
			zipFile.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 打包apk
	 * @param dir apk解压后的文件夹路径
	 * @param zip 输出打包后的apk
	 * @throws Exception
	 */
	public static void zip(File dir, File zip) throws Exception {
		zip.delete();
		CheckedOutputStream cos = new CheckedOutputStream(new FileOutputStream(zip), new CRC32());
		ZipOutputStream zos = new ZipOutputStream(cos);
		compress(dir, zos, "");
		zos.flush();
		zos.close();
	}

	private static void compress(File srcFile, ZipOutputStream zos, String basePath) throws Exception {
		if (srcFile.isDirectory()) {
			compressDir(srcFile, zos, basePath);
		} else {
			compressFile(srcFile, zos, basePath);
		}
	}

	private static void compressDir(File dir, ZipOutputStream zos, String basePath) throws Exception {
		File[] files = dir.listFiles();
		if (files.length < 1) {
			ZipEntry entry = new ZipEntry(basePath + dir.getName() + "/");
			zos.putNextEntry(entry);
			zos.closeEntry();
		}
		for (File file : files) {
			compress(file, zos, basePath + dir.getName() + "/");
		}
	}

	private static void compressFile(File file, ZipOutputStream zos, String dir) throws Exception {

		String dirName = dir + file.getName();

		String[] dirNameNew = dirName.split("/");

		StringBuffer buffer = new StringBuffer();

		if (dirNameNew.length > 1) {
			for (int i = 1; i < dirNameNew.length; i++) {
				buffer.append("/");
				buffer.append(dirNameNew[i]);

			}
		} else {
			buffer.append("/");
		}

		ZipEntry entry = new ZipEntry(buffer.toString().substring(1));
		if ("resources.arsc".equals(file.getName())) {
			entry.setMethod(ZipEntry.STORED);
			entry.setSize(file.length());
			entry.setCrc(calFileCRC32(file));
		}
		zos.putNextEntry(entry);
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
		int count;
		byte data[] = new byte[1024];
		while ((count = bis.read(data, 0, 1024)) != -1) {
			zos.write(data, 0, count);
		}
		bis.close();
		zos.closeEntry();
	}
	
	private static long calFileCRC32(File file) throws IOException {
		FileInputStream fi = new FileInputStream(file);
		CheckedInputStream checksum = new CheckedInputStream(fi, new CRC32());
		while (checksum.read() != -1) { }
		long temp = checksum.getChecksum().getValue();
		fi.close();
		checksum.close();
		return temp;
	}
}
public class SignUtils {

	private SignUtils() {
		throw new UnsupportedOperationException("u can't instantiate me...");
	}
	
	private static void exec(String[] cmd, String execName) throws IOException, InterruptedException {
		Process process = Runtime.getRuntime().exec(cmd);
		System.out.println("start " + execName);
		try {
			process.waitFor();
		} catch (InterruptedException e) {
			e.printStackTrace();
			throw e;
		}
		if (process.exitValue() != 0) {
			InputStream inputStream = process.getErrorStream();
			int len;
			byte[] buffer = new byte[2048];
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			while ((len = inputStream.read(buffer)) != -1) {
				bos.write(buffer, 0, len);
			}
			System.out.println(new String(bos.toByteArray(), "gbk"));
			throw new RuntimeException(execName + " execute fail");
		}
		System.out.println("finish " + execName);
		process.destroy();
	}

	/**
	 * V1签名
	 */
	private static String signature(File unsignedApk, String keyStore, String keyPwd, String alias, String alisaPwd)
			throws InterruptedException, IOException {
		String path = unsignedApk.getAbsolutePath();
		String v1Name = path.substring(0, path.indexOf(".apk")) + "_v1.apk";
		String cmd[] = { "cmd.exe", "/C ", "jarsigner", "-sigalg", "SHA1withRSA", "-digestalg", "SHA1", "-keystore",
				keyStore, "-storepass", keyPwd, "-keypass", alisaPwd, "-signedjar", v1Name,
				unsignedApk.getAbsolutePath(), alias };
		
		exec(cmd, "v1 sign");
		
		FileUtils.delete(path);
		
		return v1Name;
	}
	
	// zipalign -p 4 input\app-release-unsigned.apk input\app-release-unsigned.apk
	private static String apkZipalign(String v1Apk) throws IOException, InterruptedException {
		String zipalignName = v1Apk.substring(0, v1Apk.indexOf(".apk")) + "_align.apk";
		String cmd[] = {"cmd.exe", "/C ", "zipalign", "-p", "4", v1Apk, zipalignName};
		
		exec(cmd, "zipalign");
		
		FileUtils.delete(v1Apk);
		
		return zipalignName;
	}

	//apksigner.jar sign  --ks key.jks --ks-key-alias releasekey  --ks-pass pass:pp123456  --key-pass pass:pp123456  --out output.apk  input.apk    
	public static void apkSignature(File unsignedApk, File signedApk, String keyStore, String keyPwd, String alias, String alisaPwd) throws IOException, InterruptedException {
		String v1Name = signature(unsignedApk, keyStore, keyPwd, alias, alisaPwd);
		String zipalignName = apkZipalign(v1Name);
		String cmd[] = { "cmd.exe", "/C ", "apksigner", "sign", "--ks", keyStore, "--ks-pass", "pass:" + keyPwd,
				"--ks-key-alias", alias, "--key-pass", "pass:" + alisaPwd,
				"--out", signedApk.getAbsolutePath(), zipalignName };
		
		exec(cmd, "v2 sign");
		
		FileUtils.delete(zipalignName);
		FileUtils.delete(signedApk.getAbsolutePath() + ".idsig");
	}
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值