APK Package Size Optimization and Resource Obfuscation for Android Plugins
To keep Android plugins lightweight as they grow, developers should analyze and obfuscate resource files—using a custom AndroidResGuard extension that adds a fixed package ID and a mapping table protocol—to shrink the resources.arsc constant pool, integrate the changes via Gradle, verify with gray releases, and achieve roughly an 8% size reduction while ensuring whitelist compliance.
When to Optimize Package Size
Package size optimization is usually unnecessary during the early stage of an app when the codebase is small and the APK is lightweight. As the business matures and features increase, the APK size grows, making optimization more valuable.
Impact of Increased APK Size
1. For a full app, a larger download size reduces conversion rates; for a plugin (e.g., Baidu Live internal plugin) the impact is on download time and cold‑start latency.
2. Channel partners may impose higher fees for larger APKs because they require more ROM space and longer installation times, degrading user experience.
3. Larger packages, whether app or plugin, lead to longer installation and OAT compilation times (pre‑Android 9).
Composition of an APK
An Android APK consists of:
Code: class.dex
Native libraries: lib/
Resources: resources.arsc , res/ , assets/
Signature information: META-INF/
To control APK size, focus on the dex files, native libraries, and resource data. Native libraries can be downloaded on‑demand; this article concentrates on resource optimization.
Resource Optimization
Using Android Size Analyzer, developers can generate a ranked list of image resources. After analysis, images can be compressed, moved to cloud control, converted to WebP, replaced with vectors, or removed if redundant. The article mainly discusses resource obfuscation based on AndroidResGuard with custom extensions.
Why a Custom Extension?
The original tool does not support plugin resource obfuscation. Plugins need a fixed package ID in resource IDs to avoid conflicts with the host:
[<package_name>. ]R.<resource_type>.<resource_name>
针对插件 //
additionalParameters "--package-id", "0x61", "--allow-reserved-package-id"Resource Mapping Table Protocol
The obfuscation works by mapping long resource strings to short ones, reducing the constant‑pool size of resources.arsc . Adding a package ID without proper protocol support caused errors, which were solved by extending the ARSC protocol.
Implementation Overview
The solution consists of three parts: design & implementation, QA pipeline configuration, and gray‑release testing.
1. Design & Implementation
The project required a clear understanding of the existing codebase, especially the resource obfuscation rules and whitelist handling. The core modules are:
Configuration module
Plugin module
Resource‑mapping obfuscation module
Configuration parameters (Gradle plugin settings):
mappingFile = file("./resource_mapping.txt")
use7zip = false
useSign = false
// Keep original resource paths, only obfuscate names
keepRoot = true
// Reduce string pool size by obfuscating arsc name column
// fixedResName = "arg"
// Merge duplicated resources (use with caution)
mergeDuplicatedRes = true
nightmode_pattern = "_1" // night mode config
// Add basic SDKs to whitelist
whiteList = []Dynamic parameters for plugin‑specific fix IDs are generated by the following Groovy script:
// Generate whitelist for fix files
def generateWhiteList() {
def fixFile = file("host-res-fix/${rootProject.ext.plugin_fix_file_map[host]}")
def resGuard = file("resguard/res_guard_config.xml")
def resproguard = new XmlParser().parse(resGuard)
def issueNode = new NodeBuilder().issue(id: 'whitelist', isactive: true)
resproguard.issue[0].replaceNode(issueNode)
fixFile.eachLine { line ->
// TODO: generate whitelist entries
}
new XmlNodePrinter(new PrintWriter(resGuard)).print(resproguard)
return resGuard
}2. Resource Mapping Table Read/Write
The core logic reads the ARSC structure, modifies it, and writes it back:
private void writeLibraryType() throws AndrolibException, IOException {
checkChunkType(Header.TYPE_LIBRARY);
int libraryCount = mIn.readInt();
mOut.writeInt(libraryCount);
for (int i = 0; i < libraryCount; i++) {
mOut.writeInt(mIn.readInt()); // packageId
mOut.writeBytes(mIn, 256); // packageName
}
writeNextChunk(0);
while (mHeader.type == Header.TYPE_TYPE) {
writeTableTypeSpec();
}
}
private void readLibraryType() throws AndrolibException, IOException {
checkChunkType(Header.TYPE_LIBRARY);
int libraryCount = mIn.readInt();
int packageId;
String packageName;
for (int i = 0; i < libraryCount; i++) {
packageId = mIn.readInt();
packageName = mIn.readNullEndedString(128, true);
System.out.printf("Decoding Shared Library (%s), pkgId: %d\n", packageName, packageId);
}
while (nextChunk().type == Header.TYPE_TYPE) {
readTableTypeSpec();
}
}3. QA Pipeline Configuration
The modified APK is processed with a Gradle task:
./gradlew :app:resguardUseApk -PHOST=${assemble_host} -PMODE=debugBecause the modification touches many internal files, the release uses single‑gray verification to ensure stability. The optimization yields an 8% size reduction and is already applied to multiple plugins.
4. Cautions
When accessing resources by name, the names must be whitelisted. It is recommended to use a lint rule to scan for getIdentifier calls:
fixedResId = context.getApplicationContext().getResources()
.getIdentifier(resName, null, null);
uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+ resources.getResourcePackageName(model) + '/'
+ resources.getResourceTypeName(model) + '/'
+ resources.getResourceEntryName(model));Related Links
• Android Resource Documentation
• AndResGuard Project
Baidu Geek Talk
Follow us to discover more Baidu tech insights.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.