(...);
82 | }
83 |
84 | #忽略警告
85 | -ignorewarnings
86 | #保证是独立的jar,没有任何项目引用,如果不写就会认为我们所有的代码是无用的,从而把所有的代码压缩掉,导出一个空的jar
87 | -dontshrink
88 | #保护泛型
89 | -keepattributes Signature
90 | -keep class com.bytedance.android.aabresguard.AabResGuardMain { *; }
91 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/AabResGuardMain.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard;
2 |
3 |
4 | import com.android.tools.build.bundletool.flags.FlagParser;
5 | import com.android.tools.build.bundletool.flags.ParsedFlags;
6 | import com.bytedance.android.aabresguard.commands.CommandHelp;
7 | import com.bytedance.android.aabresguard.commands.DuplicatedResourcesMergerCommand;
8 | import com.bytedance.android.aabresguard.commands.FileFilterCommand;
9 | import com.bytedance.android.aabresguard.commands.ObfuscateBundleCommand;
10 | import com.bytedance.android.aabresguard.commands.StringFilterCommand;
11 | import com.bytedance.android.aabresguard.model.version.AabResGuardVersion;
12 | import com.google.common.collect.ImmutableList;
13 |
14 | import java.util.Optional;
15 |
16 | /**
17 | * Main entry point of the AabResGuard.
18 | *
19 | * Created by YangJing on 2019/10/09 .
20 | * Email: yangjing.yeoh@bytedance.com
21 | */
22 | public class AabResGuardMain {
23 | private static final String HELP_CMD = "help";
24 |
25 | public static void main(String[] args) {
26 | main(args, Runtime.getRuntime());
27 | }
28 |
29 | /**
30 | * Parses the flags and routes to the appropriate commands handler.
31 | */
32 | private static void main(String[] args, Runtime runtime) {
33 | final ParsedFlags flags;
34 | try {
35 | flags = new FlagParser().parse(args);
36 | } catch (FlagParser.FlagParseException e) {
37 | System.err.println("Error while parsing the flags: " + e.getMessage());
38 | runtime.exit(1);
39 | return;
40 | }
41 | Optional command = flags.getMainCommand();
42 | if (!command.isPresent()) {
43 | System.err.println("Error: You have to specify a commands.");
44 | help();
45 | runtime.exit(1);
46 | return;
47 | }
48 | try {
49 | switch (command.get()) {
50 | case ObfuscateBundleCommand.COMMAND_NAME:
51 | ObfuscateBundleCommand.fromFlags(flags).execute();
52 | break;
53 | case DuplicatedResourcesMergerCommand.COMMAND_NAME:
54 | DuplicatedResourcesMergerCommand.fromFlags(flags).execute();
55 | break;
56 | case FileFilterCommand.COMMAND_NAME:
57 | FileFilterCommand.fromFlags(flags).execute();
58 | break;
59 | case StringFilterCommand.COMMAND_NAME:
60 | StringFilterCommand.fromFlags(flags).execute();
61 | break;
62 | case HELP_CMD:
63 | if (flags.getSubCommand().isPresent()) {
64 | help(flags.getSubCommand().get(), runtime);
65 | } else {
66 | help();
67 | }
68 | break;
69 | default:
70 | System.err.printf("Error: Unrecognized command '%s'.%n%n%n", command.get());
71 | help();
72 | runtime.exit(1);
73 | return;
74 | }
75 | } catch (Exception e) {
76 | System.err.println(
77 | "[BT:" + AabResGuardVersion.getCurrentVersion() + "] Error: " + e.getMessage());
78 | e.printStackTrace();
79 | runtime.exit(1);
80 | return;
81 | }
82 | runtime.exit(0);
83 | }
84 |
85 | /**
86 | * Displays a general help.
87 | */
88 | public static void help() {
89 | ImmutableList commandHelps =
90 | ImmutableList.of(
91 | ObfuscateBundleCommand.help(),
92 | DuplicatedResourcesMergerCommand.help(),
93 | FileFilterCommand.help(),
94 | StringFilterCommand.help()
95 | );
96 | System.out.println("Synopsis: aabResGuard ...");
97 | System.out.println();
98 | System.out.println("Use 'aabResGuard help ' to learn more about the given command.");
99 | System.out.println();
100 | commandHelps.forEach(commandHelp -> commandHelp.printSummary(System.out));
101 | }
102 |
103 | /**
104 | * Displays help about a given commands.
105 | */
106 | public static void help(String commandName, Runtime runtime) {
107 | CommandHelp commandHelp;
108 | switch (commandName) {
109 | case ObfuscateBundleCommand.COMMAND_NAME:
110 | commandHelp = ObfuscateBundleCommand.help();
111 | break;
112 | case DuplicatedResourcesMergerCommand.COMMAND_NAME:
113 | commandHelp = DuplicatedResourcesMergerCommand.help();
114 | break;
115 | case FileFilterCommand.COMMAND_NAME:
116 | commandHelp = FileFilterCommand.help();
117 | break;
118 | case StringFilterCommand.COMMAND_NAME:
119 | commandHelp = StringFilterCommand.help();
120 | break;
121 | default:
122 | System.err.printf("Error: Unrecognized command '%s'.%n%n%n", commandName);
123 | help();
124 | runtime.exit(1);
125 | return;
126 | }
127 | commandHelp.printDetails(System.out);
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/android/AndroidDebugKeyStoreHelper.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.android;
2 |
3 | import java.io.File;
4 |
5 | /**
6 | * Created by YangJing on 2019/10/17 .
7 | * Email: yangjing.yeoh@bytedance.com
8 | */
9 | public class AndroidDebugKeyStoreHelper {
10 |
11 | public static final String DEFAULT_PASSWORD = "android";
12 | public static final String DEFAULT_ALIAS = "AndroidDebugKey";
13 |
14 | public static JarSigner.Signature debugSigningConfig() {
15 | String debugKeystoreLocation = defaultDebugKeystoreLocation();
16 | if (debugKeystoreLocation == null || !new File(debugKeystoreLocation).exists()) {
17 | return null;
18 | }
19 | return new JarSigner.Signature(
20 | new File(debugKeystoreLocation).toPath(),
21 | DEFAULT_PASSWORD,
22 | DEFAULT_ALIAS,
23 | DEFAULT_PASSWORD
24 | );
25 | }
26 |
27 | /**
28 | * Returns the location of the default debug keystore.
29 | *
30 | * @return The location of the default debug keystore
31 | */
32 | private static String defaultDebugKeystoreLocation() {
33 | //this is guaranteed to either return a non null value (terminated with a platform
34 | // specific separator), or throw.
35 | String folder = null;
36 | try {
37 | folder = AndroidLocation.getFolder();
38 | } catch (AndroidLocation.AndroidLocationException e) {
39 | e.printStackTrace();
40 | return null;
41 | }
42 | return folder + "debug.keystore";
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/android/JarSigner.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.android;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.nio.file.Path;
6 |
7 | import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable;
8 | import static com.bytedance.android.aabresguard.utils.exception.CommandExceptionPreconditions.checkStringIsEmpty;
9 |
10 | /**
11 | * Created by YangJing on 2019/10/18 .
12 | * Email: yangjing.yeoh@bytedance.com
13 | */
14 | public class JarSigner {
15 |
16 | public void sign(File toBeSigned, Signature signature) throws IOException, InterruptedException {
17 | new OpenJDKJarSigner().sign(toBeSigned, signature);
18 | }
19 |
20 | public static class Signature {
21 | public static final Signature DEBUG_SIGNATURE = AndroidDebugKeyStoreHelper.debugSigningConfig();
22 | public final Path storeFile;
23 | public final String storePassword;
24 | public final String keyAlias;
25 | public final String keyPassword;
26 |
27 |
28 | public Signature(Path storeFile, String storePassword, String keyAlias, String keyPassword) {
29 | this.storeFile = storeFile;
30 | this.storePassword = storePassword;
31 | this.keyAlias = keyAlias;
32 | this.keyPassword = keyPassword;
33 | checkFileExistsAndReadable(storeFile);
34 | checkStringIsEmpty(storePassword, "storePassword");
35 | checkStringIsEmpty(keyAlias, "keyAlias");
36 | checkStringIsEmpty(keyPassword, "keyPassword");
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/android/OpenJDKJarSigner.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.android;
2 |
3 | import com.bytedance.android.aabresguard.utils.FileUtils;
4 | import com.google.common.base.Joiner;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 | import java.util.logging.Logger;
11 |
12 | import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable;
13 |
14 | /**
15 | * Created by YangJing on 2019/10/18 .
16 | * Email: yangjing.yeoh@bytedance.com
17 | */
18 | public class OpenJDKJarSigner {
19 |
20 | private static String jarSignerExecutable = SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS
21 | ? "jarsigner.exe" : "jarsigner";
22 | private static Logger logger = Logger.getLogger(OpenJDKJarSigner.class.getName());
23 |
24 |
25 | public void sign(File toBeSigned, JarSigner.Signature signature) throws IOException, InterruptedException {
26 | checkFileExistsAndReadable(toBeSigned.toPath());
27 | checkFileExistsAndReadable(signature.storeFile);
28 | File jarSigner = locatedJarSigner();
29 | List args = new ArrayList<>();
30 | if (jarSigner != null) {
31 | args.add(jarSigner.getAbsolutePath());
32 | } else {
33 | args.add(jarSignerExecutable);
34 | }
35 | args.add("-keystore");
36 | args.add(signature.storeFile.toFile().getAbsolutePath());
37 |
38 | File keyStorePasswordFile = null;
39 | File aliasPasswordFile = null;
40 |
41 | // write passwords to a file so it cannot be spied on.
42 | if (signature.storePassword != null) {
43 | keyStorePasswordFile = File.createTempFile("store", "prv");
44 | FileUtils.writeToFile(keyStorePasswordFile, signature.storePassword);
45 | args.add("-storepass:file");
46 | args.add(keyStorePasswordFile.getAbsolutePath());
47 | }
48 |
49 | if (signature.keyPassword != null) {
50 | aliasPasswordFile = File.createTempFile("alias", "prv");
51 | FileUtils.writeToFile(aliasPasswordFile, signature.keyPassword);
52 | args.add("--keypass:file");
53 | args.add(aliasPasswordFile.getAbsolutePath());
54 | }
55 |
56 | args.add(toBeSigned.getAbsolutePath());
57 |
58 | if (signature.keyAlias != null) {
59 | args.add(signature.keyAlias);
60 | }
61 |
62 | File errorLog = File.createTempFile("error", ".log");
63 | File outputLog = File.createTempFile("output", ".log");
64 |
65 | logger.fine("Invoking " + Joiner.on(" ").join(args));
66 | Process process = start(new ProcessBuilder(args).redirectError(errorLog).redirectOutput(outputLog));
67 | int exitCode = process.waitFor();
68 | if (exitCode != 0) {
69 | String errors = FileUtils.loadFileWithUnixLineSeparators(errorLog);
70 | String output = FileUtils.loadFileWithUnixLineSeparators(outputLog);
71 | throw new RuntimeException(
72 | String.format("%s failed with exit code %d: \n %s",
73 | jarSignerExecutable, exitCode,
74 | errors.trim().isEmpty() ? output : errors
75 | )
76 | );
77 | }
78 | if (keyStorePasswordFile != null) {
79 | keyStorePasswordFile.delete();
80 | }
81 | if (aliasPasswordFile != null) {
82 | aliasPasswordFile.delete();
83 | }
84 | }
85 |
86 | private Process start(ProcessBuilder builder) throws IOException {
87 | return builder.start();
88 | }
89 |
90 | /**
91 | * Return the "jarsigner" tool location or null if it cannot be determined.
92 | */
93 | private File locatedJarSigner() {
94 | // Look in the java.home bin folder, on jdk installations or Mac OS X, this is where the
95 | // javasigner will be located.
96 | File javaHome = new File(System.getProperty("java.home"));
97 | File jarSigner = getJarSigner(javaHome);
98 | if (jarSigner.exists()) {
99 | return jarSigner;
100 | } else {
101 | // if not in java.home bin, it's probable that the java.home points to a JRE
102 | // installation, we should then look one folder up and in the bin folder.
103 | jarSigner = getJarSigner(javaHome.getParentFile());
104 | // if still cant' find it, give up.
105 | return jarSigner.exists() ? jarSigner : null;
106 | }
107 | }
108 |
109 | /**
110 | * Returns the jarsigner tool location with the bin folder.
111 | */
112 | private File getJarSigner(File parentDir) {
113 | return new File(new File(parentDir, "bin"), jarSignerExecutable);
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/android/SdkConstants.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.android;
2 |
3 | /**
4 | * Created by YangJing on 2019/10/18 .
5 | * Email: yangjing.yeoh@bytedance.com
6 | */
7 | public class SdkConstants {
8 | public static final int PLATFORM_UNKNOWN = 0;
9 | public static final int PLATFORM_LINUX = 1;
10 | public static final int PLATFORM_WINDOWS = 2;
11 | public static final int PLATFORM_DARWIN = 3;
12 |
13 | public static int currentPlatform() {
14 | String os = System.getProperty("os.name"); //$NON-NLS-1$
15 | if (os.startsWith("Mac OS")) { //$NON-NLS-1$
16 | return PLATFORM_DARWIN;
17 | } else if (os.startsWith("Windows")) { //$NON-NLS-1$
18 | return PLATFORM_WINDOWS;
19 | } else if (os.startsWith("Linux")) { //$NON-NLS-1$
20 | return PLATFORM_LINUX;
21 | }
22 |
23 | return PLATFORM_UNKNOWN;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/bundle/AppBundleAnalyzer.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.bundle;
2 |
3 | import com.android.tools.build.bundletool.model.AppBundle;
4 | import com.bytedance.android.aabresguard.utils.TimeClock;
5 |
6 | import java.io.IOException;
7 | import java.nio.file.Path;
8 | import java.util.logging.Logger;
9 | import java.util.zip.ZipFile;
10 |
11 | import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable;
12 |
13 | /**
14 | * Created by YangJing on 2019/10/10 .
15 | * Email: yangjing.yeoh@bytedance.com
16 | */
17 | public class AppBundleAnalyzer {
18 |
19 | private static final Logger logger = Logger.getLogger(AppBundleAnalyzer.class.getName());
20 | private final Path bundlePath;
21 |
22 | public AppBundleAnalyzer(Path bundlePath) {
23 | checkFileExistsAndReadable(bundlePath);
24 | this.bundlePath = bundlePath;
25 | }
26 |
27 | public AppBundle analyze() throws IOException {
28 | TimeClock timeClock = new TimeClock();
29 | ZipFile bundleZip = new ZipFile(bundlePath.toFile());
30 | AppBundle appBundle = AppBundle.buildFromZip(bundleZip);
31 | System.out.println(String.format("analyze bundle file done, const %s", timeClock.getCoast()));
32 | return appBundle;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/bundle/AppBundlePackager.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.bundle;
2 |
3 | import com.android.tools.build.bundletool.io.AppBundleSerializer;
4 | import com.android.tools.build.bundletool.model.AppBundle;
5 | import com.bytedance.android.aabresguard.utils.TimeClock;
6 |
7 | import java.io.IOException;
8 | import java.nio.file.Path;
9 | import java.util.logging.Logger;
10 |
11 | import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileDoesNotExist;
12 |
13 | /**
14 | * Created by YangJing on 2019/10/11 .
15 | * Email: yangjing.yeoh@bytedance.com
16 | */
17 | public class AppBundlePackager {
18 | private static final Logger logger = Logger.getLogger(AppBundlePackager.class.getName());
19 |
20 | private final Path output;
21 | private final AppBundle appBundle;
22 |
23 | public AppBundlePackager(AppBundle appBundle, Path output) {
24 | this.output = output;
25 | this.appBundle = appBundle;
26 | checkFileDoesNotExist(output);
27 | }
28 |
29 | public void execute() throws IOException {
30 | TimeClock timeClock = new TimeClock();
31 | AppBundleSerializer appBundleSerializer = new AppBundleSerializer();
32 | appBundleSerializer.writeToDisk(appBundle, output);
33 |
34 | System.out.println(String.format("package bundle done, coast: %s", timeClock.getCoast()));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/bundle/AppBundleSigner.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.bundle;
2 |
3 | import com.bytedance.android.aabresguard.android.JarSigner;
4 | import com.bytedance.android.aabresguard.utils.TimeClock;
5 |
6 | import java.io.IOException;
7 | import java.nio.file.Path;
8 | import java.util.logging.Logger;
9 |
10 | /**
11 | * Created by YangJing on 2019/10/11 .
12 | * Email: yangjing.yeoh@bytedance.com
13 | */
14 | public class AppBundleSigner {
15 |
16 | private Path bundleFile;
17 | private JarSigner.Signature bundleSignature = JarSigner.Signature.DEBUG_SIGNATURE;
18 |
19 | public AppBundleSigner(Path bundleFile, JarSigner.Signature signature) {
20 | this.bundleFile = bundleFile;
21 | this.bundleSignature = signature;
22 | }
23 |
24 | public AppBundleSigner(Path bundleFile) {
25 | this.bundleFile = bundleFile;
26 | }
27 |
28 | public void setBundleSignature(JarSigner.Signature bundleSignature) {
29 | this.bundleSignature = bundleSignature;
30 | }
31 |
32 | public void execute() throws IOException, InterruptedException {
33 | if (bundleSignature == null) {
34 | return;
35 | }
36 | TimeClock timeClock = new TimeClock();
37 | JarSigner.Signature signature = new JarSigner.Signature(
38 | bundleSignature.storeFile,
39 | bundleSignature.storePassword,
40 | bundleSignature.keyAlias,
41 | bundleSignature.keyPassword
42 | );
43 | new JarSigner().sign(bundleFile.toFile(), signature);
44 | System.out.println(String.format("[sign] sign done, coast: %s", timeClock.getCoast()));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/bundle/AppBundleUtils.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.bundle;
2 |
3 | import com.android.tools.build.bundletool.model.BundleModule;
4 | import com.android.tools.build.bundletool.model.ModuleEntry;
5 | import com.android.tools.build.bundletool.model.ResourceTableEntry;
6 | import com.android.tools.build.bundletool.model.ZipPath;
7 | import com.android.tools.build.bundletool.model.utils.files.BufferedIo;
8 |
9 | import org.apache.commons.codec.digest.DigestUtils;
10 | import org.apache.commons.io.IOUtils;
11 |
12 | import java.io.IOException;
13 | import java.io.InputStream;
14 | import java.util.zip.ZipEntry;
15 | import java.util.zip.ZipFile;
16 |
17 | import static com.bytedance.android.aabresguard.utils.FileOperation.getZipPathFileSize;
18 |
19 | /**
20 | * Created by YangJing on 2019/10/14 .
21 | * Email: yangjing.yeoh@bytedance.com
22 | */
23 | public class AppBundleUtils {
24 |
25 | public static long getZipEntrySize(ZipFile bundleZipFile, ModuleEntry entry, BundleModule bundleModule) {
26 | String path = String.format("%s/%s", bundleModule.getName().getName(), entry.getPath().toString());
27 | ZipEntry bundleConfigEntry = bundleZipFile.getEntry(path);
28 | return getZipPathFileSize(bundleZipFile, bundleConfigEntry);
29 | }
30 |
31 | public static long getZipEntrySize(ZipFile bundleZipFile, ZipPath zipPath) {
32 | String path = zipPath.toString();
33 | ZipEntry bundleConfigEntry = bundleZipFile.getEntry(path);
34 | return getZipPathFileSize(bundleZipFile, bundleConfigEntry);
35 | }
36 |
37 |
38 | public static String getEntryMd5(ZipFile bundleZipFile, ModuleEntry entry, BundleModule bundleModule) {
39 | String path = String.format("%s/%s", bundleModule.getName().getName(), entry.getPath().toString());
40 | ZipEntry bundleConfigEntry = bundleZipFile.getEntry(path);
41 | try {
42 | InputStream is = BufferedIo.inputStream(bundleZipFile, bundleConfigEntry);
43 | String md5 = bytesToHexString(DigestUtils.md5(is));
44 | is.close();
45 | return md5;
46 | } catch (IOException e) {
47 | throw new RuntimeException(e);
48 | }
49 | }
50 |
51 | public static byte[] readByte(ZipFile bundleZipFile, ModuleEntry entry, BundleModule bundleModule) throws IOException {
52 | String path = String.format("%s/%s", bundleModule.getName().getName(), entry.getPath().toString());
53 | ZipEntry bundleConfigEntry = bundleZipFile.getEntry(path);
54 | InputStream is = BufferedIo.inputStream(bundleZipFile, bundleConfigEntry);
55 | byte[] bytes = IOUtils.toByteArray(is);
56 | is.close();
57 | return bytes;
58 | }
59 |
60 | public static String bytesToHexString(byte[] src) {
61 | if (src.length <= 0) {
62 | return "";
63 | }
64 | StringBuilder stringBuilder = new StringBuilder(src.length);
65 | for (byte b : src) {
66 | int v = b & 0xFF;
67 | String hv = Integer.toHexString(v);
68 | if (hv.length() < 2) {
69 | stringBuilder.append(0);
70 | }
71 | stringBuilder.append(hv);
72 | }
73 | return stringBuilder.toString();
74 | }
75 |
76 | public static String getEntryNameByResourceName(String resourceName) {
77 | int index = resourceName.indexOf(".R.");
78 | String value = resourceName.substring(index + 3);
79 | String[] values = value.replace(".", "/").split("/");
80 | if (values.length != 2) {
81 | throw new RuntimeException("Invalid resource format, it should be package.type.entry, yours: " + resourceName);
82 | }
83 | return values[values.length - 1];
84 | }
85 |
86 | public static String getTypeNameByResourceName(String resourceName) {
87 | int index = resourceName.indexOf(".R.");
88 | String value = resourceName.substring(index + 3);
89 | String[] values = value.replace(".", "/").split("/");
90 | if (values.length != 2) {
91 | throw new RuntimeException("Invalid resource format, it should be package.type.entry, yours: " + resourceName);
92 | }
93 | return values[0];
94 | }
95 |
96 | public static String getResourceFullName(ResourceTableEntry entry) {
97 | return getResourceFullName(entry.getPackage().getPackageName(), entry.getType().getName(), entry.getEntry().getName());
98 | }
99 |
100 | public static String getResourceFullName(String packageName, String typeName, String entryName) {
101 | return String.format("%s.R.%s.%s", packageName, typeName, entryName);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/bundle/NativeLibrariesOperation.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.bundle;
2 |
3 | import com.android.bundle.Files;
4 |
5 | /**
6 | * Created by YangJing on 2019/10/14 .
7 | * Email: yangjing.yeoh@bytedance.com
8 | */
9 | public class NativeLibrariesOperation {
10 |
11 | public static Files.NativeLibraries removeDirectory(Files.NativeLibraries nativeLibraries, String zipPath) {
12 | int index = -1;
13 | for (int i = 0; i < nativeLibraries.getDirectoryList().size(); i++) {
14 | Files.TargetedNativeDirectory directory = nativeLibraries.getDirectoryList().get(i);
15 | if (directory.getPath().equals(zipPath)) {
16 | index = i;
17 | break;
18 | }
19 | }
20 | if (index == -1) {
21 | return nativeLibraries;
22 | }
23 | return nativeLibraries.toBuilder().removeDirectory(index).build();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/bundle/ResourcesTableBuilder.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.bundle;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.android.aapt.Resources;
6 |
7 | import java.util.ArrayList;
8 | import java.util.HashMap;
9 | import java.util.List;
10 | import java.util.Map;
11 |
12 | import static com.google.common.base.Preconditions.checkArgument;
13 | import static com.google.common.base.Preconditions.checkState;
14 |
15 |
16 | /**
17 | * 用于生成 {@link com.android.aapt.Resources.ResourceTable} 类
18 | *
19 | * Created by YangJing on 2019/04/15 .
20 | * Email: yangjing.yeoh@bytedance.com
21 | */
22 | public class ResourcesTableBuilder {
23 |
24 | private Resources.ResourceTable.Builder table;
25 | private Map resPackageMap;
26 | private List resPackages;
27 |
28 |
29 | public ResourcesTableBuilder() {
30 | table = Resources.ResourceTable.newBuilder();
31 | resPackageMap = new HashMap<>();
32 | resPackages = new ArrayList<>();
33 | }
34 |
35 | /**
36 | * 添加 package
37 | */
38 | public PackageBuilder addPackage(Resources.Package resPackage) {
39 | if (resPackageMap.containsKey(resPackage.getPackageName())) {
40 | return resPackageMap.get(resPackage.getPackageName());
41 | }
42 | PackageBuilder packageBuilder = new PackageBuilder(resPackage);
43 | resPackageMap.put(resPackage.getPackageName(), packageBuilder);
44 | return packageBuilder;
45 | }
46 |
47 | /**
48 | * 生成 ResourceTable
49 | */
50 | public Resources.ResourceTable build() {
51 | resPackageMap.entrySet().forEach(entry -> table.addPackage(entry.getValue().resPackageBuilder.build()));
52 | return table.build();
53 | }
54 |
55 | public class PackageBuilder {
56 |
57 | Resources.Package.Builder resPackageBuilder;
58 |
59 | private PackageBuilder(Resources.Package resPackage) {
60 | addPackage(resPackage);
61 | }
62 |
63 | public ResourcesTableBuilder build() {
64 | // resPackages.add(resPackageBuilder.build());
65 | return ResourcesTableBuilder.this;
66 | }
67 |
68 | /**
69 | * 添加 package
70 | */
71 | private void addPackage(Resources.Package resPackage) {
72 | int id = resPackage.getPackageId().getId();
73 | checkArgument(
74 | table.getPackageList().stream().noneMatch(pkg -> pkg.getPackageId().getId() == id),
75 | "Package ID %s already in use.",
76 | id);
77 |
78 | resPackageBuilder = Resources.Package.newBuilder()
79 | .setPackageId(resPackage.getPackageId())
80 | .setPackageName(resPackage.getPackageName());
81 | }
82 |
83 | /**
84 | * 在当前的 package 中寻找 type,如果不存在则添加指 package 中
85 | */
86 | @NonNull
87 | Resources.Type.Builder getResourceType(@NonNull Resources.Type resType) {
88 | return resPackageBuilder.getTypeBuilderList().stream()
89 | .filter(type -> type.getTypeId().getId() == resType.getTypeId().getId())
90 | .findFirst()
91 | .orElseGet(() -> addResourceType(resType));
92 | }
93 |
94 | @NonNull
95 | Resources.Type.Builder addResourceType(@NonNull Resources.Type resType) {
96 | Resources.Type.Builder typeBuilder = Resources.Type.newBuilder()
97 | .setName(resType.getName())
98 | .setTypeId(resType.getTypeId());
99 | resPackageBuilder.addType(typeBuilder);
100 | return getResourceType(resType);
101 | }
102 |
103 | /**
104 | * 添加资源
105 | *
106 | * 如果 Entry 中不指定 id 的话,该资源不会被添加进 ResourceTable ,此处强行指定 id 为 0.
107 | *
108 | * @param resType 资源类型
109 | * @param resEntry entry
110 | */
111 | @SuppressWarnings("UnusedReturnValue")
112 | public PackageBuilder addResource(@NonNull Resources.Type resType,
113 | @NonNull Resources.Entry resEntry) {
114 | // 如果 package 中不存在 package 则先添加 type
115 | Resources.Type.Builder type = getResourceType(resType);
116 | // 添加 entry 资源
117 | checkState(resPackageBuilder != null, "A package must be created before a resource can be added.");
118 | if (!resEntry.getEntryId().isInitialized()) {
119 | resEntry = resEntry.toBuilder().setEntryId(
120 | resEntry.getEntryId().toBuilder().setId(0).build()
121 | ).build();
122 | }
123 | type.addEntry(resEntry.toBuilder());
124 | return this;
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/bundle/ResourcesTableOperation.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.bundle;
2 |
3 | import com.android.aapt.Resources;
4 |
5 | import java.util.HashSet;
6 | import java.util.List;
7 | import java.util.Set;
8 |
9 | /**
10 | * Created by YangJing on 2019/10/10 .
11 | * Email: yangjing.yeoh@bytedance.com
12 | */
13 | public class ResourcesTableOperation {
14 |
15 | public static Resources.ConfigValue replaceEntryPath(Resources.ConfigValue configValue, String path) {
16 | Resources.ConfigValue.Builder entryBuilder = configValue.toBuilder();
17 | entryBuilder.setValue(
18 | configValue.getValue().toBuilder().setItem(
19 | configValue.getValue().getItem().toBuilder().setFile(
20 | configValue.getValue().getItem().getFile().toBuilder().setPath(path).build()
21 | ).build()
22 | ).build()
23 | );
24 | return entryBuilder.build();
25 | }
26 |
27 | public static Resources.Entry updateEntryConfigValueList(Resources.Entry entry, List configValueList) {
28 | Resources.Entry.Builder entryBuilder = entry.toBuilder();
29 | entryBuilder.clearConfigValue();
30 | entryBuilder.addAllConfigValue(configValueList);
31 | return entryBuilder.build();
32 | }
33 |
34 | public static Resources.Entry updateEntryName(Resources.Entry entry, String name) {
35 | Resources.Entry.Builder builder = entry.toBuilder();
36 | builder.setName(name);
37 | return builder.build();
38 | }
39 |
40 | public static void checkConfiguration(Resources.Entry entry) {
41 | if (entry.getConfigValueCount() == 0) return;
42 | Set configValues = new HashSet<>();
43 | for (Resources.ConfigValue configValue : entry.getConfigValueList()) {
44 | if (configValues.contains(configValue)) {
45 | throw new IllegalArgumentException("duplicate configuration for entry: " + entry.getName());
46 | }
47 | configValues.add(configValue);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/model/ResourcesMapping.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.model;
2 |
3 | import java.io.BufferedWriter;
4 | import java.io.FileWriter;
5 | import java.io.IOException;
6 | import java.io.Writer;
7 | import java.nio.file.Path;
8 | import java.util.HashMap;
9 | import java.util.List;
10 | import java.util.Map;
11 | import java.util.stream.Collectors;
12 |
13 | /**
14 | * Created by YangJing on 2019/10/14 .
15 | * Email: yangjing.yeoh@bytedance.com
16 | */
17 | public class ResourcesMapping {
18 |
19 | private Map dirMapping = new HashMap<>();
20 | private Map resourceMapping = new HashMap<>();
21 | private Map entryFilesMapping = new HashMap<>();
22 |
23 | private Map resourcesNameToIdMapping = new HashMap<>();
24 | private Map resourcesPathToIdMapping = new HashMap<>();
25 |
26 | public ResourcesMapping() {
27 | }
28 |
29 | public static String getResourceSimpleName(String resourceName) {
30 | String[] values = resourceName.split("/");
31 | return values[values.length - 1];
32 | }
33 |
34 | public Map getDirMapping() {
35 | return dirMapping;
36 | }
37 |
38 | public Map getResourceMapping() {
39 | return resourceMapping;
40 | }
41 |
42 | public Map getEntryFilesMapping() {
43 | return entryFilesMapping;
44 | }
45 |
46 | public void putDirMapping(String rawPath, String obfuscatePath) {
47 | dirMapping.put(rawPath, obfuscatePath);
48 | }
49 |
50 | public void putResourceMapping(String rawResource, String obfuscateResource) {
51 | if (resourceMapping.values().contains(obfuscateResource)) {
52 | throw new IllegalArgumentException(
53 | String.format("Multiple entries: %s -> %s",
54 | rawResource, obfuscateResource)
55 | );
56 | }
57 | resourceMapping.put(rawResource, obfuscateResource);
58 | }
59 |
60 | public void putEntryFileMapping(String rawPath, String obfuscatedPath) {
61 | entryFilesMapping.put(rawPath, obfuscatedPath);
62 | }
63 |
64 | public List getPathMappingNameList() {
65 | return dirMapping.values().stream()
66 | .map(value -> {
67 | String[] values = value.split("/");
68 | if (value.length() == 0) return value;
69 | return values[values.length - 1];
70 | })
71 | .collect(Collectors.toList());
72 | }
73 |
74 | public void addResourceNameAndId(String name, String id) {
75 | resourcesNameToIdMapping.put(name, id);
76 | }
77 |
78 | public void addResourcePathAndId(String path, String id) {
79 | resourcesPathToIdMapping.put(path, id);
80 | }
81 |
82 | /**
83 | * Write mapping rules to file.
84 | */
85 | public void writeMappingToFile(Path mappingPath) throws IOException {
86 | Writer writer = new BufferedWriter(new FileWriter(mappingPath.toFile(), false));
87 |
88 | // write resources dir
89 | writer.write("res dir mapping:\n");
90 | for (Map.Entry entry : dirMapping.entrySet()) {
91 | writer.write(String.format(
92 | "\t%s -> %s\n",
93 | entry.getKey(),
94 | entry.getValue()
95 | ));
96 | }
97 | writer.write("\n\n");
98 | writer.flush();
99 |
100 | // write resources name
101 | writer.write("res id mapping:\n");
102 | for (Map.Entry entry : resourceMapping.entrySet()) {
103 | writer.write(String.format(
104 | "\t%s : %s -> %s\n",
105 | resourcesNameToIdMapping.get(entry.getKey()),
106 | entry.getKey(),
107 | entry.getValue()
108 | ));
109 | }
110 | writer.write("\n\n");
111 | writer.flush();
112 |
113 | // write resources entries path
114 | writer.write("res entries path mapping:\n");
115 | for (Map.Entry entry : entryFilesMapping.entrySet()) {
116 | writer.write(String.format(
117 | "\t%s : %s -> %s\n",
118 | resourcesPathToIdMapping.get(entry.getKey()),
119 | entry.getKey(),
120 | entry.getValue()
121 | ));
122 | }
123 | writer.write("\n\n");
124 | writer.flush();
125 |
126 | writer.close();
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/model/version/AabResGuardVersion.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.model.version;
2 |
3 | import com.android.tools.build.bundletool.model.version.Version;
4 |
5 | /**
6 | * Versions of AabResGuard.
7 | *
8 | * Created by YangJing on 2019/10/09 .
9 | * Email: yangjing.yeoh@bytedance.com
10 | */
11 | public class AabResGuardVersion {
12 |
13 | private static final String CURRENT_VERSION = "0.9.0";
14 |
15 | /**
16 | * Returns the version of BundleTool being run.
17 | */
18 | public static Version getCurrentVersion() {
19 | return Version.of(CURRENT_VERSION);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/model/xml/AabResGuardConfig.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.model.xml;
2 |
3 | import java.util.HashSet;
4 | import java.util.Set;
5 |
6 | /**
7 | * Created by YangJing on 2019/10/14 .
8 | * Email: yangjing.yeoh@bytedance.com
9 | */
10 | public class AabResGuardConfig {
11 | private FileFilterConfig fileFilter;
12 | private StringFilterConfig stringFilterConfig;
13 | private boolean useWhiteList;
14 | private Set whiteList = new HashSet<>();
15 |
16 |
17 | public FileFilterConfig getFileFilter() {
18 | return fileFilter;
19 | }
20 |
21 | public void setFileFilter(FileFilterConfig fileFilter) {
22 | this.fileFilter = fileFilter;
23 | }
24 |
25 | public boolean isUseWhiteList() {
26 | return useWhiteList;
27 | }
28 |
29 | public void setUseWhiteList(boolean useWhiteList) {
30 | this.useWhiteList = useWhiteList;
31 | }
32 |
33 | public Set getWhiteList() {
34 | return whiteList;
35 | }
36 |
37 | public void addWhiteList(String whiteRule) {
38 | this.whiteList.add(whiteRule);
39 | }
40 |
41 | public StringFilterConfig getStringFilterConfig() {
42 | return stringFilterConfig;
43 | }
44 |
45 | public void setStringFilterConfig(StringFilterConfig stringFilterConfig) {
46 | this.stringFilterConfig = stringFilterConfig;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/model/xml/FileFilterConfig.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.model.xml;
2 |
3 | import java.util.HashSet;
4 | import java.util.Set;
5 |
6 | /**
7 | * Created by YangJing on 2019/10/14 .
8 | * Email: yangjing.yeoh@bytedance.com
9 | */
10 | public class FileFilterConfig {
11 | private boolean isActive;
12 | private Set rules = new HashSet<>();
13 |
14 | public boolean isActive() {
15 | return isActive;
16 | }
17 |
18 | public void setActive(boolean active) {
19 | isActive = active;
20 | }
21 |
22 | public Set getRules() {
23 | return rules;
24 | }
25 |
26 | public void addRule(String rule) {
27 | rules.add(rule);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/model/xml/StringFilterConfig.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.model.xml;
2 |
3 | import java.util.HashSet;
4 | import java.util.Set;
5 |
6 | /**
7 | * Created by jiangzilai on 2019-10-20.
8 | */
9 | public class StringFilterConfig {
10 | private boolean isActive;
11 | private String path = "";
12 | private Set languageWhiteList = new HashSet<>();
13 |
14 |
15 | public boolean isActive() {
16 | return isActive;
17 | }
18 |
19 | public void setActive(boolean active) {
20 | isActive = active;
21 | }
22 |
23 | public String getPath() {
24 | return path;
25 | }
26 |
27 | public void setPath(String path) {
28 | this.path = path;
29 | }
30 |
31 | @Override public String toString() {
32 | return "StringFilterConfig{" +
33 | "isActive=" + isActive +
34 | ", path='" + path + '\'' +
35 | ", languageWhiteList=" + languageWhiteList +
36 | '}';
37 | }
38 |
39 | public Set getLanguageWhiteList() {
40 | return languageWhiteList;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/obfuscation/ResGuardStringBuilder.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.obfuscation;
2 |
3 | import com.bytedance.android.aabresguard.utils.Utils;
4 |
5 | import java.util.ArrayList;
6 | import java.util.Collection;
7 | import java.util.HashSet;
8 | import java.util.List;
9 | import java.util.Set;
10 | import java.util.regex.Pattern;
11 |
12 | /**
13 | * 混淆字典.
14 | *
15 | * Copied from: https://github.com/shwenzhang/AndResGuard
16 | */
17 | public class ResGuardStringBuilder {
18 |
19 | private final List mReplaceStringBuffer;
20 | private final Set mIsReplaced;
21 | private final Set mIsWhiteList;
22 | private String[] mAToZ = {
23 | "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
24 | "w", "x", "y", "z"
25 | };
26 | private String[] mAToAll = {
27 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "_", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
28 | "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
29 | };
30 | /**
31 | * 在window上面有些关键字是不能作为文件名的
32 | * CON, PRN, AUX, CLOCK$, NUL
33 | * COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9
34 | * LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9.
35 | */
36 | private HashSet mFileNameBlackList;
37 |
38 | public ResGuardStringBuilder() {
39 | mFileNameBlackList = new HashSet<>();
40 | mFileNameBlackList.add("con");
41 | mFileNameBlackList.add("prn");
42 | mFileNameBlackList.add("aux");
43 | mFileNameBlackList.add("nul");
44 | mReplaceStringBuffer = new ArrayList<>();
45 | mIsReplaced = new HashSet<>();
46 | mIsWhiteList = new HashSet<>();
47 | }
48 |
49 | public void reset(HashSet blacklistPatterns) {
50 | mReplaceStringBuffer.clear();
51 | mIsReplaced.clear();
52 | mIsWhiteList.clear();
53 |
54 | for (String str : mAToZ) {
55 | if (!Utils.match(str, blacklistPatterns)) {
56 | mReplaceStringBuffer.add(str);
57 | }
58 | }
59 |
60 | for (String first : mAToZ) {
61 | for (String aMAToAll : mAToAll) {
62 | String str = first + aMAToAll;
63 | if (!Utils.match(str, blacklistPatterns)) {
64 | mReplaceStringBuffer.add(str);
65 | }
66 | }
67 | }
68 |
69 | for (String first : mAToZ) {
70 | for (String second : mAToAll) {
71 | for (String third : mAToAll) {
72 | String str = first + second + third;
73 | if (!mFileNameBlackList.contains(str) && !Utils.match(str, blacklistPatterns)) {
74 | mReplaceStringBuffer.add(str);
75 | }
76 | }
77 | }
78 | }
79 | }
80 |
81 | // 对于某种类型用过的mapping,全部不能再用了
82 | public void removeStrings(Collection collection) {
83 | if (collection == null) return;
84 | mReplaceStringBuffer.removeAll(collection);
85 | }
86 |
87 | public boolean isReplaced(int id) {
88 | return mIsReplaced.contains(id);
89 | }
90 |
91 | public boolean isInWhiteList(int id) {
92 | return mIsWhiteList.contains(id);
93 | }
94 |
95 | public void setInWhiteList(int id) {
96 | mIsWhiteList.add(id);
97 | }
98 |
99 | public void setInReplaceList(int id) {
100 | mIsReplaced.add(id);
101 | }
102 |
103 | public String getReplaceString(Collection names) throws IllegalArgumentException {
104 | if (mReplaceStringBuffer.isEmpty()) {
105 | throw new IllegalArgumentException("now can only obfuscation less than 35594 in a single type\n");
106 | }
107 | if (names != null) {
108 | for (int i = 0; i < mReplaceStringBuffer.size(); i++) {
109 | String name = mReplaceStringBuffer.get(i);
110 | if (names.contains(name)) {
111 | continue;
112 | }
113 | return mReplaceStringBuffer.remove(i);
114 | }
115 | }
116 | return mReplaceStringBuffer.remove(0);
117 | }
118 |
119 | public String getReplaceString() {
120 | return getReplaceString(null);
121 | }
122 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/parser/AabResGuardXmlParser.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.parser;
2 |
3 | import com.bytedance.android.aabresguard.model.xml.AabResGuardConfig;
4 |
5 | import org.dom4j.Document;
6 | import org.dom4j.DocumentException;
7 | import org.dom4j.Element;
8 | import org.dom4j.io.SAXReader;
9 |
10 | import java.nio.file.Path;
11 | import java.util.Iterator;
12 |
13 | import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable;
14 |
15 | /**
16 | * Created by YangJing on 2019/10/14 .
17 | * Email: yangjing.yeoh@bytedance.com
18 | */
19 | public class AabResGuardXmlParser {
20 | private final Path configPath;
21 |
22 | public AabResGuardXmlParser(Path configPath) {
23 | checkFileExistsAndReadable(configPath);
24 | this.configPath = configPath;
25 | }
26 |
27 | public AabResGuardConfig parse() throws DocumentException {
28 | AabResGuardConfig aabResGuardConfig = new AabResGuardConfig();
29 | SAXReader reader = new SAXReader();
30 | Document doc = reader.read(configPath.toFile());
31 | Element root = doc.getRootElement();
32 | for (Iterator i = root.elementIterator("issue"); i.hasNext(); ) {
33 | Element element = (Element) i.next();
34 | String id = element.attributeValue("id");
35 | if (id == null || !id.equals("whitelist")) {
36 | continue;
37 | }
38 | String isActive = element.attributeValue("isactive");
39 | if (isActive != null && isActive.equals("true")) {
40 | aabResGuardConfig.setUseWhiteList(true);
41 | }
42 | for (Iterator rules = element.elementIterator("path"); rules.hasNext(); ) {
43 | Element ruleElement = (Element) rules.next();
44 | String rule = ruleElement.attributeValue("value");
45 | if (rule != null) {
46 | aabResGuardConfig.addWhiteList(rule);
47 | }
48 | }
49 | }
50 |
51 | // file filter
52 | aabResGuardConfig.setFileFilter(new FileFilterXmlParser(configPath).parse());
53 |
54 | // string filter
55 | aabResGuardConfig.setStringFilterConfig(new StringFilterXmlParser(configPath).parse());
56 |
57 | return aabResGuardConfig;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/parser/FileFilterXmlParser.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.parser;
2 |
3 | import com.bytedance.android.aabresguard.model.xml.FileFilterConfig;
4 |
5 | import org.dom4j.Document;
6 | import org.dom4j.DocumentException;
7 | import org.dom4j.Element;
8 | import org.dom4j.io.SAXReader;
9 |
10 | import java.nio.file.Path;
11 | import java.util.Iterator;
12 |
13 | import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable;
14 |
15 | /**
16 | * Created by YangJing on 2019/10/14 .
17 | * Email: yangjing.yeoh@bytedance.com
18 | */
19 | public class FileFilterXmlParser {
20 | private final Path configPath;
21 |
22 | public FileFilterXmlParser(Path configPath) {
23 | checkFileExistsAndReadable(configPath);
24 | this.configPath = configPath;
25 | }
26 |
27 | public FileFilterConfig parse() throws DocumentException {
28 | FileFilterConfig fileFilter = new FileFilterConfig();
29 |
30 | SAXReader reader = new SAXReader();
31 | Document doc = reader.read(configPath.toFile());
32 | Element root = doc.getRootElement();
33 | for (Iterator i = root.elementIterator("filter"); i.hasNext(); ) {
34 | Element element = (Element) i.next();
35 | String isActiveValue = element.attributeValue("isactive");
36 | if (isActiveValue != null && isActiveValue.equals("true")) {
37 | fileFilter.setActive(true);
38 | }
39 | for (Iterator rules = element.elementIterator("rule"); rules.hasNext(); ) {
40 | Element ruleElement = (Element) rules.next();
41 | String rule = ruleElement.attributeValue("value");
42 | if (rule != null) {
43 | fileFilter.addRule(rule);
44 | }
45 | }
46 | }
47 | return fileFilter;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/parser/ResourcesMappingParser.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.parser;
2 |
3 | import com.bytedance.android.aabresguard.model.ResourcesMapping;
4 |
5 | import java.io.BufferedReader;
6 | import java.io.FileReader;
7 | import java.io.IOException;
8 | import java.nio.file.Path;
9 | import java.util.regex.Matcher;
10 | import java.util.regex.Pattern;
11 |
12 | import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable;
13 |
14 | /**
15 | * Created by YangJing on 2019/10/14 .
16 | * Email: yangjing.yeoh@bytedance.com
17 | */
18 | public class ResourcesMappingParser {
19 | private static final Pattern MAP_DIR_PATTERN = Pattern.compile("\\s+(.*)->(.*)");
20 | private static final Pattern MAP_RES_PATTERN = Pattern.compile("\\s+(.*):(.*)->(.*)");
21 | private final Path mappingPath;
22 |
23 | public ResourcesMappingParser(Path mappingPath) {
24 | checkFileExistsAndReadable(mappingPath);
25 | this.mappingPath = mappingPath;
26 | }
27 |
28 | public ResourcesMapping parse() throws IOException {
29 | ResourcesMapping mapping = new ResourcesMapping();
30 |
31 | FileReader fr = new FileReader(mappingPath.toFile());
32 | BufferedReader br = new BufferedReader(fr);
33 | String line = br.readLine();
34 | while (line != null) {
35 | if (line.length() <= 0) {
36 | line = br.readLine();
37 | continue;
38 | }
39 | if (!line.contains(":")) {
40 | Matcher mat = MAP_DIR_PATTERN.matcher(line);
41 | if (mat.find()) {
42 | String rawName = mat.group(1).trim();
43 | String obfuscateName = mat.group(2).trim();
44 | if (!line.contains("/") || line.contains(".")) {
45 | throw new IllegalArgumentException("Unexpected resource dir: " + line);
46 | }
47 | mapping.putDirMapping(rawName, obfuscateName);
48 | }
49 | } else {
50 | Matcher mat = MAP_RES_PATTERN.matcher(line);
51 | if (mat.find()) {
52 | String rawName = mat.group(2).trim();
53 | String obfuscateName = mat.group(3).trim();
54 | if (line.contains("/")) {
55 | mapping.putEntryFileMapping(rawName, obfuscateName);
56 | } else {
57 | int packagePos = rawName.indexOf(".R.");
58 | if (packagePos == -1) {
59 | throw new IllegalArgumentException(String.format("the mapping file packageName is malformed, "
60 | + "it should be like com.bytedance.android.ugc.R.attr.test, yours %s\n",
61 | rawName
62 | ));
63 | }
64 | mapping.putResourceMapping(rawName, obfuscateName);
65 | }
66 | }
67 | }
68 | line = br.readLine();
69 | }
70 |
71 | br.close();
72 | fr.close();
73 | return mapping;
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/parser/StringFilterXmlParser.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.parser;
2 |
3 | import com.bytedance.android.aabresguard.model.xml.StringFilterConfig;
4 |
5 | import org.dom4j.Document;
6 | import org.dom4j.DocumentException;
7 | import org.dom4j.Element;
8 | import org.dom4j.io.SAXReader;
9 |
10 | import java.nio.file.Path;
11 | import java.util.Iterator;
12 |
13 | import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable;
14 |
15 | /**
16 | * Created by jiangzilai on 2019-10-20.
17 | */
18 | public class StringFilterXmlParser {
19 | private final Path configPath;
20 |
21 | public StringFilterXmlParser(Path configPath) {
22 | checkFileExistsAndReadable(configPath);
23 | this.configPath = configPath;
24 | }
25 |
26 | public StringFilterConfig parse() throws DocumentException {
27 | StringFilterConfig config = new StringFilterConfig();
28 | SAXReader reader = new SAXReader();
29 | Document doc = reader.read(configPath.toFile());
30 | Element root = doc.getRootElement();
31 |
32 | for (Iterator i = root.elementIterator("filter-str"); i.hasNext(); ) {
33 | Element element = (Element) i.next();
34 | String isActive = element.attributeValue("isactive");
35 | if (isActive != null && isActive.toLowerCase().equals("true")) {
36 | config.setActive(true);
37 | }
38 | for (Iterator rules = element.elementIterator("path"); rules.hasNext(); ) {
39 | Element ruleElement = (Element) rules.next();
40 | String path = ruleElement.attributeValue("value");
41 | config.setPath(path);
42 | }
43 | for (Iterator rules = element.elementIterator("language"); rules.hasNext(); ) {
44 | Element ruleElement = (Element) rules.next();
45 | String path = ruleElement.attributeValue("value");
46 | config.getLanguageWhiteList().add(path);
47 | }
48 | }
49 | return config;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/utils/DrawNinePatchUtils.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.utils;
2 |
3 | import com.android.tools.build.bundletool.model.BundleModule;
4 | import com.android.tools.build.bundletool.model.ZipPath;
5 |
6 |
7 | /**
8 | * .9 文件的帮助类
9 | *
10 | * Created by YangJing on 2019/04/19 .
11 | * Email: yangjing.yeoh@bytedance.com
12 | */
13 | public class DrawNinePatchUtils {
14 |
15 | public static final String RESOURCE_SUFFIX_9_PATCH = ".9";
16 | public static final String[] RESOURCE_TYPE_IMG = new String[]{
17 | "drawable",
18 | "mipmap"
19 | };
20 |
21 | /**
22 | * 是否是 .9 的图片资源
23 | *
24 | * @param typeName 资源类型名称,如: drawable
25 | * @param simpleName 资源名称(不包含文件后缀),如 a.9 aa
26 | * @return 是否是 .9 的图片资源
27 | */
28 | public static boolean is9PatchResource(String typeName, String simpleName) {
29 | if (!simpleName.endsWith(RESOURCE_SUFFIX_9_PATCH)) {
30 | return false;
31 | }
32 | for (String type : RESOURCE_TYPE_IMG) {
33 | if (typeName.equals(type)) {
34 | return true;
35 | }
36 | }
37 | return false;
38 | }
39 |
40 | public static boolean is9PatchResource(ZipPath zipPath) {
41 | if (!zipPath.startsWith(BundleModule.RESOURCES_DIRECTORY)) {
42 | return false;
43 | }
44 | return zipPath.toString().endsWith(RESOURCE_SUFFIX_9_PATCH);
45 | }
46 |
47 | /**
48 | * 获取 .9 资源的名称
49 | *
50 | * @param simpleName 资源名称(不包含文件后缀),如 a.9 aa
51 | * @return 如:a.9 => a
52 | */
53 | public static String get9PatchSimpleName(String simpleName) {
54 | return simpleName.substring(0, simpleName.length() - RESOURCE_SUFFIX_9_PATCH.length());
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/utils/FileUtils.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.utils;
2 |
3 | import com.google.common.base.Charsets;
4 | import com.google.common.base.Joiner;
5 | import com.google.common.io.Files;
6 |
7 | import java.io.File;
8 | import java.io.IOException;
9 | import java.nio.charset.StandardCharsets;
10 |
11 | import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable;
12 |
13 | /**
14 | * Created by YangJing on 2019/10/18 .
15 | * Email: yangjing.yeoh@bytedance.com
16 | */
17 | public class FileUtils {
18 | private static final Joiner UNIX_NEW_LINE_JOINER = Joiner.on('\n');
19 |
20 | /**
21 | * Loads a text file forcing the line separator to be of Unix style '\n' rather than being
22 | * Windows style '\r\n'.
23 | */
24 | public static String loadFileWithUnixLineSeparators(File file) throws IOException {
25 | checkFileExistsAndReadable(file.toPath());
26 | return UNIX_NEW_LINE_JOINER.join(Files.readLines(file, Charsets.UTF_8));
27 | }
28 |
29 | /**
30 | * Creates a new text file or replaces content of an existing file.
31 | *
32 | * @param file the file to write to
33 | * @param content the new content of the file
34 | */
35 | public static void writeToFile(File file, String content) throws IOException {
36 | Files.createParentDirs(file);
37 | Files.asCharSink(file, StandardCharsets.UTF_8).write(content);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/utils/Pair.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.bytedance.android.aabresguard.utils;
18 |
19 | /**
20 | * A Pair class is simply a 2-tuple for use in this package. We might want to
21 | * think about adding something like this to a more central utility place, or
22 | * replace it by a common tuple class if one exists, or even rewrite the layout
23 | * classes using this Pair by a more dedicated data structure (so we don't have
24 | * to pass around generic signatures as is currently done, though at least the
25 | * construction is helped a bit by the {@link #of} factory method.
26 | *
27 | * @param < S > The type of the first value
28 | * @param < T > The type of the second value
29 | */
30 | public final class Pair {
31 | private final S mFirst;
32 | private final T mSecond;
33 |
34 | // Use {@link Pair#of} factory instead since it infers generic types
35 | private Pair(S first, T second) {
36 | this.mFirst = first;
37 | this.mSecond = second;
38 | }
39 |
40 | /**
41 | * Return the first item in the pair
42 | *
43 | * @return the first item in the pair
44 | */
45 | public S getFirst() {
46 | return mFirst;
47 | }
48 |
49 | /**
50 | * Return the second item in the pair
51 | *
52 | * @return the second item in the pair
53 | */
54 | public T getSecond() {
55 | return mSecond;
56 | }
57 |
58 | /**
59 | * Constructs a new pair of the given two objects, inferring generic types.
60 | *
61 | * @param first the first item to store in the pair
62 | * @param second the second item to store in the pair
63 | * @param < S > the type of the first item
64 | * @param < T > the type of the second item
65 | * @return a new pair wrapping the two items
66 | */
67 | public static Pair of(S first, T second) {
68 | return new Pair(first, second);
69 | }
70 |
71 | @Override
72 | public String toString() {
73 | return "Pair [first=" + mFirst + ", second=" + mSecond + "]";
74 | }
75 |
76 | @Override
77 | public int hashCode() {
78 | final int prime = 31;
79 | int result = 1;
80 | result = prime * result + ((mFirst == null) ? 0 : mFirst.hashCode());
81 | result = prime * result + ((mSecond == null) ? 0 : mSecond.hashCode());
82 | return result;
83 | }
84 |
85 | @SuppressWarnings("unchecked")
86 | @Override
87 | public boolean equals(Object obj) {
88 | if (this == obj)
89 | return true;
90 | if (obj == null)
91 | return false;
92 | if (getClass() != obj.getClass())
93 | return false;
94 | Pair other = (Pair) obj;
95 | if (mFirst == null) {
96 | if (other.mFirst != null)
97 | return false;
98 | } else if (!mFirst.equals(other.mFirst))
99 | return false;
100 | if (mSecond == null) {
101 | if (other.mSecond != null)
102 | return false;
103 | } else if (!mSecond.equals(other.mSecond))
104 | return false;
105 | return true;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/utils/TimeClock.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.utils;
2 |
3 | /**
4 | * Created by YangJing on 2019/04/19 .
5 | * Email: yangjing.yeoh@bytedance.com
6 | */
7 | public class TimeClock {
8 |
9 | private long startTime;
10 |
11 | public TimeClock() {
12 | startTime = System.currentTimeMillis();
13 | }
14 |
15 | public String getCoast() {
16 | return (System.currentTimeMillis() - startTime) + "";
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/core/src/main/java/com/bytedance/android/aabresguard/utils/exception/CommandExceptionPreconditions.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.utils.exception;
2 |
3 | import com.android.tools.build.bundletool.flags.Flag;
4 | import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException;
5 |
6 | import java.util.Optional;
7 |
8 | /**
9 | * Created by YangJing on 2019/10/10 .
10 | * Email: yangjing.yeoh@bytedance.com
11 | */
12 | public final class CommandExceptionPreconditions {
13 |
14 | public static void checkFlagPresent(Object object, Flag flag) {
15 | if (object instanceof Optional) {
16 | object = ((Optional) object).get();
17 | }
18 | Optional.of(object).orElseThrow(() -> CommandExecutionException.builder()
19 | .withMessage("Wrong properties: %s can not be empty", flag)
20 | .build());
21 | }
22 |
23 | public static void checkStringIsEmpty(String value, String name) {
24 | if (value == null || value.trim().isEmpty()) {
25 | throw new IllegalArgumentException(String.format("Wrong properties: %s can not be empty", name));
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/.gitignore:
--------------------------------------------------------------------------------
1 | issues/bytedance/
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/AabResGuardMainTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard;
2 |
3 | import org.junit.Test;
4 |
5 | /**
6 | * Created by YangJing on 2019/10/16 .
7 | * Email: yangjing.yeoh@bytedance.com
8 | */
9 | public class AabResGuardMainTest extends BaseTest {
10 |
11 | @Test
12 | public void test_help() {
13 | AabResGuardMain.main(
14 | new String[]{
15 | "help"
16 | }
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/BaseTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard;
2 |
3 |
4 | import com.bytedance.android.aabresguard.testing.ProcessThread;
5 |
6 | import org.junit.After;
7 | import org.junit.Before;
8 | import org.junit.Rule;
9 | import org.junit.Test;
10 | import org.junit.rules.TemporaryFolder;
11 | import org.junit.runner.RunWith;
12 | import org.junit.runners.JUnit4;
13 |
14 | import java.io.File;
15 | import java.io.InputStream;
16 | import java.io.Reader;
17 | import java.nio.file.Path;
18 |
19 | /**
20 | * Created by YangJing on 2019/04/14 .
21 | * Email: yangjing.yeoh@bytedance.com
22 | */
23 | @RunWith(JUnit4.class)
24 | public class BaseTest {
25 |
26 | private static final boolean DEBUG = true;
27 |
28 | @Rule
29 | public final TemporaryFolder tmp = new TemporaryFolder(); // 注意,临时文件夹在执行完毕之后会被自动删除!
30 |
31 | private Path tmpDir;
32 | private long startTime;
33 |
34 | protected static File loadResourceFile(String path) {
35 | return TestData.openFile(path);
36 | }
37 |
38 | protected static Reader loadResourceReader(String path) {
39 | return TestData.openReader(path);
40 | }
41 |
42 | protected static InputStream loadResourceStream(String path) {
43 | return TestData.openStream(path);
44 | }
45 |
46 | protected static String loadResourcePath(String path) {
47 | return TestData.resourcePath(path);
48 | }
49 |
50 | protected static boolean executeCmd(String cmd, Object... objects) {
51 | return ProcessThread.execute(cmd, objects);
52 | }
53 |
54 | protected static void openDir(String dir, Object... objects) {
55 | if (!DEBUG) {
56 | return;
57 | }
58 | ProcessThread.execute("open " + dir, objects);
59 | }
60 |
61 | @Before
62 | public void setUp() throws Exception {
63 | tmpDir = tmp.getRoot().toPath();
64 | startTime = System.currentTimeMillis();
65 | }
66 |
67 | @After
68 | public void tearDown() {
69 | System.out.println(System.currentTimeMillis() - startTime);
70 | }
71 |
72 | /**
73 | * 返回临时路径
74 | */
75 | protected Path getTempDirPath() {
76 | return tmpDir;
77 | }
78 |
79 | protected String getTempDirFilePath() {
80 | return tmpDir.toFile().toString();
81 | }
82 |
83 | @Test
84 | public void emptyTest() {
85 | assert true;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/TestData.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard;/*
2 | * Copyright (C) 2017 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 |
17 | import com.google.common.io.ByteStreams;
18 |
19 | import java.io.File;
20 | import java.io.FileInputStream;
21 | import java.io.FileNotFoundException;
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 | import java.io.InputStreamReader;
25 | import java.io.Reader;
26 | import java.io.UncheckedIOException;
27 | import java.net.MalformedURLException;
28 | import java.net.URL;
29 |
30 | import static com.google.common.base.Preconditions.checkArgument;
31 | import static java.nio.charset.StandardCharsets.UTF_8;
32 |
33 | /**
34 | * Provides centralized access to testdata files.
35 | *
36 | *
The {@code fileName} argument always starts with the "testdata/" directory.
37 | */
38 | @SuppressWarnings("WeakerAccess")
39 | class TestData {
40 |
41 | private static final String PACKAGE = "com/bytedance/android/aabresguard/";
42 |
43 | private TestData() {
44 | }
45 |
46 | static URL getUrl(String path) {
47 | URL dirURL = TestData.class.getResource(path);
48 | if (dirURL != null && dirURL.getProtocol().equals("file")) {
49 | return dirURL;
50 | }
51 | if (dirURL == null) {
52 | String className = TestData.class.getName().replace(".", "/") + ".class";
53 | dirURL = TestData.class.getClassLoader().getResource(className);
54 | String classPath = dirURL.getFile();
55 | classPath = classPath.substring(0, classPath.indexOf("/build/"));
56 | classPath = "file:" + classPath + "/src/test/resources/" + PACKAGE + path;
57 | try {
58 | dirURL = new URL(classPath);
59 | } catch (MalformedURLException e) {
60 | e.printStackTrace();
61 | }
62 | }
63 | return dirURL;
64 | }
65 |
66 | static InputStream openStream(String fileName) {
67 | InputStream is = null;
68 | try {
69 | is = new FileInputStream(new File(getUrl(fileName).getFile()));
70 |
71 | } catch (FileNotFoundException e) {
72 | e.printStackTrace();
73 | }
74 | checkArgument(is != null, "Testdata file '%s' not found.", fileName);
75 | return is;
76 | }
77 |
78 | static Reader openReader(String fileName) {
79 | return new InputStreamReader(openStream(fileName), UTF_8);
80 | }
81 |
82 | @SuppressWarnings("UnstableApiUsage")
83 | static byte[] readBytes(String fileName) {
84 | try (InputStream inputStream = openStream(fileName)) {
85 | return ByteStreams.toByteArray(inputStream);
86 | } catch (IOException e) {
87 | // Throw an unchecked exception to allow usage in lambda expressions.
88 | throw new UncheckedIOException(
89 | String.format("Failed to read contents of com.bytedance.android.aabresguard file '%s'.", fileName), e);
90 | }
91 | }
92 |
93 | static File openFile(String fileName) {
94 | String filePath = getUrl(fileName).getFile();
95 | checkArgument(filePath != null, "Testdata file '%s' not found.", fileName);
96 | return new File(filePath);
97 | }
98 |
99 | static String resourcePath(String resourceName) {
100 | return openFile(resourceName).getPath();
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/bundle/AppBundlePackagerTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.bundle;
2 |
3 | import com.android.tools.build.bundletool.model.AppBundle;
4 | import com.bytedance.android.aabresguard.BaseTest;
5 |
6 | import org.junit.Test;
7 |
8 | import java.io.File;
9 | import java.io.IOException;
10 | import java.util.zip.ZipFile;
11 |
12 | /**
13 | * Created by YangJing on 2019/10/11 .
14 | * Email: yangjing.yeoh@bytedance.com
15 | */
16 | public class AppBundlePackagerTest extends BaseTest {
17 |
18 | @Test
19 | public void testPackageAppBundle() throws IOException {
20 | File output = new File(getTempDirPath().toFile(), "package.aab");
21 | AppBundle appBundle = AppBundle.buildFromZip(new ZipFile(loadResourceFile("demo/demo.aab")));
22 | AppBundlePackager packager = new AppBundlePackager(appBundle, output.toPath());
23 | packager.execute();
24 | assert output.exists();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/bundle/AppBundleSignerTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.bundle;
2 |
3 | import com.bytedance.android.aabresguard.BaseTest;
4 | import com.bytedance.android.aabresguard.utils.FileOperation;
5 |
6 | import org.junit.Test;
7 |
8 | import java.io.File;
9 | import java.io.IOException;
10 |
11 | /**
12 | * Created by YangJing on 2019/10/11 .
13 | * Email: yangjing.yeoh@bytedance.com
14 | */
15 | public class AppBundleSignerTest extends BaseTest {
16 |
17 | @Test
18 | public void testAppBundleSigner() throws IOException, InterruptedException {
19 | File output = new File(getTempDirPath().toFile(), "signed.aab");
20 | FileOperation.copyFileUsingStream(loadResourceFile("demo/demo.aab"), output);
21 | AppBundleSigner signer = new AppBundleSigner(output.toPath());
22 | signer.execute();
23 | assert output.exists();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/bundle/AppBundleUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.bundle;
2 |
3 | import com.bytedance.android.aabresguard.BaseTest;
4 |
5 | import org.junit.Test;
6 |
7 | import static junit.framework.TestCase.assertEquals;
8 |
9 | /**
10 | * Created by YangJing on 2019/11/03 .
11 | * Email: yangjing.yeoh@bytedance.com
12 | */
13 | public class AppBundleUtilsTest extends BaseTest {
14 |
15 | @Test
16 | public void test_getEntryNameByResourceName() {
17 | assertEquals(AppBundleUtils.getEntryNameByResourceName("a.b.c.R.drawable.a"), "a");
18 | }
19 |
20 | @Test
21 | public void test_getTypeNameByResourceName() {
22 | assertEquals(AppBundleUtils.getTypeNameByResourceName("a.b.c.R.drawable.a"), "drawable");
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/commands/DuplicatedResourcesMergerCommandTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.commands;
2 |
3 | import com.android.tools.build.bundletool.flags.Flag;
4 | import com.android.tools.build.bundletool.flags.FlagParser;
5 | import com.bytedance.android.aabresguard.BaseTest;
6 | import com.bytedance.android.aabresguard.utils.FileOperation;
7 |
8 | import org.junit.Test;
9 |
10 | import java.io.File;
11 | import java.io.IOException;
12 |
13 | import static com.google.common.truth.Truth.assertThat;
14 | import static org.junit.jupiter.api.Assertions.assertThrows;
15 |
16 | /**
17 | * Created by YangJing on 2019/10/11 .
18 | * Email: yangjing.yeoh@bytedance.com
19 | */
20 | public class DuplicatedResourcesMergerCommandTest extends BaseTest {
21 |
22 | @Test
23 | public void test_noFlag() {
24 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
25 | () -> DuplicatedResourcesMergerCommand.fromFlags(
26 | new FlagParser().parse(
27 | ""
28 | )
29 | ).execute());
30 | assertThat(flagsException)
31 | .hasMessageThat()
32 | .matches("Missing the required --bundle flag.");
33 | }
34 |
35 | @Test
36 | public void test_no_bundle() {
37 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
38 | () -> DuplicatedResourcesMergerCommand.fromFlags(
39 | new FlagParser().parse(
40 | "--output=" + getTempDirFilePath()
41 | )
42 | ).execute());
43 | assertThat(flagsException)
44 | .hasMessageThat()
45 | .matches("Missing the required --bundle flag.");
46 | }
47 |
48 | @Test
49 | public void test_no_Output() {
50 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
51 | () -> DuplicatedResourcesMergerCommand.fromFlags(
52 | new FlagParser().parse(
53 | "--bundle=" + loadResourcePath("demo/demo.aab")
54 | )
55 | ).execute());
56 | assertThat(flagsException)
57 | .hasMessageThat()
58 | .matches("Missing the required --output flag.");
59 | }
60 |
61 | @Test
62 | public void test_notExists_bundle() {
63 | String tempPath = getTempDirFilePath();
64 | File apkFile = new File(tempPath + "abc.apk");
65 | IllegalArgumentException flagsException = assertThrows(IllegalArgumentException.class,
66 | () -> DuplicatedResourcesMergerCommand.fromFlags(
67 | new FlagParser().parse(
68 | "--bundle=" + apkFile.getAbsolutePath(),
69 | "--output=" + getTempDirFilePath()
70 | )
71 | ).execute());
72 | assertThat(flagsException)
73 | .hasMessageThat()
74 | .matches(String.format("File '%s' was not found.", apkFile.getAbsolutePath()));
75 | }
76 |
77 | @Test
78 | public void test_wrong_params() {
79 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
80 | () -> DuplicatedResourcesMergerCommand.fromFlags(
81 | new FlagParser().parse(
82 | "--abc=a"
83 | )
84 | ).execute());
85 | assertThat(flagsException)
86 | .hasMessageThat()
87 | .matches("Missing the required --bundle flag.");
88 | }
89 |
90 | @Test
91 | public void test_disableSign() throws IOException, InterruptedException {
92 | File rawAabFile = loadResourceFile("demo/demo.aab");
93 | File outputFile = new File(getTempDirPath().toFile(), "duplicated.aab");
94 | DuplicatedResourcesMergerCommand.fromFlags(
95 | new FlagParser().parse(
96 | "--bundle=" + rawAabFile.getAbsolutePath(),
97 | "--output=" + outputFile.getAbsolutePath(),
98 | "--disable-sign=true"
99 | )
100 | ).execute();
101 | assert outputFile.exists();
102 | }
103 |
104 | @Test
105 | public void testMergeDuplicatedRes() throws IOException, InterruptedException {
106 | File rawAabFile = loadResourceFile("demo/demo.aab");
107 | File outputFile = new File(getTempDirPath().toFile(), "duplicated.aab");
108 | DuplicatedResourcesMergerCommand.fromFlags(
109 | new FlagParser().parse(
110 | "--bundle=" + rawAabFile.getAbsolutePath(),
111 | "--output=" + outputFile.getAbsolutePath()
112 | )
113 | ).execute();
114 | assert outputFile.exists();
115 | assert FileOperation.getFileSizes(rawAabFile) > FileOperation.getFileSizes(outputFile);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/commands/FileFilterCommandTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.commands;
2 |
3 | import com.android.tools.build.bundletool.flags.Flag;
4 | import com.android.tools.build.bundletool.flags.FlagParser;
5 | import com.bytedance.android.aabresguard.BaseTest;
6 | import com.bytedance.android.aabresguard.utils.FileOperation;
7 |
8 | import org.dom4j.DocumentException;
9 | import org.junit.Test;
10 |
11 | import java.io.File;
12 | import java.io.IOException;
13 |
14 | import static com.google.common.truth.Truth.assertThat;
15 | import static org.junit.jupiter.api.Assertions.assertThrows;
16 |
17 | /**
18 | * Created by YangJing on 2019/10/14 .
19 | * Email: yangjing.yeoh@bytedance.com
20 | */
21 | public class FileFilterCommandTest extends BaseTest {
22 | @Test
23 | public void test_noFlag() {
24 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
25 | () -> FileFilterCommand.fromFlags(
26 | new FlagParser().parse(
27 | ""
28 | )
29 | ).execute());
30 | assertThat(flagsException)
31 | .hasMessageThat()
32 | .matches("Missing the required --bundle flag.");
33 | }
34 |
35 | @Test
36 | public void test_no_bundle() {
37 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
38 | () -> FileFilterCommand.fromFlags(
39 | new FlagParser().parse(
40 | "--output=" + getTempDirFilePath()
41 | )
42 | ).execute());
43 | assertThat(flagsException)
44 | .hasMessageThat()
45 | .matches("Missing the required --bundle flag.");
46 | }
47 |
48 | @Test
49 | public void test_no_Config() {
50 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
51 | () -> FileFilterCommand.fromFlags(
52 | new FlagParser().parse(
53 | "--bundle=" + loadResourcePath("demo/demo.aab")
54 | )
55 | ).execute());
56 | assertThat(flagsException)
57 | .hasMessageThat()
58 | .matches("Missing the required --config flag.");
59 | }
60 |
61 |
62 | @Test
63 | public void test_wrong_params() {
64 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
65 | () -> FileFilterCommand.fromFlags(
66 | new FlagParser().parse(
67 | "--abc=a"
68 | )
69 | ).execute());
70 | assertThat(flagsException)
71 | .hasMessageThat()
72 | .matches("Missing the required --bundle flag.");
73 | }
74 |
75 | @Test
76 | public void test_disableSign() throws IOException, DocumentException, InterruptedException {
77 | File rawAabFile = loadResourceFile("demo/demo.aab");
78 | File outputFile = new File(getTempDirPath().toFile(), "filtered.aab");
79 | FileFilterCommand.fromFlags(
80 | new FlagParser().parse(
81 | "--bundle=" + rawAabFile.getAbsolutePath(),
82 | "--output=" + outputFile.getAbsolutePath(),
83 | "--config="+loadResourcePath("demo/config-filter.xml"),
84 | "--disable-sign=true"
85 | )
86 | ).execute();
87 | assert outputFile.exists();
88 | }
89 |
90 | @Test
91 | public void testPass() throws IOException, DocumentException, InterruptedException {
92 | File rawAabFile = loadResourceFile("demo/demo.aab");
93 | File outputFile = new File(getTempDirPath().toFile(), "filtered.aab");
94 | FileFilterCommand.fromFlags(
95 | new FlagParser().parse(
96 | "--bundle=" + rawAabFile.getAbsolutePath(),
97 | "--output=" + outputFile.getAbsolutePath(),
98 | "--config="+loadResourcePath("demo/config-filter.xml")
99 | )
100 | ).execute();
101 | assert outputFile.exists();
102 | assert FileOperation.getFileSizes(rawAabFile) > FileOperation.getFileSizes(outputFile);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/commands/ObfuscateBundleCommandTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.commands;
2 |
3 | import com.android.tools.build.bundletool.flags.Flag;
4 | import com.android.tools.build.bundletool.flags.FlagParser;
5 | import com.bytedance.android.aabresguard.BaseTest;
6 | import com.bytedance.android.aabresguard.testing.BundleToolOperation;
7 | import com.bytedance.android.aabresguard.utils.FileOperation;
8 |
9 | import org.dom4j.DocumentException;
10 | import org.junit.Test;
11 |
12 | import java.io.File;
13 | import java.io.IOException;
14 | import java.nio.file.Path;
15 |
16 | import static com.google.common.truth.Truth.assertThat;
17 | import static org.junit.jupiter.api.Assertions.assertThrows;
18 |
19 | /**
20 | * Created by YangJing on 2019/10/15 .
21 | * Email: yangjing.yeoh@bytedance.com
22 | */
23 | public class ObfuscateBundleCommandTest extends BaseTest {
24 | @Test
25 | public void test_noFlag() {
26 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
27 | () -> ObfuscateBundleCommand.fromFlags(
28 | new FlagParser().parse(
29 | ""
30 | )
31 | ).execute());
32 | assertThat(flagsException)
33 | .hasMessageThat()
34 | .matches("Missing the required --bundle flag.");
35 | }
36 |
37 | @Test
38 | public void test_no_bundle() {
39 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
40 | () -> ObfuscateBundleCommand.fromFlags(
41 | new FlagParser().parse(
42 | "--output=" + getTempDirFilePath()
43 | )
44 | ).execute());
45 | assertThat(flagsException)
46 | .hasMessageThat()
47 | .matches("Missing the required --bundle flag.");
48 | }
49 |
50 | @Test
51 | public void test_no_Config() {
52 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
53 | () -> ObfuscateBundleCommand.fromFlags(
54 | new FlagParser().parse(
55 | "--bundle=" + loadResourcePath("demo/demo.aab")
56 | )
57 | ).execute());
58 | assertThat(flagsException)
59 | .hasMessageThat()
60 | .matches("Missing the required --config flag.");
61 | }
62 |
63 |
64 | @Test
65 | public void test_wrong_params() {
66 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
67 | () -> ObfuscateBundleCommand.fromFlags(
68 | new FlagParser().parse(
69 | "--abc=a"
70 | )
71 | ).execute());
72 | assertThat(flagsException)
73 | .hasMessageThat()
74 | .matches("Missing the required --bundle flag.");
75 | }
76 |
77 | @Test
78 | public void test_DisableSign() throws DocumentException, IOException, InterruptedException {
79 | File rawAabFile = loadResourceFile("demo/demo.aab");
80 | File outputFile = new File(getTempDirPath().toFile(), "obfuscated.aab");
81 | ObfuscateBundleCommand.fromFlags(
82 | new FlagParser().parse(
83 | "--bundle=" + rawAabFile.getAbsolutePath(),
84 | "--output=" + outputFile.getAbsolutePath(),
85 | "--config=" + loadResourcePath("demo/config.xml"),
86 | "--merge-duplicated-res=true",
87 | "--mapping=" + loadResourcePath("demo/mapping.txt"),
88 | "--disable-sign=true"
89 | )
90 | ).execute();
91 | assert outputFile.exists();
92 | }
93 |
94 | @Test
95 | public void testPass() throws IOException, DocumentException, InterruptedException {
96 | File rawAabFile = loadResourceFile("demo/demo.aab");
97 | File outputFile = new File(getTempDirPath().toFile(), "obfuscated.aab");
98 | ObfuscateBundleCommand.fromFlags(
99 | new FlagParser().parse(
100 | "--bundle=" + rawAabFile.getAbsolutePath(),
101 | "--output=" + outputFile.getAbsolutePath(),
102 | "--config=" + loadResourcePath("demo/config.xml"),
103 | "--merge-duplicated-res=true",
104 | "--mapping=" + loadResourcePath("demo/mapping.txt")
105 | )
106 | ).execute();
107 | assert outputFile.exists();
108 | assert FileOperation.getFileSizes(rawAabFile) > FileOperation.getFileSizes(outputFile);
109 |
110 | Path apkPath = BundleToolOperation.buildApkByBundle(outputFile.toPath(), getTempDirPath());
111 | assert apkPath.toFile().exists();
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/commands/StringFilterCommandTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.commands;
2 |
3 | import com.android.tools.build.bundletool.flags.Flag;
4 | import com.android.tools.build.bundletool.flags.FlagParser;
5 | import com.bytedance.android.aabresguard.BaseTest;
6 | import com.bytedance.android.aabresguard.utils.FileOperation;
7 |
8 | import org.dom4j.DocumentException;
9 | import org.junit.Test;
10 |
11 | import java.io.File;
12 | import java.io.IOException;
13 |
14 | import static com.google.common.truth.Truth.assertThat;
15 | import static org.junit.jupiter.api.Assertions.assertThrows;
16 |
17 | /**
18 | * Created by jiangzilai on 2019-10-20.
19 | */
20 | public class StringFilterCommandTest extends BaseTest {
21 | @Test
22 | public void test_noFlag() {
23 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
24 | () -> StringFilterCommand.fromFlags(
25 | new FlagParser().parse(
26 | ""
27 | )
28 | ).execute());
29 | assertThat(flagsException)
30 | .hasMessageThat()
31 | .matches("Missing the required --bundle flag.");
32 | }
33 |
34 | @Test
35 | public void test_no_bundle() {
36 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
37 | () -> StringFilterCommand.fromFlags(
38 | new FlagParser().parse(
39 | "--output=" + getTempDirFilePath()
40 | )
41 | ).execute());
42 | assertThat(flagsException)
43 | .hasMessageThat()
44 | .matches("Missing the required --bundle flag.");
45 | }
46 |
47 | @Test
48 | public void test_no_Config() {
49 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
50 | () -> StringFilterCommand.fromFlags(
51 | new FlagParser().parse(
52 | "--bundle=" + loadResourcePath("demo/app.aab")
53 | )
54 | ).execute());
55 | assertThat(flagsException)
56 | .hasMessageThat()
57 | .matches("Missing the required --config flag.");
58 | }
59 |
60 |
61 | @Test
62 | public void test_wrong_params() {
63 | Flag.RequiredFlagNotSetException flagsException = assertThrows(Flag.RequiredFlagNotSetException.class,
64 | () -> StringFilterCommand.fromFlags(
65 | new FlagParser().parse(
66 | "--abc=a"
67 | )
68 | ).execute());
69 | assertThat(flagsException)
70 | .hasMessageThat()
71 | .matches("Missing the required --bundle flag.");
72 | }
73 |
74 | @Test
75 | public void testPass() throws IOException, DocumentException, InterruptedException {
76 | File rawAabFile = loadResourceFile("demo/app.aab");
77 | File outputFile = new File(getTempDirPath().toFile(), "filtered.aab");
78 | StringFilterCommand.fromFlags(
79 | new FlagParser().parse(
80 | "--bundle=" + rawAabFile.getAbsolutePath(),
81 | "--output=" + outputFile.getAbsolutePath(),
82 | "--config="+loadResourcePath("demo/config.xml")
83 | )
84 | ).execute();
85 | assert outputFile.exists();
86 | assert FileOperation.getFileSizes(rawAabFile) >= FileOperation.getFileSizes(outputFile);
87 | }
88 | }
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/executors/BundleFileFilterTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.executors;
2 |
3 | import com.android.tools.build.bundletool.model.AppBundle;
4 | import com.bytedance.android.aabresguard.BaseTest;
5 | import com.bytedance.android.aabresguard.bundle.AppBundleAnalyzer;
6 | import com.google.common.collect.ImmutableSet;
7 |
8 | import org.junit.Test;
9 |
10 | import java.io.IOException;
11 | import java.nio.file.Path;
12 | import java.util.HashSet;
13 |
14 | /**
15 | * Created by YangJing on 2019/10/14 .
16 | * Email: yangjing.yeoh@bytedance.com
17 | */
18 | public class BundleFileFilterTest extends BaseTest {
19 |
20 | @Test
21 | public void test() throws IOException {
22 | Path bundlePath = loadResourceFile("demo/demo.aab").toPath();
23 | AppBundleAnalyzer analyzer = new AppBundleAnalyzer(bundlePath);
24 | AppBundle appBundle = analyzer.analyze();
25 | ImmutableSet filterRules = ImmutableSet.of(
26 | "*/arm64-v8a/*"
27 | );
28 | BundleFileFilter fileFilter = new BundleFileFilter(loadResourceFile("demo/demo.aab").toPath(), appBundle, new HashSet<>(filterRules));
29 | AppBundle filteredAppBundle = fileFilter.filter();
30 | assert filteredAppBundle != null;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/executors/BundleStringFilterTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.executors;
2 |
3 | import com.android.tools.build.bundletool.model.AppBundle;
4 | import com.bytedance.android.aabresguard.BaseTest;
5 | import com.bytedance.android.aabresguard.bundle.AppBundleAnalyzer;
6 |
7 | import org.junit.Test;
8 |
9 | import java.io.IOException;
10 | import java.nio.file.Path;
11 | import java.util.HashSet;
12 |
13 | /**
14 | * Created by jiangzilai on 2019-10-20.
15 | */
16 | public class BundleStringFilterTest extends BaseTest {
17 |
18 | @Test
19 | public void test() throws IOException {
20 | Path bundlePath = loadResourceFile("demo/demo.aab").toPath();
21 | AppBundleAnalyzer analyzer = new AppBundleAnalyzer(bundlePath);
22 | AppBundle appBundle = analyzer.analyze();
23 | BundleStringFilter filter = new BundleStringFilter(loadResourceFile("demo/demo.aab").toPath(), appBundle,
24 | loadResourceFile("demo/unused.txt").toPath().toString(), new HashSet<>());
25 | AppBundle filteredAppBundle = filter.filter();
26 | assert filteredAppBundle != null;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/executors/DuplicatedResourcesMergerTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.executors;
2 |
3 | import com.android.tools.build.bundletool.model.AppBundle;
4 | import com.bytedance.android.aabresguard.BaseTest;
5 | import com.bytedance.android.aabresguard.bundle.AppBundleAnalyzer;
6 |
7 | import org.junit.Test;
8 |
9 | import java.io.IOException;
10 | import java.nio.file.Path;
11 |
12 | /**
13 | * Created by YangJing on 2019/10/10 .
14 | * Email: yangjing.yeoh@bytedance.com
15 | */
16 | public class DuplicatedResourcesMergerTest extends BaseTest {
17 |
18 | @Test
19 | public void test() throws IOException {
20 | Path outputDirPath = getTempDirPath();
21 | Path bundlePath = loadResourceFile("demo/demo.aab").toPath();
22 | AppBundle rawAppBundle = new AppBundleAnalyzer(bundlePath).analyze();
23 | DuplicatedResourcesMerger merger = new DuplicatedResourcesMerger(bundlePath, rawAppBundle, outputDirPath);
24 | AppBundle appBundle = merger.merge();
25 | assert appBundle != null;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/executors/ResourcesObfuscatorTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.executors;
2 |
3 | import com.android.tools.build.bundletool.model.AppBundle;
4 | import com.android.tools.build.bundletool.model.BundleModule;
5 | import com.bytedance.android.aabresguard.BaseTest;
6 | import com.bytedance.android.aabresguard.bundle.AppBundleAnalyzer;
7 | import com.google.common.collect.ImmutableSet;
8 |
9 | import org.junit.Test;
10 |
11 | import java.io.IOException;
12 | import java.nio.file.Path;
13 | import java.util.HashSet;
14 | import java.util.Set;
15 |
16 | /**
17 | * Created by YangJing on 2019/10/14 .
18 | * Email: yangjing.yeoh@bytedance.com
19 | */
20 | public class ResourcesObfuscatorTest extends BaseTest {
21 |
22 | @Test
23 | public void test() throws IOException {
24 | Set whiteList = new HashSet<>(
25 | ImmutableSet.of(
26 | "com.bytedance.android.ugc.aweme.R.raw.*",
27 | "*.R.drawable.icon",
28 | "*.R.anim.ab*"
29 | )
30 | );
31 | Path bundlePath = loadResourceFile("demo/demo.aab").toPath();
32 | Path outputDir = getTempDirPath();
33 | AppBundleAnalyzer analyzer = new AppBundleAnalyzer(bundlePath);
34 | AppBundle appBundle = analyzer.analyze();
35 | ResourcesObfuscator obfuscator = new ResourcesObfuscator(bundlePath, appBundle, whiteList, outputDir, loadResourceFile("demo/mapping.txt").toPath());
36 | AppBundle obfuscateAppBundle = obfuscator.obfuscate();
37 | assert obfuscateAppBundle != null;
38 | assert obfuscateAppBundle.getModules().size() == appBundle.getModules().size();
39 | appBundle.getModules().forEach((bundleModuleName, bundleModule) -> {
40 | BundleModule obfuscatedModule = obfuscateAppBundle.getModule(bundleModuleName);
41 | assert obfuscatedModule.getEntries().size() == bundleModule.getEntries().size();
42 | });
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/issues/i1/Issue1Test.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.issues.i1;
2 |
3 | import com.android.tools.build.bundletool.flags.FlagParser;
4 | import com.bytedance.android.aabresguard.BaseTest;
5 | import com.bytedance.android.aabresguard.commands.ObfuscateBundleCommand;
6 | import com.bytedance.android.aabresguard.testing.BundleToolOperation;
7 |
8 | import org.dom4j.DocumentException;
9 | import org.junit.Test;
10 |
11 | import java.io.File;
12 | import java.io.IOException;
13 | import java.nio.file.Path;
14 |
15 | /**
16 | * Created by YangJing on 2019/10/31 .
17 | * Email: yangjing.yeoh@bytedance.com
18 | */
19 | public class Issue1Test extends BaseTest {
20 |
21 | private Path loadIssueResourcePath(String name) {
22 | return loadResourceFile("issues/i1/" + name).toPath();
23 | }
24 |
25 | /**
26 | * 增量混淆后生成的 aab 无法被解析,错误信息:
27 | * Caused by: java.util.concurrent.ExecutionException: com.android.tools.build.bundletool.model.Aapt2Command$Aapt2Exception:
28 | * Command '[/var/folders/sc/1qy4dyz527vf0j1qg5h2r0b40000gn/T/2712062020451125499/output/macos/aapt2, convert, --output-format, binary, -o, /var/folders/sc/1qy4dyz527vf0j1qg5h2r0b40000gn/T/5211613263510034859/binary.apk, /var/folders/sc/1qy4dyz527vf0j1qg5h2r0b40000gn/T/5211613263510034859/proto.apk]'
29 | * didn't terminate successfully (exit code: 1). Check the logs.
30 | *
31 | * aapt2 convert --output-format binary -o binary.apk proto.apk
32 | * proto.apk: error: failed to deserialize resources.pb: duplicate configuration in resource table.
33 | * proto.apk: error: failed to load APK.
34 | */
35 | @Test
36 | public void test() throws DocumentException, IOException, InterruptedException {
37 | Path bundlePath = loadIssueResourcePath("raw.aab");
38 | File outputFile = new File(getTempDirPath().toFile(), "obfuscated.aab");
39 | ObfuscateBundleCommand.fromFlags(
40 | new FlagParser().parse(
41 | "--bundle=" + bundlePath.toFile().getAbsolutePath(),
42 | "--output=" + outputFile.getAbsolutePath(),
43 | "--config=" + loadIssueResourcePath("config.xml"),
44 | "--merge-duplicated-res=true",
45 | "--mapping=" + loadIssueResourcePath("mapping.txt")
46 | )
47 | ).execute();
48 | assert outputFile.exists();
49 |
50 | Path apkPath = BundleToolOperation.buildApkByBundle(outputFile.toPath(), getTempDirPath());
51 | assert apkPath.toFile().exists();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/parser/AabResGuardXmlParserTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.parser;
2 |
3 | import com.bytedance.android.aabresguard.BaseTest;
4 | import com.bytedance.android.aabresguard.model.xml.AabResGuardConfig;
5 |
6 | import org.dom4j.DocumentException;
7 | import org.junit.Test;
8 |
9 | /**
10 | * Created by YangJing on 2019/10/14 .
11 | * Email: yangjing.yeoh@bytedance.com
12 | */
13 | public class AabResGuardXmlParserTest extends BaseTest {
14 |
15 | @Test
16 | public void test() throws DocumentException {
17 | AabResGuardXmlParser parser = new AabResGuardXmlParser(loadResourceFile("demo/config.xml").toPath());
18 | AabResGuardConfig config = parser.parse();
19 | assert config != null;
20 | assert config.isUseWhiteList();
21 | assert config.getFileFilter() != null;
22 | assert config.getFileFilter().getRules().size() == 2;
23 | assert config.getWhiteList().size() == 1;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/parser/FileFilterXmlParserTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.parser;
2 |
3 | import com.bytedance.android.aabresguard.BaseTest;
4 | import com.bytedance.android.aabresguard.model.xml.FileFilterConfig;
5 |
6 | import org.dom4j.DocumentException;
7 | import org.junit.Test;
8 |
9 | /**
10 | * Created by YangJing on 2019/10/14 .
11 | * Email: yangjing.yeoh@bytedance.com
12 | */
13 | public class FileFilterXmlParserTest extends BaseTest {
14 |
15 | @Test
16 | public void test() throws DocumentException {
17 | FileFilterXmlParser parser = new FileFilterXmlParser(loadResourceFile("demo/config-filter.xml").toPath());
18 | FileFilterConfig fileFilter = parser.parse();
19 | assert fileFilter != null;
20 | assert fileFilter.isActive();
21 | assert fileFilter.getRules().size() == 2;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/parser/ResourcesMappingParserTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.parser;
2 |
3 | import com.bytedance.android.aabresguard.BaseTest;
4 | import com.bytedance.android.aabresguard.model.ResourcesMapping;
5 |
6 | import org.junit.Test;
7 |
8 | import java.io.IOException;
9 |
10 | /**
11 | * Created by YangJing on 2019/10/14 .
12 | * Email: yangjing.yeoh@bytedance.com
13 | */
14 | public class ResourcesMappingParserTest extends BaseTest {
15 |
16 | @Test
17 | public void test() throws IOException {
18 | ResourcesMappingParser parser = new ResourcesMappingParser(loadResourceFile("demo/mapping.txt").toPath());
19 | ResourcesMapping mapping = parser.parse();
20 |
21 | assert !mapping.getDirMapping().isEmpty();
22 | assert !mapping.getResourceMapping().isEmpty();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/parser/StringFilterXmlParserTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.parser;
2 |
3 | import com.bytedance.android.aabresguard.BaseTest;
4 | import com.bytedance.android.aabresguard.model.xml.AabResGuardConfig;
5 |
6 | import org.dom4j.DocumentException;
7 | import org.junit.Test;
8 |
9 | /**
10 | * Created by jiangzilai on 2019-10-20.
11 | */
12 | public class StringFilterXmlParserTest extends BaseTest {
13 |
14 | @Test
15 | public void test() throws DocumentException {
16 | AabResGuardXmlParser parser = new AabResGuardXmlParser(loadResourceFile("demo/config.xml").toPath());
17 | AabResGuardConfig config = parser.parse();
18 | System.out.println(config.getStringFilterConfig().toString());
19 | // assert config != null;
20 | // assert config.isUseWhiteList();
21 | // assert config.getFileFilter() != null;
22 | // assert config.getFileFilter().getRules().size() == 2;
23 | // assert config.getWhiteList().size() == 1;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/testing/Aapt2Helper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package com.bytedance.android.aabresguard.testing;
17 |
18 | import com.android.tools.build.bundletool.model.Aapt2Command;
19 | import com.google.common.collect.ObjectArrays;
20 |
21 | import java.nio.file.Path;
22 | import java.nio.file.Paths;
23 |
24 | /** Helper for tests using aapt2. */
25 | public final class Aapt2Helper {
26 |
27 | public static final String AAPT2_PATH =
28 | System.getenv("AAPT2_PATH");
29 |
30 | public static Aapt2Command getAapt2Command() {
31 | return Aapt2Command.createFromExecutablePath(Paths.get(AAPT2_PATH));
32 | }
33 |
34 | public static void convertBinaryApkToProtoApk(Path binaryApk, Path protoApk) {
35 | runAapt2(
36 | "convert", "--output-format", "proto", "-o", protoApk.toString(), binaryApk.toString());
37 | }
38 |
39 | private static void runAapt2(String... command) {
40 | new Aapt2Command.CommandExecutor().execute(ObjectArrays.concat(AAPT2_PATH, command));
41 | }
42 |
43 | private Aapt2Helper() {}
44 | }
45 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/testing/BundleToolOperation.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.testing;
2 |
3 | import com.android.tools.build.bundletool.commands.BuildApksCommand;
4 | import com.android.tools.build.bundletool.commands.ExtractApksCommand;
5 | import com.android.tools.build.bundletool.device.AdbServer;
6 | import com.android.tools.build.bundletool.device.DdmlibAdbServer;
7 | import com.android.tools.build.bundletool.flags.FlagParser;
8 | import com.bytedance.android.aabresguard.BaseTest;
9 |
10 | import java.io.File;
11 | import java.nio.file.Path;
12 |
13 | import static com.bytedance.android.aabresguard.testing.Aapt2Helper.AAPT2_PATH;
14 |
15 | /**
16 | * Created by YangJing on 2019/10/15 .
17 | * Email: yangjing.yeoh@bytedance.com
18 | */
19 | public class BundleToolOperation extends BaseTest {
20 |
21 | public static Path buildApkByBundle(Path bundlePath, Path apkDirPath) {
22 | // build apks
23 | Path apksPath = new File(apkDirPath.toFile(), "app.apks").toPath();
24 | Path deviceSpecPath = loadResourceFile("device-spec/armeabi-v7a_sdk16.json").toPath();
25 | AdbServer adbServer = DdmlibAdbServer.getInstance();
26 | BuildApksCommand.fromFlags(
27 | new FlagParser().parse(
28 | "--bundle=" + bundlePath.toFile().getAbsolutePath(),
29 | "--output=" + apksPath.toFile(),
30 | "--device-spec=" + deviceSpecPath.toFile(),
31 | "--aapt2=" + AAPT2_PATH
32 | ),
33 | adbServer
34 | ).execute();
35 | assert apksPath.toFile().exists();
36 | // extract apks
37 | ExtractApksCommand.fromFlags(
38 | new FlagParser().parse(
39 | "--apks=" + apksPath.toFile().getAbsolutePath(),
40 | "--output-dir=" + apkDirPath.toFile(),
41 | "--device-spec=" + deviceSpecPath.toFile()
42 | )
43 | ).execute();
44 | File[] apkList = apkDirPath.toFile().listFiles((file, s) -> s.endsWith(".apk"));
45 | assert apkList != null;
46 | assert apkList.length > 0;
47 | return apkList[0].toPath();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/testing/ProcessThread.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.testing;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.io.InputStreamReader;
7 |
8 | /**
9 | * Used to execute shell cmd
10 | *
11 | * Created by YangJing on 2019/04/11 .
12 | * Email: yangjing.yeoh@bytedance.com
13 | */
14 | public class ProcessThread extends Thread {
15 |
16 |
17 | private InputStream is;
18 | private String printType;
19 |
20 | ProcessThread(InputStream is, String printType) {
21 | this.is = is;
22 | this.printType = printType;
23 | }
24 |
25 | public static boolean execute(String cmd) {
26 | try {
27 | Process process = Runtime.getRuntime().exec(cmd);
28 | new ProcessThread(process.getInputStream(), "INFO").start();
29 | new ProcessThread(process.getErrorStream(), "ERR").start();
30 | int value = process.waitFor();
31 | return value == 0;
32 | } catch (IOException e) {
33 | e.printStackTrace();
34 | } catch (InterruptedException e) {
35 | e.printStackTrace();
36 | }
37 | return false;
38 | }
39 |
40 | public static boolean execute(String cmd, Object... objects) {
41 | return execute(String.format(cmd, objects));
42 | }
43 |
44 | @Override
45 | public void run() {
46 | try {
47 | InputStreamReader isr = new InputStreamReader(is);
48 | BufferedReader br = new BufferedReader(isr);
49 | String line = null;
50 | while ((line = br.readLine()) != null) {
51 | if (printType == "ERR") {
52 | // System.out.println(printType + ">" + line);
53 | }
54 | System.out.println(printType + ">" + line);
55 | }
56 | } catch (IOException ioe) {
57 | ioe.printStackTrace();
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/core/src/test/java/com/bytedance/android/aabresguard/utils/FileOperationTest.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.aabresguard.utils;
2 |
3 |
4 | import com.bytedance.android.aabresguard.BaseTest;
5 |
6 | import org.junit.Test;
7 |
8 | import java.io.File;
9 | import java.io.IOException;
10 | import java.nio.file.Path;
11 |
12 | import static junit.framework.TestCase.assertEquals;
13 |
14 | /**
15 | * Created by YangJing on 2019/04/10 .
16 | * Email: yangjing.yeoh@bytedance.com
17 | */
18 | public class FileOperationTest extends BaseTest {
19 |
20 | @Test
21 | public void testUnZip() throws IOException {
22 | File aabFile = loadResourceFile("demo/demo.aab");
23 | Path unzipDirPath = getTempDirPath();
24 | Path targetDir = new File(getTempDirPath().toFile(), "/aab").toPath();
25 | FileOperation.uncompress(aabFile.toPath(), targetDir);
26 | System.out.println("testUnZip method coast:");
27 | FileOperation.uncompress(aabFile.toPath(), unzipDirPath);
28 | }
29 |
30 | @Test
31 | public void testDrawNinePatchName() {
32 | assertEquals(FileOperation.getParentFromZipFilePath("res/a/a.9.png"), "res/a");
33 | assertEquals(FileOperation.getNameFromZipFilePath("res/a/a.9.png"), "a.9.png");
34 | assertEquals(FileOperation.getFilePrefixByFileName("a.9.png"), "a");
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/core/src/test/resources/com/bytedance/android/aabresguard/.gitignore:
--------------------------------------------------------------------------------
1 | issues/bytedance/
--------------------------------------------------------------------------------
/core/src/test/resources/com/bytedance/android/aabresguard/demo/config-filter.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/core/src/test/resources/com/bytedance/android/aabresguard/demo/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/core/src/test/resources/com/bytedance/android/aabresguard/demo/demo.aab:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytedance/AabResGuard/e8f3a5d361ce61a3d4fa8bafb9d030bbe459c400/core/src/test/resources/com/bytedance/android/aabresguard/demo/demo.aab
--------------------------------------------------------------------------------
/core/src/test/resources/com/bytedance/android/aabresguard/demo/mapping.txt:
--------------------------------------------------------------------------------
1 | res path mapping:
2 | res/anim -> res/a
3 | res/drawable-v23 -> res/b
4 | res/drawable-ldrtl-xxxhdpi-v17 -> res/c
5 | res/drawable-xxhdpi-v4 -> res/d
6 | res/drawable-ldrtl-mdpi-v17 -> res/e
7 | res/transition -> res/f
8 | res/color-v23 -> res/g
9 | res/drawable-night-xhdpi-v8 -> res/h
10 | res/drawable-nodpi-v4 -> res/i
11 | res/layout-v22 -> res/j
12 | res/layout-land-v17 -> res/k
13 | res/layout-land -> res/l
14 | res/interpolator -> res/m
15 | res/mipmap-xxxhdpi-v4 -> res/n
16 | res/drawable -> res/o
17 | res/mipmap-hdpi-v4 -> res/p
18 | res/layout-watch-v20 -> res/q
19 | res/mipmap-xhdpi-v4 -> res/r
20 | res/drawable-ldrtl-xxhdpi-v17 -> res/s
21 | res/layout -> res/t
22 | res/drawable-xhdpi-v4 -> res/u
23 | res/color -> res/v
24 | res/layout-sw600dp-v13 -> res/w
25 | res/animator-v21 -> res/x
26 | res/animator-v19 -> res/y
27 | res/anim-land -> res/z
28 | res/animator -> res/a0
29 | res/drawable-xxxhdpi-v4 -> res/a1
30 | res/xml -> res/a2
31 | res/drawable-ldrtl-xhdpi-v17 -> res/a3
32 | res/color-v21 -> res/a4
33 | res/drawable-v21 -> res/a5
34 | res/drawable-night-v8 -> res/a6
35 | res/drawable-night-xxhdpi-v8 -> res/a7
36 | res/drawable-anydpi-v21 -> res/a8
37 | res/drawable-mdpi-v4 -> res/a9
38 | res/layout-v26 -> res/a_
39 | res/layout-v19 -> res/aa
40 | res/layout-v21 -> res/ab
41 | res/menu -> res/ac
42 | res/layout-v17 -> res/ad
43 | res/drawable-watch-v20 -> res/ae
44 | res/mipmap-xxhdpi-v4 -> res/af
45 | res/anim-v21 -> res/ag
46 | res/layout-v16 -> res/ah
47 | res/drawable-hdpi-v4 -> res/ai
48 | res/interpolator-v21 -> res/aj
49 | res/raw -> res/ak
50 | res/drawable-ldpi-v4 -> res/al
51 | res/drawable-ldrtl-hdpi-v17 -> res/am
52 |
53 |
54 | res id mapping:
55 | 0x7e01000a : com.bytedance.android.ugc.dynamic_feature.R.attr.pressedStateOverlayImage -> com.bytedance.android.ugc.dynamic_feature.R.attr.k
56 | 0x7e01000e : com.bytedance.android.ugc.dynamic_feature.R.attr.retryImage -> com.bytedance.android.ugc.dynamic_feature.R.attr.o
57 | 0x7f0c0099 : com.bytedance.android.ugc.R.style.Base.Widget.AppCompat.SearchView.ActionBar -> com.bytedance.android.ugc.R.style.df
58 | 0x7f070061 : com.bytedance.android.ugc.R.id.right_icon -> com.bytedance.android.ugc.R.id.bx
59 | 0x7f05000f : com.bytedance.android.ugc.R.dimen.abc_action_button_min_width_overflow_material -> com.bytedance.android.ugc.R.dimen.p
60 | 0x7f0c00fc : com.bytedance.android.ugc.R.style.Theme.AppCompat.DayNight.NoActionBar -> com.bytedance.android.ugc.R.style.g4
61 |
62 |
63 | res entries path mapping:
64 | 0x7f010000 : base/res/anim/abc_fade_in.xml -> res/a/a.xml
65 | 0x7f010001 : base/res/anim/abc_fade_out.xml -> res/a/b.xml
66 | 0x7f040002 : base/res/color-v21/abc_btn_colored_borderless_text_material.xml -> res/a4/a.xml
67 | 0x7f060036 : base/res/drawable-xxhdpi-v4/abc_popup_background_mtrl_mult.9.png -> res/d/a2.9.png
68 | 0x7e020000 : dynamic_feature/res/drawable-xhdpi-v4/df_xh.png -> res/u/a.png
69 | 0x7e020002 : dynamic_feature/res/drawable-xxxhdpi-v4/df_xxxh.png -> res/a1/a.png
70 |
71 |
--------------------------------------------------------------------------------
/core/src/test/resources/com/bytedance/android/aabresguard/demo/test.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytedance/AabResGuard/e8f3a5d361ce61a3d4fa8bafb9d030bbe459c400/core/src/test/resources/com/bytedance/android/aabresguard/demo/test.apk
--------------------------------------------------------------------------------
/core/src/test/resources/com/bytedance/android/aabresguard/demo/unused.txt:
--------------------------------------------------------------------------------
1 | abc_action_bar_home_description
2 | abc_action_bar_up_description
3 | abc_action_menu_overflow_description
4 | abc_action_mode_done
--------------------------------------------------------------------------------
/core/src/test/resources/com/bytedance/android/aabresguard/device-spec/armeabi-v7a_sdk16.json:
--------------------------------------------------------------------------------
1 | {
2 | "supportedAbis": ["armeabi-v7a"],
3 | "supportedLocales": ["zh-CN", "en-US", "ja-JP", "zh-HK", "zh-TW"],
4 | "screenDensity": 480,
5 | "sdkVersion": 16
6 | }
7 |
--------------------------------------------------------------------------------
/core/src/test/resources/com/bytedance/android/aabresguard/issues/i1/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/core/src/test/resources/com/bytedance/android/aabresguard/issues/i1/mapping.txt:
--------------------------------------------------------------------------------
1 | res dir mapping:
2 | res/layout -> res/v
3 | res/drawable-xxhdpi-v4 -> res/i
4 | res/layout-v17 -> res/w
5 | res/drawable-ldpi-v4 -> res/t
6 | res/mipmap-mdpi-v4 -> res/a3
7 | res/layout-v16 -> res/a1
8 | res/drawable -> res/k
9 | res/drawable-ldrtl-mdpi-v17 -> res/o
10 | res/mipmap-anydpi-v26 -> res/a2
11 | res/mipmap-hdpi-v4 -> res/a4
12 | res/mipmap-xxxhdpi-v4 -> res/a7
13 | res/drawable-ldrtl-xxxhdpi-v17 -> res/s
14 | res/drawable-watch-v20 -> res/n
15 | res/mipmap-xhdpi-v4 -> res/a5
16 | res/drawable-xxxhdpi-v4 -> res/l
17 | res/drawable-ldrtl-hdpi-v17 -> res/p
18 | res/drawable-ldrtl-xxhdpi-v17 -> res/r
19 | res/drawable-anydpi-v21 -> res/u
20 | res/drawable-hdpi-v4 -> res/g
21 | res/drawable-v24 -> res/e
22 | res/color -> res/b
23 | res/layout-watch-v20 -> res/x
24 | res/layout-v22 -> res/y
25 | res/layout-v21 -> res/z
26 | res/drawable-mdpi-v4 -> res/f
27 | res/drawable-v23 -> res/m
28 | res/layout-v26 -> res/a0
29 | res/drawable-v21 -> res/j
30 | res/drawable-ldrtl-xhdpi-v17 -> res/q
31 | res/drawable-xhdpi-v4 -> res/h
32 | res/color-v21 -> res/c
33 | res/color-v23 -> res/d
34 | res/anim -> res/a
35 | res/mipmap-xxhdpi-v4 -> res/a6
36 |
37 |
38 | res id mapping:
39 | 0x7e01000a : com.ss.android.ugc.dynamic_feature.R.attr.pressedStateOverlayImage -> com.ss.android.ugc.dynamic_feature.R.attr.k
40 | 0x7f02011c : com.ss.android.ugc.R.attr.textAppearanceSearchResultTitle -> com.ss.android.ugc.R.attr.gz
41 | 0x7f0c00d6 : com.ss.android.ugc.R.style.TextAppearance.AppCompat.Title -> com.ss.android.ugc.R.style.f3
42 | 0x7f060062 : com.ss.android.ugc.R.drawable.notification_bg_normal -> com.ss.android.ugc.R.drawable.by
43 |
44 |
45 | res entries path mapping:
46 | 0x7f060031 : base/res/drawable-xxhdpi-v4/abc_list_selector_disabled_holo_dark.9.png -> res/i/z.9.png
47 | 0x7f060023 : base/res/drawable-xxxhdpi-v4/abc_ic_star_half_black_16dp.png -> res/l/o.png
48 | 0x7f09001a : base/res/layout/abc_select_dialog_material.xml -> res/v/a0.xml
49 | 0x7f01000a : base/res/anim/abc_tooltip_enter.xml -> res/a/k.xml
50 |
--------------------------------------------------------------------------------
/core/src/test/resources/com/bytedance/android/aabresguard/issues/i1/raw.aab:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytedance/AabResGuard/e8f3a5d361ce61a3d4fa8bafb9d030bbe459c400/core/src/test/resources/com/bytedance/android/aabresguard/issues/i1/raw.aab
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | #maven
15 | # maven
16 | NEXUS_USERNAME=
17 | USERNAME=
18 | NEXUS_PASSWORD=
19 | PASSWORD=
20 | RELEASE_REPOSITORY_URL=
21 | SNAPSHOT_REPOSITORY_URL=
22 | PUBLISH_LOCAL_REPO=../repo
23 | # 是否使用本地 maven 依赖
24 | useLocalMaven=false
25 | # 是否源码依赖
26 | useSource=false
27 | enableAabResGuardPlugin=true
28 | # bintray
29 | uploadToBintray=true
30 | bintrayInfo.user=****
31 | bintrayInfo.apiKey=***************
32 |
--------------------------------------------------------------------------------
/gradle/aabresguard.gradle:
--------------------------------------------------------------------------------
1 | if (!"true".equalsIgnoreCase(System.getProperty("enableAabResGuardPlugin", "false"))) {
2 | return
3 | }
4 |
5 | apply plugin: "com.bytedance.android.aabResGuard"
6 | aabResGuard {
7 | enableObfuscate = true
8 | mappingFile = file("../mapping.txt").toPath()
9 | whiteList = [
10 | // keep resources
11 | "*.R.raw.*",
12 | "*.R.drawable.icon",
13 | "*.R.drawable.ic_*",
14 | "*.R.anim.abc*",
15 | "*.R.xml.actions",
16 | // keep resource file
17 | "*/res/xml/actions.xml",
18 | ]
19 | obfuscatedBundleFileName = "obfuscated-app.aab"
20 | mergeDuplicatedRes = false
21 | enableFilterFiles = true
22 | filterList = [
23 | "*/arm64-v8a/*"
24 | ]
25 | enableFilterStrings = true
26 | unusedStringPath = "core/src/test/resources/com/bytedance/android/aabresguard/demo/unused.txt"
27 | languageWhiteList = ["en", "zh"]
28 | }
29 |
--------------------------------------------------------------------------------
/gradle/config.gradle:
--------------------------------------------------------------------------------
1 | loadSystemProperties(rootProject.file("gradle.properties"))
2 | loadSystemProperties(rootProject.file("local.properties"))
3 |
4 | apply from: 'gradle/ext.gradle'
5 | apply from: 'gradle/versions.gradle'
6 |
7 | allprojects {
8 | tasks.withType(Javadoc) {
9 | options.addStringOption('Xdoclint:none', '-quiet')
10 | options.addStringOption('encoding', 'UTF-8')
11 | }
12 | }
13 |
14 | static void loadSystemProperties(File path) {
15 | if (!path.exists()) {
16 | return
17 | }
18 | Properties properties = new Properties()
19 | properties.load(path.newDataInputStream())
20 | System.properties.putAll(properties)
21 | }
22 |
--------------------------------------------------------------------------------
/gradle/ext.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | GROUP_ID = "com.bytedance.android"
3 | bintrayInfo = [
4 | repo : [
5 | name : "AabResGuard",
6 | groupId : GROUP_ID,
7 | artifactId : "aabresguard",
8 | description: "The tool of obfuscated aab resources",
9 | packaging : "jar",
10 | siteUrl : "https://github.com/bytedance/AabResGuard",
11 | gitUrl : "https://github.com/bytedance/AabResGuard.git",
12 | userOrg : System.getProperty("bintrayInfo.userOrg", "****"),
13 | ],
14 | developer: [
15 | id : "JingYeoh",
16 | name : "JingYeoh",
17 | email : "yangjing.yeoh@gmail.com",
18 | user : System.getProperty("bintrayInfo.user", "****"),
19 | apikey: System.getProperty("bintrayInfo.apiKey", "*************")
20 | ],
21 | javadoc : [
22 | name: "AabResGuard"
23 | ]
24 | ]
25 | }
26 |
27 | // 读取 local.properties 文件内容
28 | def localFile = project.rootProject.file("local.properties")
29 | if (localFile.exists()) {
30 | println "======== read local.properties ========"
31 | Properties properties = new Properties()
32 | properties.load(localFile.newDataInputStream())
33 | // 逐行读取文件
34 | localFile.eachLine { line ->
35 | if (line.startsWith("#")) {
36 | return
37 | }
38 | def arr = line.split("=")
39 | ext.set(arr[0], arr[1])
40 | System.setProperty(arr[0], arr[1])
41 | }
42 | ext.properties.keySet().forEach {
43 | println "\t${it} : ${ext.get(it)}"
44 | }
45 | println "======== read local.properties ========"
46 | }
47 |
--------------------------------------------------------------------------------
/gradle/gradle-chrome-trace.gradle:
--------------------------------------------------------------------------------
1 | initscript {
2 | dependencies {
3 | classpath files("tools/gradle-chrome-trace.jar")
4 | }
5 | }
6 |
7 | rootProject {
8 | ext.chromeTraceFile = new File(rootProject.buildDir, "trace.html")
9 | }
10 |
11 | apply plugin: org.gradle.trace.GradleTracingPlugin
12 |
--------------------------------------------------------------------------------
/gradle/publish-bintray.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.jfrog.bintray'
2 | if (System.getProperty("uploadToBintray", "false").equalsIgnoreCase("true")) {
3 | bintray {
4 | user = bintrayInfo.developer.user
5 | key = bintrayInfo.developer.apikey
6 | pkg {
7 | repo = 'maven'
8 | name = ARTIFACT_ID
9 | websiteUrl = bintrayInfo.repo.siteUrl
10 | vcsUrl = bintrayInfo.repo.gitUrl
11 | licenses = ["Apache-2.0"]
12 | publish = true
13 | userOrg = bintrayInfo.repo.userOrg
14 |
15 | version {
16 | name = versions[ARTIFACT_ID]
17 | released = new Date()
18 | vcsTag = versions[ARTIFACT_ID]
19 | }
20 | }
21 | publications = ['MyPublication']
22 | }
23 | }
--------------------------------------------------------------------------------
/gradle/publish-shadow.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'digital.wup.android-maven-publish'
2 |
3 | task sourcesJar(type: Jar) {
4 | from sourceSets.main.java.srcDirs
5 | classifier = 'sources'
6 | version = versions[ARTIFACT_ID]
7 | }
8 |
9 | task javadocJar(type: Jar, dependsOn: javadoc) {
10 | classifier = 'javadoc'
11 | from javadoc.destinationDir
12 | version = versions[ARTIFACT_ID]
13 | }
14 |
15 | def releasesRepoUrl = System.getProperty("RELEASE_REPOSITORY_URL")
16 | def snapshotsRepoUrl = System.getProperty("SNAPSHOT_REPOSITORY_URL")
17 | def publishUrl = versions[ARTIFACT_ID].endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
18 | def publishUserName = System.getProperty("NEXUS_USERNAME")
19 | def publishPassword = System.getProperty("NEXUS_PASSWORD")
20 |
21 | println "-------publish info---------"
22 | println "url: $publishUrl"
23 | println "username: $publishUserName"
24 | println "password: $publishPassword"
25 |
26 | apply plugin: 'maven-publish'
27 |
28 | def pomConfig = {
29 | licenses {
30 | license {
31 | name "The Apache Software License, Version 2.0"
32 | url "http://www.apache.org/licenses/LICENSE-2.0.txt"
33 | distribution "repo"
34 | }
35 | }
36 | developers {
37 | developer {
38 | id bintrayInfo.developer.id
39 | name bintrayInfo.developer.name
40 | email bintrayInfo.developer.email
41 | }
42 | }
43 | scm {
44 | url bintrayInfo.repo.siteUrl
45 | }
46 | }
47 |
48 | publishing {
49 | publications {
50 | MyPublication(MavenPublication) { publication ->
51 | project.shadow.component(publication)
52 | groupId GROUP_ID
53 | artifactId ARTIFACT_ID
54 | version versions[ARTIFACT_ID]
55 |
56 | artifact sourcesJar
57 | artifact javadocJar
58 |
59 | pom.withXml {
60 | def root = asNode()
61 | root.appendNode('description', bintrayInfo.repo.description)
62 | root.appendNode('name', bintrayInfo.repo.name)
63 | root.appendNode('url', bintrayInfo.repo.siteUrl)
64 | root.children().last() + pomConfig
65 | }
66 | }
67 | }
68 |
69 | if (!System.getProperty("uploadToBintray", "false").equalsIgnoreCase("true")) {
70 | repositories {
71 | maven {
72 | url = publishUrl
73 | credentials {
74 | username = publishUserName
75 | password = publishPassword
76 | }
77 | }
78 | }
79 | }
80 | }
81 |
82 | apply from: "$rootDir/gradle/publish-bintray.gradle"
83 |
--------------------------------------------------------------------------------
/gradle/publish.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'digital.wup.android-maven-publish'
2 |
3 | task sourcesJar(type: Jar) {
4 | from sourceSets.main.java.srcDirs
5 | classifier = 'sources'
6 | version = versions[ARTIFACT_ID]
7 | }
8 |
9 | task javadocJar(type: Jar, dependsOn: javadoc) {
10 | classifier = 'javadoc'
11 | from javadoc.destinationDir
12 | version = versions[ARTIFACT_ID]
13 | }
14 |
15 | def releasesRepoUrl = System.getProperty("RELEASE_REPOSITORY_URL")
16 | def snapshotsRepoUrl = System.getProperty("SNAPSHOT_REPOSITORY_URL")
17 | def publishUrl = versions[ARTIFACT_ID].endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
18 | def publishUserName = System.getProperty("NEXUS_USERNAME")
19 | def publishPassword = System.getProperty("NEXUS_PASSWORD")
20 |
21 | println "-------publish info---------"
22 | println "url: $publishUrl"
23 | println "username: $publishUserName"
24 | println "password: $publishPassword"
25 |
26 | publishing {
27 | if (!System.getProperty("uploadToBintray", "false").equalsIgnoreCase("true")) {
28 | repositories {
29 | maven {
30 | url = publishUrl
31 | credentials {
32 | username = publishUserName
33 | password = publishPassword
34 | }
35 | }
36 | if (System.getProperty("useLocalMaven", "false").equalsIgnoreCase("true")) {
37 | maven {
38 | url = "${rootProject.projectDir}/repo"
39 | }
40 | }
41 | }
42 | }
43 |
44 | if (project.plugins.hasPlugin("java-library") || project.plugins.hasPlugin("java")) {
45 | publications {
46 | MyPublication(MavenPublication) {
47 | from components.java
48 | artifact sourcesJar
49 | artifact javadocJar
50 |
51 | groupId GROUP_ID
52 | artifactId ARTIFACT_ID
53 | version versions[ARTIFACT_ID]
54 | }
55 | }
56 | } else {
57 | throw new GradleScriptException("java-library plugin required")
58 | }
59 | }
60 |
61 | apply from: "$rootDir/gradle/publish-bintray.gradle"
--------------------------------------------------------------------------------
/gradle/tools/gradle-chrome-trace.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytedance/AabResGuard/e8f3a5d361ce61a3d4fa8bafb9d030bbe459c400/gradle/tools/gradle-chrome-trace.jar
--------------------------------------------------------------------------------
/gradle/versions.gradle:
--------------------------------------------------------------------------------
1 | def versions = [:]
2 | versions.agp = "4.1.0"
3 | versions.kotlin = "1.3.61"
4 | versions.java = "8"
5 | versions.aabresguard = "0.1.9"
6 | // android
7 | versions.compileSdkVersion = 28
8 | versions.minSdkVersion = 15
9 | versions.targetSdkVersion = 28
10 | versions.support = "28.0.0"
11 |
12 | versions.bundletool = "0.10.0"
13 |
14 | versions["aabresguard-core"] = versions.aabresguard
15 | versions["aabresguard-plugin"] = versions.aabresguard
16 |
17 | // plugin
18 | versions["bintray-plugin"] = "1.8.4"
19 | versions.shadow = "4.0.4"
20 | versions.digital = "3.4.0"
21 |
22 | ext.versions = versions
23 |
24 | ext.deps = [:]
25 | // gradle
26 | def gradle = [:]
27 | gradle.agp = "com.android.tools.build:gradle:${versions.agp}"
28 | deps.gradle = gradle
29 | // kotlin
30 | def kotlin = [:]
31 | kotlin.stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlin"
32 | kotlin.plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin"
33 | kotlin.allopen = "org.jetbrains.kotlin:kotlin-allopen:$versions.kotlin"
34 | kotlin.embeddable = "org.jetbrains.kotlin:kotlin-compiler-embeddable:$versions.kotlin"
35 | deps.kotlin = kotlin
36 | // plugin
37 | def plugin = [:]
38 | plugin.digital = "digital.wup:android-maven-publish:${versions.digital}"
39 | plugin.shadow = "com.github.jengelman.gradle.plugins:shadow:${versions.shadow}"
40 | plugin['bintray-plugin'] = "com.jfrog.bintray.gradle:gradle-bintray-plugin:${versions["bintray-plugin"]}"
41 | deps.plugin = plugin
42 | // library
43 | deps.appcompatV7 = "com.android.support:appcompat-v7:${versions.support}"
44 | deps.bundletool = "com.android.tools.build:bundletool:${versions.bundletool}"
45 | // aabresguard
46 | def aabresguard = [:]
47 | aabresguard.core = "$GROUP_ID:aabresguard-core:${versions["aabresguard"]}"
48 | aabresguard.plugin = "$GROUP_ID:aabresguard-plugin:${versions["aabresguard"]}"
49 | deps.aabresguard = aabresguard
50 |
51 | ext.deps = deps
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytedance/AabResGuard/e8f3a5d361ce61a3d4fa8bafb9d030bbe459c400/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Oct 15 17:54:10 CST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | #distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
7 | #distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
8 | #distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-milestone-2-all.zip
9 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
10 | #distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
11 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/plugin/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/plugin/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java-library'
2 | apply plugin: 'kotlin'
3 | apply plugin: "groovy"
4 | apply from: rootProject.file('gradle/publish.gradle')
5 |
6 | dependencies {
7 | implementation fileTree(dir: 'libs', include: ['*.jar'])
8 | implementation gradleApi()
9 | implementation localGroovy()
10 | compileOnly deps.gradle.agp
11 |
12 | implementation deps.kotlin.stdlib
13 | implementation deps.kotlin.plugin
14 | compileOnly deps.aabresguard.core
15 | api(deps.aabresguard.core) {
16 | exclude group: "com.google.guava", module: "guava"
17 | exclude group: "com.android.tools.build", module: "gradle"
18 | }
19 | }
20 |
21 | configurations {
22 | all*.exclude group: "com.android.tools.build", module: "bundletool"
23 | }
24 |
25 | sourceCompatibility = versions.java
26 | targetCompatibility = versions.java
27 |
--------------------------------------------------------------------------------
/plugin/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | #publish
15 |
16 | ARTIFACT_ID=aabresguard-plugin
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/bytedance/android/plugin/AabResGuardPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.plugin
2 |
3 | import com.android.build.gradle.AppExtension
4 | import com.android.build.gradle.api.ApplicationVariant
5 | import com.bytedance.android.plugin.extensions.AabResGuardExtension
6 | import com.bytedance.android.plugin.tasks.AabResGuardTask
7 | import org.gradle.api.GradleException
8 | import org.gradle.api.Plugin
9 | import org.gradle.api.Project
10 | import org.gradle.api.Task
11 |
12 | /**
13 | * Created by YangJing on 2019/10/15 .
14 | * Email: yangjing.yeoh@bytedance.com
15 | */
16 | class AabResGuardPlugin : Plugin {
17 |
18 | override fun apply(project: Project) {
19 | checkApplicationPlugin(project)
20 | project.extensions.create("aabResGuard", AabResGuardExtension::class.java)
21 |
22 | val android = project.extensions.getByName("android") as AppExtension
23 | project.afterEvaluate {
24 | android.applicationVariants.all { variant ->
25 | createAabResGuardTask(project, variant)
26 | }
27 | }
28 | }
29 |
30 | private fun createAabResGuardTask(project: Project, variant: ApplicationVariant) {
31 | val variantName = variant.name.capitalize()
32 | val bundleTaskName = "bundle$variantName"
33 | if (project.tasks.findByName(bundleTaskName) == null) {
34 | return
35 | }
36 | val aabResGuardTaskName = "aabresguard$variantName"
37 | val aabResGuardTask: AabResGuardTask
38 | aabResGuardTask = if (project.tasks.findByName(aabResGuardTaskName) == null) {
39 | project.tasks.create(aabResGuardTaskName, AabResGuardTask::class.java)
40 | } else {
41 | project.tasks.getByName(aabResGuardTaskName) as AabResGuardTask
42 | }
43 | aabResGuardTask.setVariantScope(variant)
44 |
45 | val bundleTask: Task = project.tasks.getByName(bundleTaskName)
46 | val bundlePackageTask: Task = project.tasks.getByName("package${variantName}Bundle")
47 | bundleTask.dependsOn(aabResGuardTask)
48 | aabResGuardTask.dependsOn(bundlePackageTask)
49 | // AGP-4.0.0-alpha07: use FinalizeBundleTask to sign bundle file
50 | // FinalizeBundleTask is executed after PackageBundleTask
51 | val finalizeBundleTaskName = "sign${variantName}Bundle"
52 | if (project.tasks.findByName(finalizeBundleTaskName) != null) {
53 | aabResGuardTask.dependsOn(project.tasks.getByName(finalizeBundleTaskName))
54 | }
55 | }
56 |
57 | private fun checkApplicationPlugin(project: Project) {
58 | if (!project.plugins.hasPlugin("com.android.application")) {
59 | throw GradleException("Android Application plugin required")
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/bytedance/android/plugin/extensions/AabResGuardExtension.kt:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.plugin.extensions
2 |
3 | import java.nio.file.Path
4 |
5 | /**
6 | * Created by YangJing on 2019/10/15 .
7 | * Email: yangjing.yeoh@bytedance.com
8 | */
9 | open class AabResGuardExtension {
10 | var enableObfuscate: Boolean = true
11 | var mappingFile: Path? = null
12 | var whiteList: Set? = HashSet()
13 | lateinit var obfuscatedBundleFileName: String
14 | var mergeDuplicatedRes: Boolean = false
15 | var enableFilterFiles: Boolean = false
16 | var filterList: Set? = HashSet()
17 | var enableFilterStrings: Boolean = false
18 | var unusedStringPath: String? = ""
19 | var languageWhiteList: Set? = HashSet()
20 |
21 | override fun toString(): String {
22 | return "AabResGuardExtension\n" +
23 | "\tenableObfuscate=$enableObfuscate" +
24 | "\tmappingFile=$mappingFile" +
25 | "\twhiteList=${if (whiteList == null) null else whiteList}\n" +
26 | "\tobfuscatedBundleFileName=$obfuscatedBundleFileName\n" +
27 | "\tmergeDuplicatedRes=$mergeDuplicatedRes\n" +
28 | "\tenableFilterFiles=$enableFilterFiles\n" +
29 | "\tfilterList=${if (filterList == null) null else filterList}" +
30 | "\tenableFilterStrings=$enableFilterStrings\n" +
31 | "\tunusedStringPath=$unusedStringPath\n" +
32 | "\tlanguageWhiteoolean`List=${if (languageWhiteList == null) null else languageWhiteList}"
33 | }
34 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/bytedance/android/plugin/internal/AGPVersionResolution.kt:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.plugin.internal
2 |
3 | import org.gradle.api.GradleException
4 | import org.gradle.api.Project
5 | import org.gradle.api.initialization.dsl.ScriptHandler
6 | import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
7 |
8 | /**
9 | * Created by YangJing on 2020/04/13 .
10 | * Email: yangjing.yeoh@bytedance.com
11 | */
12 | internal fun getAGPVersion(project: Project): String {
13 | var agpVersion: String? = null
14 | for (artifact in project.rootProject.buildscript.configurations.getByName(ScriptHandler.CLASSPATH_CONFIGURATION)
15 | .resolvedConfiguration.resolvedArtifacts) {
16 | val identifier = artifact.id.componentIdentifier
17 | if (identifier is DefaultModuleComponentIdentifier) {
18 | if (identifier.group == "com.android.tools.build" || identifier.group.hashCode() == 432891823) {
19 | if (identifier.module == "gradle") {
20 | agpVersion = identifier.version
21 | }
22 | }
23 | }
24 | }
25 | if (agpVersion == null) {
26 | throw GradleException("get AGP version failed")
27 | }
28 | return agpVersion
29 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/bytedance/android/plugin/internal/BundleResolution.kt:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.plugin.internal
2 |
3 | import com.android.build.gradle.api.ApplicationVariant
4 | import com.android.build.gradle.internal.scope.VariantScope
5 | import org.gradle.api.Project
6 | import java.io.File
7 | import java.nio.file.Path
8 |
9 | /**
10 | * Created by YangJing on 2020/01/07 .
11 | * Email: yangjing.yeoh@bytedance.com
12 | */
13 | internal fun getBundleFilePath(project: Project, variant:ApplicationVariant): Path {
14 | val agpVersion = getAGPVersion(project)
15 | val flavor = variant.name
16 | return when {
17 | // AGP3.2.0 - 3.2.1: packageBundle task class is com.android.build.gradle.internal.tasks.BundleTask
18 | // AGP3.3.0 - 3.3.2: packageBundle task class is com.android.build.gradle.internal.tasks.PackageBundleTask
19 | agpVersion.startsWith("3.2") || agpVersion.startsWith("3.3") -> {
20 | getBundleFileForAGP32To33(project, flavor).toPath()
21 | }
22 | // AGP3.4.0+: use FinalizeBundleTask sign bundle file
23 | // packageBundle task bundleLocation is intermediates dir
24 | // The finalize bundle file path: FinalizeBundleTask.finalBundleLocation
25 | agpVersion.startsWith("3.4") || agpVersion.startsWith("3.5") -> {
26 | getBundleFileForAGP34To35(project, flavor).toPath()
27 | }
28 | // AGP4.0+: removed finalBundleLocation field, and finalBundleFile is public field
29 | else -> {
30 | getBundleFileForAGP40After(project, flavor).toPath()
31 | }
32 | }
33 | }
34 |
35 | fun getBundleFileForAGP32To33(project: Project, flavor: String): File {
36 | val bundleTaskName = "package${flavor.capitalize()}Bundle"
37 | val bundleTask = project.tasks.getByName(bundleTaskName)
38 | return File(bundleTask.property("bundleLocation") as File, bundleTask.property("fileName") as String)
39 | }
40 |
41 | fun getBundleFileForAGP34To35(project: Project, flavor: String): File {
42 | // use FinalizeBundleTask to sign bundle file
43 | val finalizeBundleTask = project.tasks.getByName("sign${flavor.capitalize()}Bundle")
44 | // FinalizeBundleTask.finalBundleFile is the final bundle path
45 | val location = finalizeBundleTask.property("finalBundleLocation") as File
46 | return File(location, finalizeBundleTask.property("finalBundleFileName") as String)
47 | }
48 |
49 | fun getBundleFileForAGP40After(project: Project, flavor: String): File {
50 | // use FinalizeBundleTask to sign bundle file
51 | val finalizeBundleTask = project.tasks.getByName("sign${flavor.capitalize()}Bundle")
52 | // FinalizeBundleTask.finalBundleFile is the final bundle path
53 | val bundleFile = finalizeBundleTask.property("finalBundleFile")
54 | val regularFile = bundleFile!!::class.java.getMethod("get").invoke(bundleFile)
55 | return regularFile::class.java.getMethod("getAsFile").invoke(regularFile) as File
56 | }
57 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/bytedance/android/plugin/internal/SigningConfigResolution.kt:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.plugin.internal
2 |
3 | import com.android.build.gradle.api.ApplicationVariant
4 | import com.bytedance.android.plugin.model.SigningConfig
5 | import org.gradle.api.Project
6 |
7 | /**
8 | * Created by YangJing on 2020/01/06 .
9 | * Email: yangjing.yeoh@bytedance.com
10 | */
11 | internal fun getSigningConfig(project: Project, variant: ApplicationVariant): SigningConfig {
12 | val agpVersion = getAGPVersion(project)
13 | // get signing config
14 | return when {
15 | // AGP3.2+: use VariantScope.getVariantConfiguration.getSigningConfig
16 | agpVersion.startsWith("3.") -> {
17 | getSigningConfigForAGP3(project, variant)
18 | }
19 | // AGP4.0+: VariantScope class removed getVariantConfiguration method.
20 | // VariantManager add getBuildTypes method
21 | // Use BuildType.getSigningConfig method to get signingConfig
22 | else -> {
23 | getSigningConfigForAGP4(agpVersion, project, variant)
24 | }
25 | }
26 | }
27 |
28 | private fun getSigningConfigForAGP3(project: Project, variant: ApplicationVariant): SigningConfig {
29 | return getSigningConfigByAppVariant(variant)
30 | }
31 |
32 | private fun getSigningConfigForAGP4(agpVersion: String, project: Project, variant: ApplicationVariant): SigningConfig {
33 | return getSigningConfigByAppVariant(variant)
34 | }
35 |
36 | private fun getSigningConfigByAppVariant(variant: ApplicationVariant): SigningConfig {
37 | return SigningConfig(variant.signingConfig.storeFile, variant.signingConfig.storePassword, variant.signingConfig.keyAlias, variant.signingConfig.keyPassword)
38 | }
39 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/bytedance/android/plugin/model/SigningConfig.kt:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.plugin.model
2 |
3 | import java.io.File
4 |
5 | /**
6 | * Created by YangJing on 2020/01/07 .
7 | * Email: yangjing.yeoh@bytedance.com
8 | */
9 | data class SigningConfig(
10 | val storeFile: File?,
11 | val storePassword: String?,
12 | val keyAlias: String?,
13 | val keyPassword: String?
14 | )
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/bytedance/android/plugin/tasks/AabResGuardTask.kt:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.plugin.tasks
2 |
3 | import com.android.build.gradle.api.ApplicationVariant
4 | import com.android.build.gradle.internal.scope.VariantScope
5 | import com.bytedance.android.aabresguard.commands.ObfuscateBundleCommand
6 | import com.bytedance.android.plugin.extensions.AabResGuardExtension
7 | import com.bytedance.android.plugin.internal.getBundleFilePath
8 | import com.bytedance.android.plugin.internal.getSigningConfig
9 | import com.bytedance.android.plugin.model.SigningConfig
10 | import org.gradle.api.DefaultTask
11 | import org.gradle.api.tasks.TaskAction
12 | import java.io.File
13 | import java.nio.file.Path
14 |
15 | /**
16 | * Created by YangJing on 2019/10/15 .
17 | * Email: yangjing.yeoh@bytedance.com
18 | */
19 | open class AabResGuardTask : DefaultTask() {
20 |
21 | private lateinit var variant: ApplicationVariant
22 | lateinit var signingConfig: SigningConfig
23 | var aabResGuard: AabResGuardExtension = project.extensions.getByName("aabResGuard") as AabResGuardExtension
24 | private lateinit var bundlePath: Path
25 | private lateinit var obfuscatedBundlePath: Path
26 |
27 | init {
28 | description = "Assemble resource proguard for bundle file"
29 | group = "bundle"
30 | outputs.upToDateWhen { false }
31 | }
32 |
33 | fun setVariantScope(variant:ApplicationVariant) {
34 | this.variant=variant;
35 | // init bundleFile, obfuscatedBundlePath must init before task action.
36 | bundlePath = getBundleFilePath(project, variant)
37 | obfuscatedBundlePath = File(bundlePath.toFile().parentFile, aabResGuard.obfuscatedBundleFileName).toPath()
38 | }
39 |
40 | fun getObfuscatedBundlePath(): Path {
41 | return obfuscatedBundlePath
42 | }
43 |
44 | @TaskAction
45 | private fun execute() {
46 | println(aabResGuard.toString())
47 | // init signing config
48 | signingConfig = getSigningConfig(project, variant)
49 | printSignConfiguration()
50 |
51 | prepareUnusedFile()
52 |
53 | val command = ObfuscateBundleCommand.builder()
54 | .setEnableObfuscate(aabResGuard.enableObfuscate)
55 | .setBundlePath(bundlePath)
56 | .setOutputPath(obfuscatedBundlePath)
57 | .setMergeDuplicatedResources(aabResGuard.mergeDuplicatedRes)
58 | .setWhiteList(aabResGuard.whiteList)
59 | .setFilterFile(aabResGuard.enableFilterFiles)
60 | .setFileFilterRules(aabResGuard.filterList)
61 | .setRemoveStr(aabResGuard.enableFilterStrings)
62 | .setUnusedStrPath(aabResGuard.unusedStringPath)
63 | .setLanguageWhiteList(aabResGuard.languageWhiteList)
64 | if (aabResGuard.mappingFile != null) {
65 | command.setMappingPath(aabResGuard.mappingFile)
66 | }
67 |
68 | if (signingConfig.storeFile != null && signingConfig.storeFile!!.exists()) {
69 | command.setStoreFile(signingConfig.storeFile!!.toPath())
70 | .setKeyAlias(signingConfig.keyAlias)
71 | .setKeyPassword(signingConfig.keyPassword)
72 | .setStorePassword(signingConfig.storePassword)
73 | }
74 | command.build().execute()
75 | }
76 |
77 | private fun prepareUnusedFile() {
78 | val simpleName = variant.name.replace("Release", "")
79 | val name = simpleName[0].toLowerCase() + simpleName.substring(1)
80 | val resourcePath = "${project.buildDir}/outputs/mapping/$name/release/unused.txt"
81 | val usedFile = File(resourcePath)
82 | if (usedFile.exists()) {
83 | println("find unused.txt : ${usedFile.absolutePath}")
84 | if (aabResGuard.enableFilterStrings) {
85 | if (aabResGuard.unusedStringPath == null || aabResGuard.unusedStringPath!!.isBlank()) {
86 | aabResGuard.unusedStringPath = usedFile.absolutePath
87 | println("replace unused.txt!")
88 | }
89 | }
90 | } else {
91 | println("not exists unused.txt : ${usedFile.absolutePath}\n" +
92 | "use default path : ${aabResGuard.unusedStringPath}")
93 | }
94 | }
95 |
96 | private fun printSignConfiguration() {
97 | println("-------------- sign configuration --------------")
98 | println("\tstoreFile : ${signingConfig.storeFile}")
99 | println("\tkeyPassword : ${encrypt(signingConfig.keyPassword)}")
100 | println("\talias : ${encrypt(signingConfig.keyAlias)}")
101 | println("\tstorePassword : ${encrypt(signingConfig.storePassword)}")
102 | println("-------------- sign configuration --------------")
103 | }
104 |
105 | private fun encrypt(value: String?): String {
106 | if (value == null) return "/"
107 | if (value.length > 2) {
108 | return "${value.substring(0, value.length / 2)}****"
109 | }
110 | return "****"
111 | }
112 | }
--------------------------------------------------------------------------------
/plugin/src/main/resources/META-INF/gradle-plugins/com.bytedance.android.aabResGuard.properties:
--------------------------------------------------------------------------------
1 | implementation-class=com.bytedance.android.plugin.AabResGuardPlugin
--------------------------------------------------------------------------------
/samples/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/samples/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply from: "$rootDir/gradle/aabresguard.gradle"
3 |
4 | android {
5 | compileSdkVersion versions.compileSdkVersion
6 |
7 | defaultConfig {
8 | minSdkVersion versions.minSdkVersion
9 | targetSdkVersion versions.targetSdkVersion
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 |
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
18 | }
19 | debug {
20 | minifyEnabled false
21 | shrinkResources false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 |
26 | bundle {
27 | language {
28 | enableSplit = false
29 | }
30 | density {
31 | enableSplit = true
32 | }
33 | abi {
34 | enableSplit = true
35 | }
36 | }
37 | dynamicFeatures = [":df_module1", ":df_module2"]
38 | }
39 |
40 | dependencies {
41 | implementation fileTree(dir: 'libs', include: ['*.jar'])
42 | implementation deps.appcompatV7
43 |
44 | implementation 'com.mikepenz:ionicons-typeface:2.0.1.5-kotlin@aar'
45 | implementation 'com.mikepenz:pixeden-7-stroke-typeface:1.2.0.3-kotlin@aar'
46 | implementation 'com.mikepenz:material-design-icons-dx-typeface:5.0.1.0-kotlin@aar'
47 | compileOnly deps.plugin['bintray-plugin']
48 | }
49 |
--------------------------------------------------------------------------------
/samples/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 | -dontwarn java.awt.**
23 | -flattenpackagehierarchy
24 | -allowaccessmodification
25 | -keepattributes Exceptions,InnerClasses,Signature,SourceFile,LineNumberTable
26 | -dontskipnonpubliclibraryclassmembers
27 | -ignorewarnings
28 | #kotlin
29 | -keep class kotlin.** { *; }
30 | -keep class kotlin.Metadata { *; }
31 | -dontwarn kotlin.**
32 | -keepclassmembers class **$WhenMappings {
33 | ;
34 | }
35 | -keepclassmembers class kotlin.Metadata {
36 | public ;
37 | }
38 | -assumenosideeffects class kotlin.jvm.internal.Intrinsics {
39 | static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
40 | }
41 |
42 | -keepclasseswithmembernames class * {
43 | native ;
44 | }
45 |
46 | -keepclassmembers class * extends android.app.Activity {
47 | public void *(android.view.View);
48 | }
49 | -keepclassmembers class * implements android.os.Parcelable {
50 | public static final android.os.Parcelable$Creator *;
51 | }
52 | -keep class **.R$* {*;}
53 | -keepclassmembers enum * { *;}
54 |
55 | #-keepresourcexmlelements manifest/application/meta-data@value=GlideModule
56 | -dontwarn com.bumptech.glide.**
57 |
--------------------------------------------------------------------------------
/samples/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
--------------------------------------------------------------------------------
/samples/app/src/main/res/drawable/ic_abc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytedance/AabResGuard/e8f3a5d361ce61a3d4fa8bafb9d030bbe459c400/samples/app/src/main/res/drawable/ic_abc.png
--------------------------------------------------------------------------------
/samples/app/src/main/res/drawable/ic_bcd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytedance/AabResGuard/e8f3a5d361ce61a3d4fa8bafb9d030bbe459c400/samples/app/src/main/res/drawable/ic_bcd.png
--------------------------------------------------------------------------------
/samples/app/src/main/res/drawable/ic_keep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytedance/AabResGuard/e8f3a5d361ce61a3d4fa8bafb9d030bbe459c400/samples/app/src/main/res/drawable/ic_keep.png
--------------------------------------------------------------------------------
/samples/app/src/main/res/values-ml-rIN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | app
3 | ml-rIN
4 |
5 |
--------------------------------------------------------------------------------
/samples/app/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | app
3 | zh-rTW
4 |
5 |
--------------------------------------------------------------------------------
/samples/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | app
3 | lan_default
4 |
5 |
--------------------------------------------------------------------------------
/samples/app/src/main/res/xml/actions.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/samples/dynamic-features/df_module1/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/samples/dynamic-features/df_module1/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.dynamic-feature'
2 |
3 | android {
4 | compileSdkVersion versions.compileSdkVersion
5 |
6 | defaultConfig {
7 | minSdkVersion versions.minSdkVersion
8 | targetSdkVersion versions.targetSdkVersion
9 | versionCode 1
10 | versionName "1.0"
11 | }
12 |
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | }
17 | }
18 |
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(dir: 'libs', include: ['*.jar'])
23 | implementation deps.appcompatV7
24 | implementation project(":app")
25 |
26 | implementation 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar'
27 | implementation 'com.mikepenz:material-design-iconic-typeface:2.2.0.6-kotlin@aar'
28 | implementation 'com.mikepenz:fontawesome-typeface:5.9.0.0-kotlin@aar'
29 | implementation 'com.mikepenz:octicons-typeface:3.2.0.6-kotlin@aar'
30 | implementation 'com.mikepenz:meteocons-typeface:1.1.0.5-kotlin@aar'
31 | implementation 'com.mikepenz:community-material-typeface:3.5.95.1-kotlin@aar'
32 | }
33 |
--------------------------------------------------------------------------------
/samples/dynamic-features/df_module1/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/samples/dynamic-features/df_module1/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/samples/dynamic-features/df_module1/src/main/java/com/bytedance/android/df/module1/DfModule1.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.df.module1;
2 |
3 | import android.support.v4.app.Fragment;
4 |
5 | /**
6 | * Created by YangJing on 2019/10/16 .
7 | * Email: yangjing.yeoh@bytedance.com
8 | */
9 | public class DfModule1 extends Fragment {
10 | }
11 |
--------------------------------------------------------------------------------
/samples/dynamic-features/df_module1/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | df_module1
3 |
4 |
--------------------------------------------------------------------------------
/samples/dynamic-features/df_module2/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/samples/dynamic-features/df_module2/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.dynamic-feature'
2 |
3 | android {
4 | compileSdkVersion versions.compileSdkVersion
5 |
6 | defaultConfig {
7 | minSdkVersion versions.minSdkVersion
8 | targetSdkVersion versions.targetSdkVersion
9 | versionCode 1
10 | versionName "1.0"
11 | }
12 |
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | }
17 | }
18 |
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(dir: 'libs', include: ['*.jar'])
23 | implementation deps.appcompatV7
24 | implementation project(":app")
25 |
26 | implementation 'com.mikepenz:weather-icons-typeface:2.0.10.5-kotlin@aar'
27 | implementation 'com.mikepenz:typeicons-typeface:2.0.7.5-kotlin@aar'
28 | implementation 'com.mikepenz:entypo-typeface:1.0.0.5-kotlin@aar'
29 | implementation 'com.mikepenz:devicon-typeface:2.0.0.5-kotlin@aar'
30 | implementation 'com.mikepenz:foundation-icons-typeface:3.0.0.5-kotlin@aar'
31 | }
32 |
--------------------------------------------------------------------------------
/samples/dynamic-features/df_module2/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/samples/dynamic-features/df_module2/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/samples/dynamic-features/df_module2/src/main/java/com/bytedance/android/df/module2/DfModule2.java:
--------------------------------------------------------------------------------
1 | package com.bytedance.android.df.module2;
2 |
3 | import android.support.v4.app.Fragment;
4 |
5 | /**
6 | * Created by YangJing on 2019/10/16 .
7 | * Email: yangjing.yeoh@bytedance.com
8 | */
9 | public class DfModule2 extends Fragment {
10 | }
11 |
--------------------------------------------------------------------------------
/samples/dynamic-features/df_module2/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | df_module2
3 |
4 |
--------------------------------------------------------------------------------
/samples/mapping.txt:
--------------------------------------------------------------------------------
1 | res dir mapping:
2 | res/drawable-v23 -> res/l
3 | res/layout-v26 -> res/y
4 | res/drawable-v21 -> res/i
5 | res/drawable-ldrtl-xhdpi-v17 -> res/p
6 | res/drawable-xhdpi-v4 -> res/g
7 | res/color-v21 -> res/c
8 | res/color-v23 -> res/d
9 | res/anim -> res/a
10 |
11 | res id mapping:
12 | 0x7f0c00ba : com.bytedance.android.app.R.style.RtlUnderlay.Widget.AppCompat.ActionButton.Overflow -> com.bytedance.android.app.R.style.eb
13 | 0x7f040002 : com.bytedance.android.app.R.color.abc_btn_colored_borderless_text_material -> com.bytedance.android.app.R.color.c
14 | 0x7f0c00d5 : com.bytedance.android.app.R.style.TextAppearance.AppCompat.Title -> com.bytedance.android.app.R.style.f2
15 | 0x7f0c0022 : com.bytedance.android.app.R.style.Base.TextAppearance.AppCompat.Small.Inverse -> com.bytedance.android.app.R.style.a8
16 |
17 | res entries path mapping:
18 | 0x7f060030 : base/res/drawable-xxhdpi-v4/abc_list_selector_disabled_holo_dark.9.png -> res/h/z.9.png
19 | 0x7f060022 : base/res/drawable-xxxhdpi-v4/abc_ic_star_half_black_16dp.png -> res/k/o.png
20 | 0x7f0a001a : base/res/layout/abc_select_dialog_material.xml -> res/t/a0.xml
21 | 0x7f01000a : base/res/anim/abc_tooltip_enter.xml -> res/a/k.xml
22 |
--------------------------------------------------------------------------------
/samples/unused.txt:
--------------------------------------------------------------------------------
1 | abc_action_bar_home_description
2 | abc_action_bar_up_description
3 | abc_action_menu_overflow_description
4 | abc_action_mode_done
--------------------------------------------------------------------------------
/script/publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function showHelp() {
4 | echo "publishToMavenLocal: ./publish.sh l"
5 | echo "publish: ./publish.sh m"
6 | # publish to JCenter
7 | echo "publish: ./publish.sh j"
8 | }
9 |
10 | if [ -z $1 ];then
11 | showHelp
12 | exit -1
13 | fi
14 |
15 | function publishMaven(){
16 | ./gradlew clean :core:$1 :plugin:$1 --no-daemon --stacktrace
17 | }
18 |
19 | if [[ $1 == 'l' ]];then
20 | publishMaven publishToMavenLocal
21 | elif [[ $1 == 'm' ]];then
22 | publishMaven publish
23 | elif [[ $1 == 'j' ]];then
24 | publishMaven bintrayUpload
25 | else
26 | showHelp
27 | exit -1
28 | fi
29 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':core', ':plugin', ':app', ':df_module1', ':df_module2'
2 |
3 | settings.project(":app").projectDir = file("$rootDir/samples/app")
4 | settings.project(":df_module1").projectDir = file("$rootDir/samples/dynamic-features/df_module1")
5 | settings.project(":df_module2").projectDir = file("$rootDir/samples/dynamic-features/df_module2")
6 |
7 |
--------------------------------------------------------------------------------
/wiki/en/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | **[English](CHANGELOG.md)** | [简体中文](../zh-cn/CHANGELOG.md)
2 |
3 | # Change log
4 | ## 0.1.6(2020/4/21)
5 | - Compatible wit `AGP-3.5.0`
6 | - Bugfix: `Fix get AGP version failed issue`
7 |
8 | ## 0.1.5(2020/4/5)
9 | - Compatible with `AGP-4.0.0-alpha09`
10 | - Add `enableObfuscate` for plugin extension.
11 |
12 | ## 0.1.3(2020/1/8)
13 | - Compatible with `AGP-3.5.2`
14 |
15 | ## 0.1.2(2020/1/7)
16 | - Compatible with `AGP-4.0.0-alpha07`
17 | - Fix issue [#13](https://github.com/bytedance/AabResGuard/issues/13)
18 |
19 | ## 0.1.1(2019/11/26)
20 | - Compatible with `AGP-3.4.1.`
21 | - Fix issue [#4](https://github.com/bytedance/AabResGuard/issues/4)
22 |
23 | ## 0.1.0(2019/10/16)
24 | - Add support for resources obfuscation.
25 | - Add support for merge duplicated resources.
26 | - Add support for files filtering.
27 | - Add support for string filtering.
28 | - Added support for `gradle plugin` .
29 | - Add support for `command line` .
30 |
--------------------------------------------------------------------------------
/wiki/en/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | education, socio-economic status, nationality, personal appearance, race,
10 | religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at yangjing.yeoh@bytedance.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html)
72 |
73 | [homepage]: https://www.contributor-covenant.org
--------------------------------------------------------------------------------
/wiki/en/COMMAND.md:
--------------------------------------------------------------------------------
1 | **[English](COMMAND.md)** | [简体中文](../zh-cn/COMMAND.md)
2 |
3 | # Command line
4 |
5 | > **AabResGuard** provides a jar file that can run resource obfuscation by command line.
6 |
7 | ## Merge duplicated resources
8 | The duplicate files will be merged according to the file `md5` value, only one file will be retained, and then the values in the original resource path index table will be redirected to reduce the volume of the package.
9 | ```cmd
10 | aabresguard merge-duplicated-res --bundle=app.aab --output=merged.aab
11 | --storeFile=debug.store
12 | --storePassword=android
13 | --keyAlias=android
14 | --keyPassword=android
15 | ```
16 | The signature information is optional. If you do not specify the signature information, it will be signed using the `Android` default signature file on the PC.
17 |
18 | ## File filtering
19 | Support for specifying specific files for filtering. Currently only filtering under the `META-INF/` and `lib/` folders is supported.
20 | ```cmd
21 | aabresguard filter-file --bundle=app.aab --output=filtered.aab --config=config.xml
22 | --storeFile=debug.store
23 | --storePassword=android
24 | --keyAlias=android
25 | --keyPassword=android
26 | ```
27 |
28 | Configuration file `config.xml`, filtering rules support `regular expressions`
29 | ```xml
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | ```
38 | **Applicable scenarios:** Due to the needs of the business, some channels need to make a full package, but the full package will include all `so` files, `files filter` can be used to filter the `abi` of a certain latitude and will not affect `bundletool` process.
39 |
40 | ## Resources obfuscation
41 | Resource aliasing of the input `aab` file, and outputting the obfuscated `aab` file, supporting `Merge duplicated resources` and `file filtering`.
42 | ```cmd
43 | aabresguard obfuscate-bundle --bundle=app.aab --output=obfuscated.aab --config=config.xml --mapping=mapping.txt
44 | --merge-duplicated-res=true
45 | --storeFile=debug.store
46 | --storePassword=android
47 | --keyAlias=android
48 | --keyPassword=android
49 | ```
50 |
51 | Configuration file `config.xml`, whitelist support `regular expressions`
52 | ```xml
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | ```
64 |
65 | ## String filtering
66 | Specify a line-by-line split string list file to filter out value and translations if name is matched in the string resource type
67 | ```cmd
68 | aabresguard filter-string --bundle=app.aab --output=filtered.aab --config=config.xml
69 | --storeFile=debug.store
70 | --storePassword=android
71 | --keyAlias=android
72 | --keyPassword=android
73 | ```
74 | Configuration file `config.xml`
75 | ```xml
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | ```
87 |
88 |
89 | ## #Parameter Description
90 | For the description of the parameters, please execute the following command:
91 |
92 | ```cmd
93 | aabresguard help
94 | ```
--------------------------------------------------------------------------------
/wiki/en/CONTRIBUTOR.md:
--------------------------------------------------------------------------------
1 | **[English](CONTRIBUTOR.md)** | [简体中文](../zh-cn/CONTRIBUTOR.md)
2 |
3 | # Contribute guide
4 |
5 | This guide will show you how to contribute to **AabResGuard**. Please ask for an [issue](https://github.com/bytedance/AabResGuard/issues) or [pull request](https://github.com/bytedance/AabResGuard/pulls).
6 | Take a few minutes to read this guide before.
7 |
8 | ## Contributing
9 | We are always very happy to have contributions, whether for typo fix, bug fix or big new features. Please do not ever hesitate to ask a question or send a pull request.
10 |
11 | ## [#Code of Conduct](CODE_OF_CONDUCT.md)
12 | Please make sure to read and observe our **[Code of Conduct](CODE_OF_CONDUCT.md)** .
13 |
14 | ## GitHub workflow
15 | All work on **AabResGuard** happens directly on GitHub. Both core team members and external contributors send pull requests which go through the same review process.
16 |
17 | We use the `develop` branch as our development branch, and this code is an unstable branch. Each version will create a `release` branch (such as `release/0.1.1`) as a stable release branch.
18 | Each time a new version is released, it will be merged into the corresponding branch and the corresponding `tag` will be applied.
19 |
20 | Here are the workflow for contributors:
21 |
22 | - Fork to your own.
23 | - Clone fork to local repository.
24 | - Create a new branch and work on it.
25 | - Keep your branch in sync.
26 | - Commit your changes (make sure your commit message concise).
27 | - Push your commits to your forked repository.
28 | - Create a pull request.
29 |
30 | Please follow the pull request template. Please make sure the PR has a corresponding issue.
31 |
32 | After creating a PR, one or more reviewers will be assigned to the pull request. The reviewers will review the code.
33 |
34 | Before merging a PR, squash any fix review feedback, typo, merged, and rebased sorts of commits. The final commit message should be clear and concise.
35 |
36 | ## Open an issue / PR
37 | ### Where to Find Known Issues
38 | We will be using GitHub Issues for our public bugs. We will keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new issue, try to make sure your problem doesn't already exist.
39 |
40 | ### Reporting New Issues
41 | The best way to get your bug fixed is to provide a reduced test case. Please provide a public repository with a runnable example.
42 |
--------------------------------------------------------------------------------
/wiki/en/DATA.md:
--------------------------------------------------------------------------------
1 | **[English](DATA.md)** | [简体中文](../zh-cn/DATA.md)
2 |
3 | # Data of size savings
4 | **AabResGuard** was developed in June 2019 and launched at the end of July 2019 in several overseas products such as `Tiktok`, `Vigo`.
5 | Provides resource protection and package size optimization capabilities for overseas products.
6 |
7 | At present, no feedback has been received on related resources. Due to some reasons for the R&D process, **AabResGuard** is supported by additional commands based on resource obfuscation.
8 | It has the ability to run by command line, and provides the `jar` package directly to provide convenient support for `CI`.
9 | The current data of size savings for multiple products is below:
10 |
11 | >Since each application has different levels of optimization for resources, the optimization of the data in different applications is different, and the actual data is subject to change.
12 |
13 | **AabResGuard-0.1.0**
14 |
15 | |App|coast time|aab size|apk raw size|apk download size|
16 | |---|-------|--------|-------------|----------------|
17 | |Tiktok/840|75s|-2.9MB|-1.9MB|-0.7MB|
18 | |Vigo/v751|60s|-1.0Mb|-1.4MB|-0.6MB|
19 |
20 |
21 | **`device-spec` Configuration:**
22 | ```json
23 | {
24 | "supportedAbis": ["armeabi-v7a"],
25 | "supportedLocales": ["zh-CN", "en-US", "ja-JP", "zh-HK", "zh-TW"],
26 | "screenDensity": 480,
27 | "sdkVersion": 16
28 | }
29 | ```
30 |
--------------------------------------------------------------------------------
/wiki/en/OUTPUT.md:
--------------------------------------------------------------------------------
1 | **[English](OUTPUT.md)** | [简体中文](../zh-cn/OUTPUT.md)
2 | # Output file
3 |
4 | >The obfuscated file output directory is identical to the file directory output by the bundle package, both under the `build/outputs/bundle/{flavor}/` directory.
5 |
6 | The obfuscated output file is shown below:
7 |
8 | 
9 |
10 | ## resources-mapping
11 | A log file for recording resource obfuscation rules, the example is shown below:
12 |
13 | ```txt
14 | res dir mapping:
15 | res/color-v21 -> res/c
16 | res/color-v23 -> res/d
17 | res/anim -> res/a
18 |
19 | res id mapping:
20 | 0x7f0c00ba : com.bytedance.android.app.R.style.RtlUnderlay.Widget.AppCompat.ActionButton.Overflow -> com.bytedance.android.app.R.style.eb
21 | 0x7f040002 : com.bytedance.android.app.R.color.abc_btn_colored_borderless_text_material -> com.bytedance.android.app.R.color.c
22 | 0x7f0c00d5 : com.bytedance.android.app.R.style.TextAppearance.AppCompat.Title -> com.bytedance.android.app.R.style.f2
23 | 0x7f0c0022 : com.bytedance.android.app.R.style.Base.TextAppearance.AppCompat.Small.Inverse -> com.bytedance.android.app.R.style.a8
24 |
25 | res entries path mapping:
26 | 0x7f060030 : base/res/drawable-xxhdpi-v4/abc_list_selector_disabled_holo_dark.9.png -> res/h/z.9.png
27 | 0x7f060022 : base/res/drawable-xxxhdpi-v4/abc_ic_star_half_black_16dp.png -> res/k/o.png
28 | ```
29 |
30 | - **res dir mapping:** The obfuscated rules for storing resource file directories. Format: dir -> dir (`res/` root directory can not be obfuscated)
31 | - **res id mapping:** The obfuscated rules for storing resource names. Format: resourceId : resourceName -> resourceName (resourceId will not be read in increment obfuscating)
32 | - **res entries path mapping:** The obfuscated rules for storing resource file paths. Format: resourceId : path -> path (resourceId will not be read in obfuscating)
33 |
34 | ## -duplicated.txt
35 | Used to record the deduplicated resource files, the example is shown below:
36 |
37 | ```txt
38 | res filter path mapping:
39 | res/drawable-hdpi-v4/abc_list_divider_mtrl_alpha.9.png -> res/drawable-mdpi-v4/abc_list_divider_mtrl_alpha.9.png (size 167B)
40 | res/color-v23/abc_tint_spinner.xml -> res/color-v23/abc_tint_edittext.xml (size 942B)
41 | res/drawable-xhdpi-v4/abc_list_divider_mtrl_alpha.9.png -> res/drawable-mdpi-v4/abc_list_divider_mtrl_alpha.9.png (size 167B)
42 | removed: count(3), totalSize(1.2KB)
43 | ```
44 |
--------------------------------------------------------------------------------
/wiki/en/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Pull request template
2 |
3 | - Describe what this PR does / why we need it
4 | - Does this pull request fix one issue?
5 | - Describe how you did it
6 | - Describe how to verify it
7 | - Special notes for reviews
8 |
--------------------------------------------------------------------------------
/wiki/en/WHITELIST.md:
--------------------------------------------------------------------------------
1 | # Whitelist
2 |
3 | Welcome PR your configs which is not included in whitelist.
4 |
5 | ## Google Services
6 | ```
7 | *.R.string.default_web_client_id
8 | *.R.string.firebase_database_url
9 | *.R.string.gcm_defaultSenderId
10 | *.R.string.google_api_key
11 | *.R.string.google_app_id
12 | *.R.string.google_crash_reporting_api_key
13 | *.R.string.google_storage_bucket
14 | *.R.string.project_id
15 | *.R.string.com.crashlytics.android.build_id
16 | ```
17 |
--------------------------------------------------------------------------------
/wiki/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytedance/AabResGuard/e8f3a5d361ce61a3d4fa8bafb9d030bbe459c400/wiki/images/logo.png
--------------------------------------------------------------------------------
/wiki/images/output.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytedance/AabResGuard/e8f3a5d361ce61a3d4fa8bafb9d030bbe459c400/wiki/images/output.png
--------------------------------------------------------------------------------
/wiki/zh-cn/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | [English](../en/CHANGELOG.md) | **[简体中文](CHANGELOG.md)**
2 |
3 | # 版本日志
4 | ## 0.1.6(2020/4/21)
5 | - 适配 `AGP-3.5.0`
6 | - 修复获取 `AGP` 版本号失败的问题
7 |
8 | ## 0.1.5(2020/4/5)
9 | - 适配 `AGP-4.0.0-alpha09`
10 | - 给插件添加 `enableObfuscate` 参数
11 |
12 | ## 0.1.3(2020/1/8)
13 | - 适配 `AGP-3.5.2`
14 |
15 | ## 0.1.2(2020/1/7)
16 | - 适配 `AGP-4.0.0-alpha07`
17 | - Fix issue [#13](https://github.com/bytedance/AabResGuard/issues/13)
18 |
19 | ## 0.1.1(2019/11/26)
20 | - 适配 `AGP-3.4.1`
21 | - Fix issue [#4](https://github.com/bytedance/AabResGuard/issues/4)
22 |
23 | ## 0.1.0(2019/10/16)
24 | - 添加资源混淆功能
25 | - 添加资源去重功能
26 | - 添加文件过滤功能
27 | - 添加字符串过滤功能
28 | - 添加 `gradle plugin` 的支持
29 | - 添加命令行支持
30 |
--------------------------------------------------------------------------------
/wiki/zh-cn/COMMAND.md:
--------------------------------------------------------------------------------
1 | [English](../en/COMMAND.md) | **[简体中文](COMMAND.md)**
2 |
3 | # 命令行支持
4 |
5 | > **AabResGuard** 提供了 jar 包,可以直接通过命令行来运行资源混淆。
6 |
7 | ## 资源去重
8 | 根据文件 `md5` 值对重复的文件进行合并,只保留一份,然后重定向原本的资源路径索引表中的值,以达到缩减包体积的目的。
9 | ```cmd
10 | aabresguard merge-duplicated-res --bundle=app.aab --output=merged.aab
11 | --storeFile=debug.store
12 | --storePassword=android
13 | --keyAlias=android
14 | --keyPassword=android
15 | ```
16 | 签名信息为可选参数,如果不指定签名信息,则会使用机器中 `Android` 默认的签名文件进行签名。
17 |
18 | ## 文件过滤
19 | 支持指定特定的文件进行过滤,目前只支持 `META-INF/` 和 `lib/` 文件夹下的过滤。
20 | ```cmd
21 | aabresguard filter-file --bundle=app.aab --output=filtered.aab --config=config.xml
22 | --storeFile=debug.store
23 | --storePassword=android
24 | --keyAlias=android
25 | --keyPassword=android
26 | ```
27 | 配置文件 `config.xml`,过滤规则支持`正则表达式`
28 | ```xml
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | ```
37 | **适用场景:** 由于业务的需要,部分渠道需要打全量包,但是全量包会包括所有的 `so`,使用该根据可以过滤某一个纬度的 `abi`,并且不会影响 `bundletool` 的解析过程。
38 |
39 | ## 资源混淆
40 | 对输入的 `aab` 文件进行资源混淆,并输出混淆后的 `aab` 文件,支持 `资源去重` 和 `文件过滤`。
41 | ```cmd
42 | aabresguard obfuscate-bundle --bundle=app.aab --output=obfuscated.aab --config=config.xml --mapping=mapping.txt
43 | --merge-duplicated-res=true
44 | --storeFile=debug.store
45 | --storePassword=android
46 | --keyAlias=android
47 | --keyPassword=android
48 | ```
49 | 配置文件 `config.xml`,白名单支持`正则表达式`
50 | ```xml
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | ```
62 |
63 | ## 文案过滤
64 | 指定一个按行分割的字符串列表文件,过滤掉string资源类型中name匹配的文案及翻译
65 | ```cmd
66 | aabresguard filter-string --bundle=app.aab --output=filtered.aab --config=config.xml
67 | --storeFile=debug.store
68 | --storePassword=android
69 | --keyAlias=android
70 | --keyPassword=android
71 | ```
72 | 配置文件 `config.xml`
73 | ```xml
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | ```
85 |
86 |
87 | ## 参数说明
88 | 参数的说明请执行以下命令来进行查看:
89 |
90 | ```cmd
91 | aabresguard help
92 | ```
--------------------------------------------------------------------------------
/wiki/zh-cn/CONTRIBUTOR.md:
--------------------------------------------------------------------------------
1 | [English](../en/CONTRIBUTOR.md) | **[简体中文](../zh-cn/CONTRIBUTOR.md)**
2 |
3 | # 贡献指南
4 |
5 | 这篇指南会指导你如何为 **AabResGuard** 贡献一份自己的力量,请在你要提 [issue](https://github.com/bytedance/AabResGuard/issues) 或者 [pull request](https://github.com/bytedance/AabResGuard/pulls)
6 | 之前花几分钟来阅读一遍这篇指南。
7 |
8 | ## 贡献
9 | 我们随时都欢迎任何贡献,无论是简单的错别字修正,BUG 修复还是增加新功能。请踊跃提出问题或发起 PR。我们同样重视文档以及与其它开源项目的整合,欢迎在这方面做出贡献。
10 |
11 | ## [#行为准则](../en/CODE_OF_CONDUCT.md)
12 | 我们有一份[行为准则](../en/CODE_OF_CONDUCT.md),希望所有的贡献者都能遵守,请花时间阅读一遍全文以确保你能明白哪些是可以做的,哪些是不可以做的。
13 |
14 | ## 研发流程
15 | 我们所有的工作都会放在 GitHub 上。不管是核心团队的成员还是外部贡献者的 pull request 都需要经过同样流程的 review。
16 |
17 | 我们使用 `develop` 分支作为我们的开发分支,这代码它是不稳定的分支。每个版本都会创建一个 `release` 分支(如 `release/0.1`) 作为稳定的发布分支。
18 | 每发布一个新版本都会将其合并到对应的分支并打上对应的 `tag`。
19 |
20 | 下面是开源贡献者常用的工作流(workflow):
21 |
22 | - 将仓库 fork 到自己的 GitHub 下
23 | - 将 fork 后的仓库 clone 到本地
24 | - 创建新的分支,在新的分支上进行开发操作(请确保对应的变更都有测试用例或 demo 进行验证)
25 | - 保持分支与远程 master 分支一致(通过 fetch 和 rebase 操作)
26 | - 在本地提交变更(注意 commit log 保持简练、规范),注意提交的 email 需要和 GitHub 的 email 保持一致
27 | - 将提交 push 到 fork 的仓库下
28 | - 创建一个 pull request (PR)
29 |
30 | 提交 PR 的时候请参考 [PR 模板](../en/PULL_REQUEST_TEMPLATE.md)。在进行较大的变更的时候请确保 PR 有一个对应的 Issue。
31 |
32 |
33 | 在合并 PR 的时候,请把多余的提交记录都 squash 成一个。最终的提交信息需要保证简练、规范。
34 |
35 | ## 提交 bug
36 | ### 查找已知的 Issues
37 | 我们使用 GitHub Issues 来管理项目 bug。 我们将密切关注已知 bug,并尽快修复。 在提交新问题之前,请尝试确保您的问题尚不存在。
38 |
39 | ### 提交新的 Issues
40 | 请按照 Issues Template 的指示来提交新的 Issues。
41 |
--------------------------------------------------------------------------------
/wiki/zh-cn/DATA.md:
--------------------------------------------------------------------------------
1 | [English](../en/DATA.md) | **[简体中文](DATA.md)**
2 |
3 | # 数据收益
4 | **AabResGuard** 于2019年六月研发完成,于2019年七月底在 `Tiktok`、`Vigo` 等多个海外产品上线,
5 | 为海外产品提供了资源保护和包大小优化的能力。
6 |
7 | 目前未收到相关资源方面的问题反馈,由于研发流程的一些原因,**AabResGuard** 在资源混淆的基础上由提供了额外的其他命令的支持,
8 | 做到了命令之间独立运行的能力,并且直接提供 `jar` 包,为 `CI` 提供便利支持。
9 | 目前在多个产品的收益数据如下所示:(解析 apk 的配置固定)
10 |
11 | >由于每个应用对资源的优化程度不同,所以该数据在不同的应用上的优化不同,以实际数据为准。
12 |
13 | **AabResGuard-0.1.0**
14 |
15 | |产品|运行时间|aab size|apk raw size|apk download size|
16 | |---|-------|--------|-------------|----------------|
17 | |Tiktok/840|75s|-2.9MB|-1.9MB|-0.7MB|
18 | |Vigo/v751|60s|-1.0Mb|-1.4MB|-0.6MB|
19 |
20 |
21 | **`device-spec` 配置:**
22 | ```json
23 | {
24 | "supportedAbis": ["armeabi-v7a"],
25 | "supportedLocales": ["zh-CN", "en-US", "ja-JP", "zh-HK", "zh-TW"],
26 | "screenDensity": 480,
27 | "sdkVersion": 16
28 | }
29 | ```
30 |
--------------------------------------------------------------------------------
/wiki/zh-cn/OUTPUT.md:
--------------------------------------------------------------------------------
1 | [English](../OUTPUT.md) | **[简体中文](OUTPUT.md)**
2 | # 输出文件
3 |
4 | >混淆后的文件输出目录和 bundle 打包后输出的文件目录一致,均在 `build/outputs/bundle/{flavor}/` 目录下。
5 |
6 | 混淆后的输出文件如下图所示:
7 |
8 | 
9 |
10 | ## resources-mapping
11 | 用于记录资源混淆规则的日志文件,示例如下:
12 |
13 | ```txt
14 | res dir mapping:
15 | res/color-v21 -> res/c
16 | res/color-v23 -> res/d
17 | res/anim -> res/a
18 |
19 | res id mapping:
20 | 0x7f0c00ba : com.bytedance.android.app.R.style.RtlUnderlay.Widget.AppCompat.ActionButton.Overflow -> com.bytedance.android.app.R.style.eb
21 | 0x7f040002 : com.bytedance.android.app.R.color.abc_btn_colored_borderless_text_material -> com.bytedance.android.app.R.color.c
22 | 0x7f0c00d5 : com.bytedance.android.app.R.style.TextAppearance.AppCompat.Title -> com.bytedance.android.app.R.style.f2
23 | 0x7f0c0022 : com.bytedance.android.app.R.style.Base.TextAppearance.AppCompat.Small.Inverse -> com.bytedance.android.app.R.style.a8
24 |
25 | res entries path mapping:
26 | 0x7f060030 : base/res/drawable-xxhdpi-v4/abc_list_selector_disabled_holo_dark.9.png -> res/h/z.9.png
27 | 0x7f060022 : base/res/drawable-xxxhdpi-v4/abc_ic_star_half_black_16dp.png -> res/k/o.png
28 | ```
29 |
30 | - **res dir mapping:** 存储资源文件目录的混淆规则。格式:dir -> dir(`res/` 根目录不可以被混淆)
31 | - **res id mapping:** 存储资源名称的混淆规则。格式:resourceId : resourceName -> resourceName(增量混淆时,resourceId 不会被读入)
32 | - **res entries path mapping:** 存储资源文件路径的混淆规则。格式:resourceId : path -> path(增量混淆时,resourceId 不会被读入)
33 |
34 | ## -duplicated.txt
35 | 用于记录被去重的资源文件,示例如下:
36 |
37 | ```txt
38 | res filter path mapping:
39 | res/drawable-hdpi-v4/abc_list_divider_mtrl_alpha.9.png -> res/drawable-mdpi-v4/abc_list_divider_mtrl_alpha.9.png (size 167B)
40 | res/color-v23/abc_tint_spinner.xml -> res/color-v23/abc_tint_edittext.xml (size 942B)
41 | res/drawable-xhdpi-v4/abc_list_divider_mtrl_alpha.9.png -> res/drawable-mdpi-v4/abc_list_divider_mtrl_alpha.9.png (size 167B)
42 | removed: count(3), totalSize(1.2KB)
43 | ```
44 |
--------------------------------------------------------------------------------
/wiki/zh-cn/README.md:
--------------------------------------------------------------------------------
1 | # AabResGuard
2 |
3 |
4 |
针对 aab 文件的资源混淆工具
5 |
6 |
7 | [  ](https://bintray.com/yeoh/maven/aabresguard-plugin/0.1.4/link)
8 | [](../../LICENSE)
9 | [](https://github.com/google/bundletool)
10 |
11 | [English](../../README.md) | **[简体中文](README.md)**
12 |
13 | > 本工具由字节跳动抖音 Android 团队提供。
14 |
15 | ## 特性
16 | > 针对 aab 文件的资源混淆工具
17 |
18 | - **资源去重:** 对重复资源文件进行合并,缩减包体积。
19 | - **文件过滤:** 支持对 `bundle` 包中的文件进行过滤,目前只支持 `MATE-INFO/`、`lib/` 路径下的过滤。
20 | - **白名单:** 白名单中的资源,名称不予混淆。
21 | - **增量混淆:** 输入 `mapping` 文件,支持增量混淆。
22 | - **文案删除:** 输入按行分割的字符串文件,移除文案及翻译。
23 | - **???:** 展望未来,会有更多的特性支持,欢迎提交 PR & issue。
24 |
25 | ## [数据收益](DATA.md)
26 | **AabResGuard** 是抖音Android团队完成的资源混淆工具,目前已经在 **Tiktok、Vigo** 等多个产品上线多月,目前无相关资源问题的反馈。
27 | 具体的数据详细信息请移步 **[数据收益](DATA.md)** 。
28 |
29 | ## 快速开始
30 | - **命令行工具:** 支持命令行一键输入输出。
31 | - **Gradle plugin:** 支持 `gradle plugin`,使用原始打包命令执行混淆。
32 |
33 | ### Gradle plugin
34 | 在 `build.gradle(root project)` 中进行配置
35 | ```gradle
36 | buildscript {
37 | repositories {
38 | mavenCentral()
39 | jcenter()
40 | google()
41 | }
42 | dependencies {
43 | classpath "com.bytedance.android:aabresguard-plugin:0.1.0"
44 | }
45 | }
46 | ```
47 |
48 | 在 `build.gradle(application)` 中配置
49 | ```gradle
50 | apply plugin: "com.bytedance.android.aabResGuard"
51 | aabResGuard {
52 | mappingFile = file("mapping.txt").toPath() // 用于增量混淆的 mapping 文件
53 | whiteList = [ // 白名单规则
54 | "*.R.raw.*",
55 | "*.R.drawable.icon"
56 | ]
57 | obfuscatedBundleFileName = "duplicated-app.aab" // 混淆后的文件名称,必须以 `.aab` 结尾
58 | mergeDuplicatedRes = true // 是否允许去除重复资源
59 | enableFilterFiles = true // 是否允许过滤文件
60 | filterList = [ // 文件过滤规则
61 | "*/arm64-v8a/*",
62 | "META-INF/*"
63 | ]
64 | enableFilterStrings = false // 过滤文案
65 | unusedStringPath = file("unused.txt").toPath() // 过滤文案列表路径 默认在mapping同目录查找
66 | languageWhiteList = ["en", "zh"] // 保留en,en-xx,zh,zh-xx等语言,其余均删除
67 | }
68 | ```
69 |
70 | `aabResGuard plugin` 侵入了 `bundle` 打包流程,可以直接执行原始打包命令进行混淆。
71 | ```cmd
72 | ./gradlew clean :app:bundleDebug --stacktrace
73 | ```
74 |
75 | 通过 `gradle` 获取混淆后的 `bundle` 文件路径
76 | ```groovy
77 | def aabResGuardPlugin = project.tasks.getByName("aabresguard${VARIANT_NAME}")
78 | Path bundlePath = aabResGuardPlugin.getObfuscatedBundlePath()
79 | ```
80 |
81 | ### [白名单](../en/WHITELIST.md)
82 | 不需要混淆的资源. 如果[白名单](../en/WHITELIST.md)中没有包含你的配置,欢迎提交 PR.
83 |
84 | ### [命令行支持](COMMAND.md)
85 | **AabResGuard** 提供了 `jar` 包,可以使用命令行直接执行,具体的使用请移步 **[命令行支持](COMMAND.md)** 。
86 |
87 | ### [输出文件](OUTPUT.md)
88 | 在打包完成后会输出混淆后的文件和相应的日志文件,详细信息请移步 **[输出文件](OUTPUT.md)** 。
89 | - **resources-mapping.txt:** 资源混淆 mapping,可作为下次混淆输入以达到增量混淆的目的。
90 | - **aab:** 优化后的 aab 文件。
91 | - **-duplicated.txt:** 被去重的文件日志记录。
92 |
93 | ## [版本日志](CHANGELOG.md)
94 | 版本变化日志记录,详细信息请移步 **[版本日志](CHANGELOG.md)** 。
95 |
96 | ## [代码贡献](CONTRIBUTOR.md)
97 | 阅读详细内容,了解如何参与改进 **AabResGuard**。
98 |
99 | ### 贡献者
100 | * [JingYeoh](https://github.com/JingYeoh)
101 | * [Jun Li]()
102 | * [Zilai Jiang](https://github.com/Zzzia)
103 | * [Zhiqian Yang](https://github.com/yangzhiqian)
104 | * [Xiaoshuang Bai (Designer)](https://www.behance.net/shawnpai)
105 |
106 | ## 感谢
107 | * [AndResGuard](https://github.com/shwenzhang/AndResGuard/)
108 | * [BundleTool](https://github.com/google/bundletool)
109 |
--------------------------------------------------------------------------------