Mobile Development 15 min read

Meituan Android Plugin Framework: Design, Compatibility, and Build System

Meituan’s Android plugin framework combines MultiDex‑style dex loading, global AssetManager replacement, component proxies, and a four‑phase Gradle integration to enable stable AAR‑based development while dynamically loading plugins across Android versions and OEM ROMs, handling resources, bytecode, and native libraries with minimal compatibility issues.

Meituan Technology Team
Meituan Technology Team
Meituan Technology Team
Meituan Android Plugin Framework: Design, Compatibility, and Build System

In the Android ecosystem, pluginization has become a standard technique for large‑scale platform apps. By 2017, open‑source frameworks such as Atlas, Replugin and VirtualAPK marked the maturity of the technology, yet each framework is tailored to its own business needs and struggles with compatibility across Android versions and OEM custom ROMs.

Meituan’s platform, which serves dozens of business lines (e.g., food delivery, ride‑hailing, finance), requires a plugin solution that preserves high stability for mature services while allowing rapid iteration for fast‑moving ones, all without changing the existing AAR‑based development workflow.

Key compatibility measures implemented by Meituan include:

Dex loading using a MultiDex‑like approach to keep reflection compatible.

Replacing every AssetManager instance to ensure resource access works in plugins.

Embedding proxies for the four Android components to support dynamic Activity loading.

Integrating the build system to bridge the gap between AAR and plugin development modes.

Resource handling is a major pain point. Simply adding a plugin’s path to an AssetManager is insufficient because the manager must be injected into the actual Resources or ResourcesImpl objects used at runtime. Meituan’s solution performs a global replacement:

Create or reuse an AssetManager that loads plugin assets.

Locate all Resources / Theme objects and swap their internal AssetManager.

Clear cached resources and rebuild themes.

Protect the rebuilt AssetManager from being overwritten.

Below are representative code snippets illustrating the approach.

for (Activity activity : activities) {
    // ... omitted code
    Resources.Theme theme = activity.getTheme();
    try {
        try {
            Field ma = Resources.Theme.class.getDeclaredField("mAssets");
            ma.setAccessible(true);
            ma.set(theme, newAssetManager);
        } catch (NoSuchFieldException ignore) {
            Field themeField = Resources.Theme.class.getDeclaredField("mThemeImpl");
            themeField.setAccessible(true);
            Object impl = themeField.get(theme);
            Field ma = impl.getClass().getDeclaredField("mAssets");
            ma.setAccessible(true);
            ma.set(impl, newAssetManager);
        }
        // ...
    } catch (Throwable e) {
        Log.e(LOG_TAG, "Failed to update existing theme for activity " + activity, e);
    }
    pruneResourceCaches(resources);
}

Another example shows how to intercept bytecode that stores a Resources.Theme in a static field, using ASM to inject a collector method:

static class MyMethodVisitor extends MethodVisitor {
    int stackSize = 0;
    MyMethodVisitor(MethodVisitor mv) { super(Opcodes.ASM5, mv); }
    @Override
    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
        if (opcode == Opcodes.PUTFIELD || opcode == Opcodes.PUTSTATIC) {
            if ("Landroid/content/res/Resources$Theme;".equals(desc)) {
                stackSize = 1;
                visitInsn(Opcodes.DUP);
                super.visitMethodInsn(Opcodes.INVOKESTATIC,
                    "com/meituan/hydra/runtime/Transformer",
                    "collectTheme",
                    "(Landroid/content/res/Resources$Theme;)V",
                    false);
            }
        }
        super.visitFieldInsn(opcode, owner, name, desc);
    }
    @Override
    public void visitMaxs(int maxStack, int maxLocals) {
        super.visitMaxs(maxStack + stackSize, maxLocals);
        stackSize = 0;
    }
}

The build system is divided into four phases—dependency collection, resource processing, code processing, and packaging/signing. It inserts plugin‑specific tasks without disrupting the normal Gradle flow:

After the host resolves its dependencies, the system analyzes plugin dependencies, performs arbitration, and tracks reference counts.

Before the host processes resources, plugin resources are merged and an AAPT‑compatible list is generated.

During code processing, plugin API pitfalls are mitigated by reusing the host’s ProGuard and Gradle plugins; mapping issues are also fixed.

Before signing, the plugin APK is built, a compatibility hash is calculated, and V2 signing is applied for fast runtime verification.

To avoid “API traps” such as missing animation resources, unsupported style parents, or native library loading failures, Meituan’s system automatically extracts relevant assets from the plugin’s AndroidManifest.xml, scans bytecode for resource‑related API calls, and rewrites problematic patterns (e.g., a custom system_loadLibrary that retries missing .so files).

private static Pattern compile = Pattern.compile("dlopen failed: library \"lib(.+)\.so\" not found");
public static void system_loadLibrary(String libname) {
    LinkedList<String> list = new LinkedList<>();
    list.add(libname);
    while (list.size() > 0) {
        try {
            System.loadLibrary(list.peekFirst());
            list.pop();
        } catch (UnsatisfiedLinkError error) {
            Matcher matcher = compile.matcher(error.getMessage());
            if (matcher.matches()) {
                String group = matcher.group(1);
                list.addFirst(group);
            } else {
                throw error;
            }
        }
    }
}

Conclusion

The article demonstrates how Meituan’s plugin framework achieves seamless switching between traditional AAR integration and dynamic plugin deployment, with minimal compatibility issues. Recent Meituan app releases load critical modules such as search, favorites, and orders as plugins, validating the effectiveness of the design.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

AndroidResource ManagementCompatibilityBuild SystemPlugin Frameworkbytecode injection
Meituan Technology Team
Written by

Meituan Technology Team

Over 10,000 engineers powering China’s leading lifestyle services e‑commerce platform. Supporting hundreds of millions of consumers, millions of merchants across 2,000+ industries. This is the public channel for the tech teams behind Meituan, Dianping, Meituan Waimai, Meituan Select, and related services.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.