Home

Awesome

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

Comparasion

Implementation

Android Build Process

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:

     // 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.

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

Build

Simplified Chinese Version

介绍

DynamicAPK是一套用于实现多dex/apk加载的解决方案。它可以帮助你重新组织Android工程的配置和开发模式,实现多个子工程并行开发(以android studio module的形式),同时支持hot fix(在线修复有问题的功能), 插件式载入不常用的功能(下载插件后再载入)。所有动态加载的插件不仅包含代码,也可以包含资源(资源的动态加载比代码要麻烦很多),因此是以APK形式实现的。

DynamicAPK已经在携程旅行Android App中使用,欢迎关注携程移动技术公众号:CtripMobile

价值

对比

实现细节

更深入的分析文章详见 InfoQ -《携程Android App插件化和动态加载实践》

使用方法

aapt

Build