Ctrip Android App Plugin Architecture and Dynamic Loading Framework
This article explains the motivations, design principles, compilation and runtime techniques of Ctrip's Android plugin‑based architecture and dynamic loading framework, detailing how it solves method‑count limits, improves build speed, enables hot‑deployment, and outlines the associated trade‑offs.
Demand Driven
In 2014, as business expanded and Ctrip's wireless department was split, product modules were assigned to different business units, fragmenting the original development team into framework, hotel, flight, and train teams. This new model dramatically increased communication cost and made the previous collaboration approach unsustainable, prompting the need for a new development model and technical solution.
From a technical perspective, Ctrip had already encountered Android's notorious 65,535 method limit in 2012. The old workaround placed third‑party libraries into a secondary dex and used a Facebook‑discovered hack to enlarge LinearAllocHdr from 5 MB to 8 MB. As the codebase grew, this solution became insufficient, and the only viable path was to split dex files intelligently.
Organizational changes also raised the bar for app quality control, exhausting the development team. While the front‑end teams enjoyed hot‑deployment, the native Android architecture lacked such capability. Plugin‑based dynamic loading offered the needed hot‑deployment feature.
From these fundamental demands, the benefits of a plugin‑based dynamic loading architecture became clear, including faster compilation, quicker startup, A/B testing, and on‑demand module downloading.
Compilation speed improvement: Splitting the project into about ten sub‑projects reduced the previously hour‑long compile time on a Windows 7 HDD.
Startup speed improvement: MultiDex loads all dex files on the main thread, causing long black screens and possible ANR; the plugin approach avoids this.
A/B testing: Allows independent development of AB modules instead of mixing code in a single module.
Selective module download: Debug modules can be downloaded only when needed, reducing overall app size.
Principles
Plugin concepts are familiar from browsers, IDEs, and even QQ. Implementing plugins on Android requires solving two problems:
Compile‑time: resource and code compilation.
Run‑time: resource and code loading.
Once these are addressed, the specific plugin APIs become a matter of preference or scenario.
How Android Compiles
The compilation pipeline involves aapt, javac, ProGuard, and dex. The key stages are highlighted in the original diagram.
Resource Compilation
Android uses the <SDK>/build-tools/<buildToolsVersion>/aapt tool. Important aapt parameters include: -I: adds an existing package to the base include set, allowing the compiler to reference the SDK's android.jar resources or another APK. -G: outputs ProGuard keep rules discovered during resource compilation, preventing reflection‑related classes from being stripped. -J: specifies where to generate the R.java resource constants.
All resources receive integer IDs stored in R.java. The ID consists of three fields: package ID, type ID, and entry ID. Example:
// Resource from android.jar, PackageID = 0x01
public static final int cancel = 0x01040000;
// Resource from the app, PackageID = 0x7F
public static final int zip_code = 0x7f090f2e;By modifying aapt we can assign distinct PackageIDs to each sub‑APK, preventing ID collisions.
Code Compilation
Standard Java compilation considerations apply:
Classpath: all dependent jars and directories must be specified.
Obfuscation: most Android projects use ProGuard; keep rules must be generated from resource analysis.
Implementation
Implementation consists of two parts: (1) compile‑time modifications for plugin sub‑projects, and (2) runtime dynamic loading (resource and class loading).
Plugin Resource Compilation
Use the -I parameter to reference the host APK, enabling plugins to use host resources and layouts.
Add --apk-module to aapt so each plugin receives a unique PackageID, allowing the loader to identify the source APK.
Add --public-R-path so aapt copies the host's R definitions into the plugin, letting the plugin reference base.R without code changes. This requires a naming convention to avoid duplicate resource names.
Plugin Code Compilation
Classpath must include android.jar, third‑party libraries, and the host's base.jar. The host's public/protected APIs must not be obfuscated.
During obfuscation, the host's processed jar is added as a reference library.
After compilation, plugin APKs are merged into the pre‑built host APK and re‑signed.
Runtime Resource Loading
Resources are normally accessed via Context.getAssets() and Context.getResources(), implemented in ContextImpl. By overriding these methods and using the hidden AssetManager.addAssetPath(String) (invoked via reflection), we can add a plugin APK's asset path so the system loads its resources.arsc and compiled resources.
/** Add an additional set of assets to the asset manager. */
public final int addAssetPath(String path) {
synchronized (this) {
int res = addAssetPathNative(path);
makeStringBlocks(mStringBlocks);
return res;
}
}Instrumentation is then used to replace the host's Resources with a delegate that knows how to resolve plugin resources, ensuring a seamless experience for Activities, Services, etc.
Runtime Class Loading
Android's PathClassLoader holds a DexPathList containing dex file entries. By appending additional dex paths (similar to Google’s MultiDex), plugins can be loaded at runtime.
private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
File optimizedDirectory) throws ... {
Field pathListField = findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
expandFieldArray(dexPathList, "dexElements",
makeDexElements(dexPathList, new ArrayList<File>(additionalClassPathEntries), optimizedDirectory));
}Version‑specific handling may be required, but the principle remains the same.
Benefits and Costs
Benefits
Aligns with organizational structure, achieving high cohesion and low coupling across BUs.
Eliminates the 65 K method limit by splitting into multiple plugins.
Provides HotFix capability at class or full‑APK level.
Enables clean A/B testing without tangled conditional code.
Greatly improves compilation speed for individual BUs.
Reduces host APK size; plugins are loaded on demand, improving startup time and avoiding ANR.
Allows precise control of app size per BU.
Costs
Resource aliasing can cause ID mismatches on some devices (e.g., Samsung S6), requiring the feature to be disabled.
Duplicate resource names between host and plugins can cause conflicts; mitigated by strict naming conventions.
Enum IDs are generated without a namespace, so host and plugin enums may clash; also mitigated by naming rules.
External access to plugin resources before the app starts is not possible; such resources must reside in the host APK.
Future Optimizations
Support native .so libraries in plugin projects.
Allow plugins to depend on .aar, Maven, and other advanced dependency types.
Improve IDE integration for easier plugin APK generation.
Open Source
The full implementation can be found in the open‑source GitHub project DynamicAPK . The Ctrip Mobile R&D team will continue to share experience and updates. Follow the Ctrip App team on WeChat (CtripMobile) for more information.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Ctrip Technology
Official Ctrip Technology account, sharing and discussing growth.
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.
