DynamicAPK
Solution to implement multi apk dynamic loading and hot fixing for Android App. (实现Android App多apk插件化和动态加载,支持资源分包和热修复)
Top Related Projects
Quick Overview
DynamicAPK is a framework developed by Ctrip Mobile that enables dynamic loading of Android application components, allowing developers to update their apps without the need for a full app update. This approach can significantly reduce the app's size and improve the user experience.
Pros
- Dynamic Loading: DynamicAPK allows developers to dynamically load and unload application components, reducing the initial app size and enabling faster updates.
- Modular Design: The framework promotes a modular design approach, making it easier to manage and maintain different parts of the application.
- Improved User Experience: By reducing the app size and enabling faster updates, DynamicAPK can improve the overall user experience.
- Reduced Maintenance Costs: The dynamic loading feature can help reduce the costs associated with app updates and maintenance.
Cons
- Complexity: Integrating DynamicAPK into an existing project may add complexity to the development process, requiring additional setup and configuration.
- Potential Performance Impact: Dynamically loading components may have a slight performance impact, which needs to be carefully managed and monitored.
- Dependency Management: Developers need to carefully manage the dependencies between the dynamically loaded components to ensure compatibility and stability.
- Limited Platform Support: DynamicAPK is currently designed for Android platforms, and there may be limited support for other platforms.
Code Examples
Since DynamicAPK is a framework for Android, here are a few code examples demonstrating its usage:
// Initializing the DynamicAPK framework
DynamicAPK.init(this);
// Dynamically loading a module
DynamicAPK.loadModule("my_module", new DynamicAPK.ModuleLoadCallback() {
@Override
public void onModuleLoaded(Module module) {
// Module is now loaded and ready to use
}
@Override
public void onModuleLoadFailed(Exception e) {
// Handle module load failure
}
});
// Unloading a module
DynamicAPK.unloadModule("my_module");
// Checking if a module is loaded
boolean isModuleLoaded = DynamicAPK.isModuleLoaded("my_module");
These examples demonstrate how to initialize the DynamicAPK framework, dynamically load and unload modules, and check the load status of a module.
Getting Started
To get started with DynamicAPK, follow these steps:
- Add the DynamicAPK dependency to your project's
build.gradle
file:
dependencies {
implementation 'com.ctrip.android:dynamic-apk:1.0.0'
}
- Initialize the DynamicAPK framework in your application's
onCreate()
method:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
DynamicAPK.init(this);
}
}
-
Integrate DynamicAPK into your app's architecture by defining and managing your application's modules. Refer to the DynamicAPK documentation for detailed instructions on how to do this.
-
Utilize the DynamicAPK API to dynamically load and unload modules as needed, as shown in the code examples above.
Competitor Comparisons
A powerful Android Dynamic Component Framework.
Pros of Atlas
- Atlas provides a more comprehensive solution for dynamic loading and hot-patching, including support for resources, native libraries, and other assets.
- Atlas has a larger and more active community, with more contributors and a wider range of use cases.
- Atlas offers better performance and stability, with a more mature and optimized codebase.
Cons of Atlas
- Atlas has a steeper learning curve and requires more configuration and setup compared to DynamicAPK.
- Atlas may be overkill for simpler use cases, where DynamicAPK could be a more lightweight and easier-to-use solution.
- Atlas has a larger codebase and dependency footprint, which could be a concern for some projects.
Code Comparison
DynamicAPK:
public class DynamicApkManager {
public static void loadDynamicApk(Context context, String apkPath) {
try {
AssetManager assetManager = context.getAssets();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, apkPath);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Atlas:
public class AtlasUpdater {
public static void update(Context context, String apkPath) {
try {
AtlasDelegate.getInstance().update(context, apkPath);
} catch (Exception e) {
e.printStackTrace();
}
}
}
The key difference is that Atlas provides a more comprehensive and abstracted API for dynamic loading, while DynamicAPK has a more direct and lower-level approach.
Android V1 and V2 Signature Channel Package Plugin
Pros of VasDolly
- VasDolly supports dynamic loading of resources, which can reduce the initial APK size and improve the app's startup performance.
- VasDolly provides a plugin-based architecture, allowing developers to easily integrate new features without modifying the core codebase.
- VasDolly has a well-documented API and a large community, making it easier to get started and find support.
Cons of VasDolly
- VasDolly may have a steeper learning curve compared to DynamicAPK, as it requires a deeper understanding of the plugin-based architecture.
- VasDolly may have a larger codebase and dependencies, which could potentially increase the overall complexity of the project.
Code Comparison
DynamicAPK:
public class DynamicApkManager {
public static void loadDynamicApk(String apkPath) {
// Load the dynamic APK
DexClassLoader classLoader = new DexClassLoader(apkPath, getCacheDir().getAbsolutePath(), null, getClassLoader());
// Invoke the entry point of the dynamic APK
Class<?> entryClass = classLoader.loadClass("com.example.dynamic.EntryPoint");
Method entryMethod = entryClass.getMethod("main", String[].class);
entryMethod.invoke(null, new Object[] { new String[0] });
}
}
VasDolly:
public class VasDollyManager {
public static void loadPlugin(String pluginPath) {
// Load the plugin
PluginManager.getInstance().loadPlugin(pluginPath);
// Invoke the entry point of the plugin
PluginManager.getInstance().invokePlugin("com.example.plugin.EntryPoint", "main", new Object[0]);
}
}
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
Introduction
What is DynamicAPK?
DynamicAPK is a solution that contains framework, tool and configuration to implement multi apk/dex dynamic loading. It can help reorganize Android project configuration and development model to achieve sub-projects parallel development (in the form of android studio module), while supporting hot fix (repairing online bug), on-demand loading seldom-used modules. All dynamically loaded modules not only contain code but also contain resources if you need.
DynamicAPK is already uesed in Ctrip Android App (Simplified Chinese Version). Ctrip is the biggest online travel agency in China, while 72 percent of orders are from App.
Benefits
-
Less transformation effort (no activity/fragment/resource proxy stuff)
DynamicAPK doesn't need activity or fragment proxy to manage their life cycle. Modules' resources are processed by modified aapt, so resource reference in R.java is not different with normal Android project. Developers can maintain their original development paradigm.
-
Parallel development
-
Speed up compilation
-
Speed up app booting
MultiDex solution offered by Google will execute dex decompression, dexopt, load operation in the main thread. That means a very long process, while users will see significant long black screen and more likely to encounter the ANR. By DynamicAPK, app loads only the necessary modules, other modules are loaded on demand.
-
Hot fix (code and resource)
-
On-demand module (code and resource) downloading and loading
Comparasion
-
Heavy develpment paradigm transformation: use "that" instead of "this", activity should inherit from their proxy avtivity (The proxy activity manage life cycle).
Restrictions of starting activity within module apk.
Doesn't support Service and BroadcastReceiver.
-
Heavy develpment paradigm transformation:
Changes the usage of resources:
MyResources.getResource(Me.class)
instead ofcontext.getResources()
.Use Fragment as UI container, each page is implemented in Fragment instead of Activity. So you need use URL mapping to start new page.
-
Not tested in released App.
Doesn't support Service and BroadcastReceiver.
-
DroidPlugin from Qihu360
Very interesting framework! DroidPlugin can start totally independent app (not installed) in your app. The features are more suitable for Qihu360 security app because the bundle apk is totally irrelevant to host apk.
Doesn't support custom nitification.
Implementation
We focus on aapt, javac, proguard and dex process. The key of dynamic loading is about two things:
Code compilation and loading
Java compilation is nothing special, while class loading needs some hacking. Android's DexClassLoader has some restrictions, so we use Android's system PathClassLoader. PathClassLoader has a member pathList, as the name suggests it is essentially a List to load classes from each dex path in the list at runtime. So we can add our dynamically loaded dex at the head of the list. In fact, Google's official MultiDex library is also implemented by the method. The following snippet shows the details:
MultiDex.java
private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
File optimizedDirectory)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
/* The patched class loader is expected to be a descendant of
* dalvik.system.BaseDexClassLoader. We modify its
* dalvik.system.DexPathList pathList field to append additional DEX
* file entries.
*/
Field pathListField = findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
new ArrayList<File>(additionalClassPathEntries), optimizedDirectory));
}
For different versions of Android, class loading has a slightly different way. Reference [MultiDex Source] (https://android.googlesource.com/platform/frameworks/multidex/+/d79604bd38c101b54e41745f85ddc2e04d978af2/library/src/android/support/multidex/MultiDex.java).
Resource compilation and loading
Resource compilation is proceesed by Android tool: aapt, which is located in <SDK> / build-tools / <buildToolsVersion> / aapt
, with many [command line parameter] (http: //7xns6i.com1.z0 .glb.clouddn.com / ctrip-pluggable / aapt.txt "aapt Command Line Reference"). Some of them deserve special attention:
-
-I Add an existing package to base include set
This parameter is to add an existing package in the dependency path. In Android, the compilation of resources also need rely on android.jar. "android.jar" is not an ordinary jar package, which contains the existing SDK library class, compiled resources and resource index file (resources.arsc). Similarly, we can also use this parameter references an existing apk packages as dependencies resources to participate in the compilation.
-
-G A file to output proguard options into.
In resource compilation, component class and method references will result in runtime reflection invocation, so this kind of symbol can not be confused. -G parameter will derive classes and interfaces found in the resource compilation process that must be kept. It will participate to the confusion in the late stage as an additional configuration file.
-
-J Specify where to output R.java resource constant definitions
In Android, all resources will be generated as corresponding constant ID, the ID will be merged to R.java file. Resource ID in R.java is a four-byte int type. Actually it consists of three fields. The first byte represents the package, the second byte represents type, three and four bytes represent real ID. E.g: Â Â Â Â
//android.jar resources, PackageID is 0x01 public static final int cancel = 0x01040000;
    // User app resources, PackageID is 0x7F public static final int zip_code = 0x7f090f2e; ```
We modifed aapt to provide each module different PackageID, so there will be no conflict.
-
To add new aapt
--apk-module
parameter.As previously mentioned, we specified for each module project unique PackageID field, so we can find where to find and load resources. In the resource loading section there will be more details. Â Â Â
-
To add new aapt
--public-R-path
parameter.Android system resources can be referenced by its fully qualified name
android.R
to refer specific source. If we usebase.package.name.R
for modules to refer public app common resources, that means we need modify every existed resource reference code. It's error-prone and less transparent in the future development. We add--public-R-path
parameter to specifybase.R
's location and make copy of common resource ID into modules' R.java.
Resource loading is processed by AssetManager and Resources class. We can access them in the Context.
Context.java
/** Return an AssetManager instance for your application's package. */
public abstract AssetManager getAssets();
/** Return a Resources instance for your application's package. */
public abstract Resources getResources();
They are two abstract methods, implementation is in ContextImpl class. After initialization of ContextImpl class objects, each subclass of Context such as Activity, Service and other components can access resources by these two methods.
ContextImpl.java
private final Resources mResources;
@Override
public AssetManager getAssets() {
return getResources().getAssets();
}
@Override
public Resources getResources() {
return mResources;
}
Since we allocate PackageID (the first byte of resource ID) by aapt to know where to find resource's apk, we override these two methods to find specific resource.
And there is a hidden method addAssetPath in AssetManager, so we can add a resource path to AssetManager.
/ **
* Add an additional set of assets to the asset manager. This can be
* Either a directory or ZIP file. Not for use by applications. Returns
* The cookie of the added asset, or 0 on failure.
* {hide}
* /
public final int addAssetPath(String path) {
   synchronized(this) {
       int res = addAssetPathNative(path);
       makeStringBlocks(mStringBlocks);
       return res;
   }
}
We just need to reflect this method, then add all apk's location to AssetManager. AssetManager will finish the resource loading by compiled resources.arsc resources within apk.
To achieve "seamless" experience, we need last step: using the Instrumentation to take over all creation of Activity , Service and other components. Activity, Service and other system components will be loaded in the main thread by android.app.ActivityThread. ActivityThread class has a member mInstrumentation, that is responsible for creating Activity and other operations. So it's the best candidate for loading our modified resource class. Every time the system creates Activity, we replace its mResources by our DelegateResources that will know how to load resources. Done!
Usage
aapt
-
Use parameter --apk-module to allocate packageID
E.g: ex: aapt ...... --apk-module 0x58 ï¼PackageID in ResourceID is 0x58ï¼
-
Use parameter --public-R-path to merge R.java (RMerge.cpp)
Then the output R file contains the base apk R.java and the module apk R.java.
Build
- $ git clone https://github.com/CtripMobile/DynamicAPK.git
- $ cd DynamicAPK/
- $ gradle assembleRelease bundleRelease repackAll
- Release APK in /build-outputs/***-release-final.apk
Simplified Chinese Version
ä»ç»
DynamicAPKæ¯ä¸å¥ç¨äºå®ç°å¤dex/apkå è½½ç解å³æ¹æ¡ãå®å¯ä»¥å¸®å©ä½ éæ°ç»ç»Androidå·¥ç¨çé ç½®åå¼å模å¼ï¼å®ç°å¤ä¸ªåå·¥ç¨å¹¶è¡å¼åï¼ä»¥android studio moduleçå½¢å¼ï¼ï¼åæ¶æ¯æhot fixï¼å¨çº¿ä¿®å¤æé®é¢çåè½ï¼, æ件å¼è½½å ¥ä¸å¸¸ç¨çåè½ï¼ä¸è½½æ件ååè½½å ¥ï¼ãææå¨æå è½½çæ件ä¸ä» å å«ä»£ç ï¼ä¹å¯ä»¥å å«èµæºï¼èµæºçå¨æå è½½æ¯ä»£ç è¦éº»ç¦å¾å¤ï¼ï¼å æ¤æ¯ä»¥APKå½¢å¼å®ç°çã
DynamicAPKå·²ç»å¨æºç¨æ è¡Android Appä¸ä½¿ç¨ï¼æ¬¢è¿å ³æ³¨æºç¨ç§»å¨ææ¯å ¬ä¼å·ï¼CtripMobile
ä»·å¼
-
æ´å°çè¿ç§»ææ¬ï¼æ éåä»»ä½activity/fragment/resourceçproxyå®ç°ï¼
DynamicAPKä¸éè¦å®ç°ä»»ä½activityæfragment proxyæ¥ç®¡çä»ä»¬ççå½å¨æãä¿®æ¹åçaaptä¼å¤çæ件ä¸çèµæºï¼å æ¤R.javaä¸çèµæºå¼ç¨åæ®éAndroidå·¥ç¨æ²¡æåºå«ãå¼åè å¯ä»¥ä¿æåæçå¼åèå¼ï¼æ éåç¹æ®çæ´æ¹ã
-
并åå¼å
-
æåç¼è¯é度
-
æåå¯å¨é度
Googleæä¾çMultiDexæ¹æ¡ï¼ä¼å¨ä¸»çº¿ç¨ä¸æ§è¡æædexç解åãdexoptãå è½½æä½ï¼è¿æ¯ä¸ä¸ªé常漫é¿çè¿ç¨ï¼ç¨æ·ä¼ææ¾ççå°é¿ä¹ çé»å±ï¼æ´å®¹æé æ主线ç¨çANRï¼å¯¼è´é¦æ¬¡å¯å¨åå§å失败ãDynamicAPKå¯ä»¥å¨Appå¯å¨æ¶ä» å è½½å¿ é¡»ç模åï¼å ¶ä»æ¨¡åæéå è½½ã
-
Hot fix (å å«ä»£ç åèµæº)
-
æéä¸è½½åå 载任æåè½æ¨¡å(å å«ä»£ç åèµæº)
对æ¯
-
è¿ç§»ææ¬å¾éï¼éè¦ä½¿ç¨ãthatãèä¸æ¯ãthisãï¼ææactivityé½éè¦ç»§æ¿èªproxy avtivityï¼proxy avtivityè´è´£ç®¡çææactivityççå½å¨æï¼ã
æ æ³å¯å¨apkå é¨çactivityã
ä¸æ¯æServiceåBroadcastReceiverã
-
è¿ç§»ææ¬å¾éï¼
使ç¨èµæºæ¶è¦ç¨
MyResources.getResource(Me.class)
èä¸æ¯context.getResources()
使ç¨Fragmentä½ä¸ºUI容å¨ï¼æææ¯ä¸ªé¡µé¢é½æ¯ä½¿ç¨Fragmentèä¸æ¯Activityï¼éè¦ä½¿ç¨URL mappingæè½å®ç°é¡µé¢è·³è½¬ã
-
æªç»è¿ç产ç¯å¢Appæµè¯ã
ä¸æ¯æServiceåBroadcastReceiverã
-
DroidPlugin from å¥è360
é常æ趣çæ¡æ¶ï¼DroidPluginè½å¤å¨ä¸ä¸ªAppå å¯å¨ä¸ä¸ªæ²¡æå®è£ çAppãè¿ä¸ªç¹æ§å¯è½æ´éå360çå®å ¨äº§åï¼å 为被å¯å¨çAppå宿主Appå®å ¨æ²¡æä»»ä½å ³èï¼ç¸äºé´ä¸è½æ¯æèµæºå代ç è°ç¨ã
ä¸æ¯æèªå®ä¹æ¨éæ ã
å®ç°ç»è
æ´æ·±å ¥çåææç« è¯¦è§ InfoQ -ãæºç¨Android Appæ件ååå¨æå è½½å®è·µã
使ç¨æ¹æ³
aapt
-
使ç¨åæ° --apk-module æ¥åé PackageID
E.g: ex: aapt ...... --apk-module 0x58 ï¼ResourceIDçPackageIDå®ä¹ä¸º0x58ï¼
-
使ç¨ä¸ªåæ° --public-R-path æ¥å并R.java (å®ç°æ件å¨RMerge.cppä¸)
çæçR.javaæ件ä¼å并åºç¡APKå模åAPKä¸çR.javaã
Build
- $ git clone https://github.com/CtripMobile/DynamicAPK.git
- $ cd DynamicAPK/
- $ gradle assembleRelease bundleRelease repackAll
- Release APK in /build-outputs/***-release-final.apk
Top Related Projects
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot