Optimizing Rust Android Dynamic Library Size: Strategies and Results
This article details how the author reduced the size of a Rust‑based Android JNI dynamic library from 495 KB to 95 KB by adjusting compiler optimization levels, enabling LTO, switching panic handling to abort, removing unused strings, applying panic_immediate_abort, and crafting a custom linker script, while providing code snippets and size comparison tables.
Background
The size of an application package directly impacts download rates, installation time, and device storage usage; Google Play reports that each 6 MB increase reduces conversion by 1 %.
The author’s Rust‑written JNI library initially weighed 495 KB after stripping, prompting a series of size‑reduction measures.
Why Rust?
Rust offers stability and safety for JNI code, eliminating many memory‑management bugs and making reverse engineering harder, though its default build produces relatively large binaries.
Optimization Strategies
Adjust Optimization Level
Changing the Cargo profile from the default opt-level = 3 to opt-level = 'z' targets minimal binary size.
[profile.release]
opt-level = 'z'Result: size reduced from 495 KB to 437 KB.
Enable LTO
Link‑time optimization removes redundant code at the cost of longer link times.
Cargo.toml
[profile.release]
opt-level = 'z'
lto = trueResult: size marginally decreased to 436 KB.
Abort on Panic
Replacing the default panic backtrace with panic = 'abort' eliminates the extra unwind information.
[profile.release]
opt-level = 'z'
lto = true
panic = 'abort'Result: size dropped to 366 KB.
Remove Unused Strings & Enable panic_immediate_abort
By trimming unnecessary string literals in third‑party crates and disabling generated formatting strings, further reductions are achieved.
.cargo/config.toml
[unstable]
build-std-features = ["panic_immediate_abort"]
build-std = ["std","panic_abort"]Result: size fell to 135 KB, with the author’s core code accounting for 52 % of the total.
Linker Script Optimization
Analyzing ELF sections with readelf -S revealed removable sections. A custom linker script keeps only essential sections and discards the rest.
PHDRS
{
headers PT_PHDR PHDRS ;
text PT_LOAD FILEHDR PHDRS ;
data PT_LOAD ;
dynamic PT_DYNAMIC ;
}
ENTRY(Reset);
EXTERN(RESET_VECTOR);
SECTIONS
{
. = SIZEOF_HEADERS;
.text : { *(.text .text.*) } :text
.rodata : { *(.rodata .rodata.*) } :text
. = . + 0x1000;
.data : { *(.data .data.*) *(.fini_array .fini_array.*) *(.got .got.*) *(.got.plt .got.plt.*) } :data
.bss : {*(.bss .bss.*)} :data
.dynamic : { *(.dynamic .dynamic.*) } :data :dynamic
/DISCARD/ :
{
*(.ARM.exidx .ARM.exidx.*);
*(.gnu.version .gnu.version.*);
*(.gnu.version_r .gnu.version_r.*);
*(.eh_frame_hdr .eh_frame .eh_frame_hdr.* .eh_frame.* );
*(.note.android.ident .note.android.ident.*);
*(.comment .comment.*);
}
}Updating Cargo configuration to use the new script further reduced the binary to 95 KB, meeting the target size requirement.
Summary
Compilation Option
Size
strip
495 KB
strip + opt-level = 'z'
437 KB
strip + opt-level = 'z' + lto
436 KB
strip + opt-level = 'z' + lto + panic = 'abort' + code trimming + panic_immediate_abort
135 KB
All above + section removal (linker script)
95 KB
The techniques described are applicable to other languages such as C/C++ and can serve as a reference for developers needing to shrink native libraries for mobile platforms.
JD Tech Talk
Official JD Tech public account delivering best practices and technology innovation.
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.