跪拜 Guibai
← Back to the summary

ComboNative Puts Real Kotlin/Compose Code Inside Android Mini Programs, Each in Its Own Process

Let 'Mini Programs' Run Real Native Code: ComboNative Multi-Process Native Mini Program Runtime · Alpha Launch

An Android dynamic framework: Each mini program is native Kotlin/Compose, runs in an independent process, and can be independently obfuscated and hardened from the host. The framework runtime is about 347 KB, and a fully functional mini program package can be as low as 32.8 KB. A complete demo (release) containing three mini programs is only 7.74 MB.

The main library is not yet open source, but has been published to Maven Central. Alpha early access is now open, and you can directly integrate it for trial. This article is long; it is recommended to skip read by subheadings as needed: from 'Why build it' to 'How to use it' to 'Where are the boundaries', explained thoroughly at once.

0_cover.png


demo_light_480p12.gif


TL;DR (30-second speed read)

If you don't have time to read the full article, just remember these points:


🚀 Experience Immediately (Don't want to read the long article? Get started first)

Entry Link
📥 Sample App Download (No build needed, install and play) https://130.94.11.104:17558/down/9yiZ75pfO4QR.apk
🧩 Sample Mini Program .cbp Downloads Mini Program ① · Mini Program ② · Mini Program ③
🔗 Sample Open Source Repository (Host + 3 native mini programs, can be independently cloned and built) https://github.com/lnzz123/combonative-samples
📚 Maven Integration io.github.lnzz123:combonative-engine:0.1.0-alpha01 (and abi/runtime/mini-sdk)
🧰 Packaging Plugin (Gradle Plugin Portal) io.github.lnzz123.combonative-packer:0.1.0-alpha01

If you find it interesting, feel free to give a ⭐ Star, raise an Issue, and participate in the Alpha public test together. If you want to know 'why it was designed this way', continue reading below.


Table of Contents

  1. A Real Dilemma: Let's Start with a Story
  2. Background: Why Build ComboNative
  3. Its Relationship with ComboLite
  4. Design Goals and Core Trade-offs (including a three-way comparison)
  5. Core Mechanisms (Principle Level)
  6. Lightweight: Reproducible Size Data
  7. Ten-Minute Hands-On: From Host Integration to Running Your First Mini Program
  8. Real Security Boundaries and Applicable Scenarios
  9. Selection: ComboLite or ComboNative
  10. Frequently Asked Questions (FAQ)
  11. Current Status
  12. Early Access and Resources

〇、A Real Dilemma: Let's Start with a Story

Imagine you are building an in-vehicle system (or a smart cockpit / all-in-one machine / industry PDA, pick one). The product manager puts forward three seemingly ordinary but actually conflicting requirements:

  1. Must be able to dynamically deliver features. After the car is sold, 'charging maps', 'member malls', 'third-party ecosystem apps' must be able to update remotely and install on demand; it's impossible to do a full OTA every time.
  2. Must have native experience and capabilities. Any stutter or dropped frame on the central control screen will be magnified into 'this car is cheap'; and it needs to use underlying capabilities like Bluetooth, CAN bus, location, camera. Wrapping it in a JS container is both slow and limited everywhere.
  3. Must be stable and secure. The in-vehicle system runs for months without a reboot; any third-party module crash or memory leak must not affect the main HMI; partner modules cannot arbitrarily read things you don't want to give them, and your own core code cannot be easily reverse-engineered.

So you oscillate between two old paths:

You will find that no existing path can satisfy these three points simultaneously. This triangular contradiction of 'native vs dynamic vs isolation' is precisely the starting point for me to build ComboNative. It does not try to reinvent Android, but rather reorganizes Android's existing capabilities—using 'multi-process + static stubs + narrow contracts' to take care of these three corners at once. Let's start from the beginning.


一、Background: Why Build ComboNative

Before creating ComboNative, I maintained the previous generation single-process pluginization framework ComboLite (0 Hook / 0 Reflection, Compose-first, open-sourced and on Maven Central). ComboLite achieved a fairly complete level of 'clean loading' for single-process pluginization, but when pushed to enterprise-level, production-grade scenarios, it exposed three structural limitations rooted in the architectural foundation, difficult to cure by patching:

The conclusion is clear: these problems have little room for optimization within the single-process model and require a redesign from the process model level. ComboNative comes from this—it is not a version upgrade of ComboLite, but a next-generation runtime redesigned under the same technical lineage to address the above structural defects.


二、Its Relationship with ComboLite

Dimension ComboLite (Single-Process) ComboNative (Multi-Process)
Model Single-process pluginization, shared ClassLoader, cross-plugin linking by FQCN Multi-process 'native mini program' runtime, independent process + independent ClassLoader
Obfuscation & Hardening Cross-plugin linking by class name, difficult to independently obfuscate Cross-process only via contract, no class names transmitted, host and mini programs independently obfuscate and harden
Crashes Plugin crash affects host Sub-process crash isolation + crash circuit breaker
Memory Unloading relies on cleaning references, prone to leaks Close means kill process, overall reclamation by the OS
Open Source Open-sourced, on Maven Central Main library not yet open source (on Maven, Alpha experience open)
Maturity Relatively mature, can be used in production 0.1.0-alpha01, early access stage

Two points to clarify:

ComboLite will continue to be open source. Its clean class loading, AAR→APK packaging plugin, global class index, dependency graph, and chain restart implementations are all publicly readable, serving as a complete pluginization engineering reference. For developers who want to deeply understand the principles of dynamicization, after understanding the capability boundaries of the single-process model, they can fully implement a runtime similar to ComboNative along the 'multi-process + IPC contract' direction.

ComboNative's main library is not open source for now (considering its Alpha stage and future commercialization plans), but the framework's four libraries have been published to Maven, and a complete open-source sample repository is provided, allowing you to integrate and experience it first.


三、Design Goals and Core Trade-offs

Android dynamicization within the industry has long been a trade-off between two types of solutions:

ComboNative's design goal is to take the strengths of both: execute natively, deliver and govern like a mini program, and eliminate the above defects from the architectural level. Its core design and corresponding benefits are as follows:

Goal Implementation Method Benefit
Independent Obfuscation & Hardening Cross-process only transmits apiId + serialized bytes, never class names Host and mini programs can be obfuscated and hardened separately
Crash Isolation Each mini program runs in an independent sub-process Crashes only affect themselves, host is unaware, with circuit breaker as a fallback
Controllable Memory Closing a mini program terminates its process Memory is overall reclaimed by the OS, eliminating leak accumulation
No Dependency on System Private APIs Build-time static generation of stub components and stub processes 0 Hook AMS/PMS, all public APIs, version compatibility controllable

The security model needs to be honestly stated: ComboNative uses controlled access + privilege interception + process isolation, not a strong sandbox. Mini programs share the same UID as the host, and native code can access underlying resources reachable by that process. Therefore, it is aimed at first-party / partners, and semi-trusted ecosystems governed by signature whitelist + review access, not open markets for anonymous arbitrary third parties. Section 7 will specifically expand on the boundaries.

Three-Way Comparison: JS Mini Programs / Traditional Pluginization / ComboNative

Putting the three paths side by side, the trade-offs are clear at a glance:

Dimension JS Mini Programs / Cross-Platform Containers Traditional Native Pluginization ComboNative
Execution Method JS + Rendering Bridge Native Native Kotlin/Compose
Performance Ceiling Limited by bridge and serialization Native Native
Underlying Capabilities Constrained by container opening pace Complete Complete (including NDK)
Crash Isolation Container-level isolation ❌ Same-process contagion Process-level isolation + circuit breaker
Memory Governance Container reclamation ❌ Manual cleanup, prone to leaks Close means kill process
Independent Obfuscation & Hardening Not applicable (JS) ❌ Class-name linking, hard to obfuscate Only transmits bytes, can independently harden
Hooks System APIs No Most require Hook AMS/PMS 0 Hook, all public APIs
Security Strength Strong sandbox Weak Controlled access + interception + isolation (not a strong sandbox)
Applicability Open ecosystems, cross-platform General App dynamicization Controlled / semi-trusted terminals

One sentence to understand this table: JS mini programs win on 'security + cross-platform', traditional pluginization wins on 'native performance', and ComboNative aims to take 'isolation + memory governance + code protection' together on the basis of 'native performance'—the cost is that it only targets controlled ecosystems with manageable sources, and trades a certain amount of cross-process overhead and resource usage.


四、Core Mechanisms (Principle Level, Not Involving Internal Implementation)

4.1 Stub Process Pool: 0 Hook Multi-Process

Android's four major components cannot be dynamically created at runtime; the system only recognizes components statically declared in the pre-packaged manifest, and android:process=":mpN" is also determined by the manifest. To allow plugin components to be scheduled by the system, traditional pluginization commonly uses Hook AMS/PMS + stub component deception—it works, but relies on system private APIs and requires re-adaptation with every major version upgrade.

ComboNative's approach is no hooking, no deceiving the system, only using public APIs, shifting the dynamism from 'runtime deception' to 'build-time slot creation':

At build time, pre-declare N groups of 'stub' components, each bound to an independent process :mp1:mpN; at runtime, when a mini program needs to run, pick an idle slot from the process pool and launch it; when closing, terminate the process and return the slot.

The key here is: slots are homogeneous, empty, and generic—not bound to any specific mini program, like hotel rooms, anyone can check in, and they are vacated when they leave. Stub components, process declarations, and supporting manifests are all automatically generated by the build-time Gradle plugin, requiring no manual writing by the developer.

3_process_pool.png

The cost needs to be honestly stated: The maximum number of concurrently running mini programs = the number of pre-declared slots; static attributes like launchMode are unified for homogeneous slots. This is the inevitable trade-off of the 'no hooking, only public APIs' path—exchanging controllable resource overhead and flexibility constraints for long-term stability and version compatibility.

4.2 AIDL Contract: Only Transmit Bytes, Not Class Names

Multi-process solves isolation but brings communication problems. ComboNative's answer is a layer of AIDL + Parcelable contracts, with an iron rule running throughout:

Cross-process only transmits apiId, apiVersion, and serialized payload bytes; does not transmit Class, fully qualified class names, or any arbitrary serializable objects.

What flows between the host and the mini program is always 'data', not 'types'. Precisely because the two ends never reference each other by class name, the host can arbitrarily obfuscate its classes, and the mini program can arbitrarily obfuscate its classes, without affecting each other—this is the fundamental reason why ComboLite cannot, but ComboNative can, achieve 'independent obfuscation and hardening'. The narrower and more stable the contract surface, the freer the evolution of both ends.

Additionally, requests carry apiVersion, allowing capabilities to evolve backward-compatibly within compatibility intervals; the host creates a communication endpoint bound to the mini program's real identity for each one, and any caller-forged identity fields will be overwritten by the real identity.

4.3 Cross-Process Proxying for Four Major Components

Mini programs can normally use Activity, Service (including foreground services), BroadcastReceiver, and ContentProvider, all carried by stub components:

4.4 fd Bypass for Large Payloads

Binder's single transaction buffer is limited (about 1 MB and shared with concurrent transactions); inlining large data triggers TransactionTooLargeException. ComboNative handles this at the IPC boundary: when a payload exceeds 128 KiB, it automatically switches to a ParcelFileDescriptor pipe bypass (only transmitting an fd within the Binder), completely transparent to business code, requiring no manual chunking.

4.5 Access Control, Circuit Breaker, and Process Reclamation

Architecture Overview

2_architecture.png

五、Lightweight: Reproducible Size Data

Data caliber: 0.1.0-alpha01, release / R8 obfuscated. All numbers can be reproduced with one click in the open-source sample repository (see Section 11 for commands).

5.1 Framework Runtime: About 347 KB

Module AAR Size Role
combonative-abi 66.9 KB Contract layer (AIDL + serialization models)
combonative-engine 202.4 KB Host main process engine: scheduling, API bridge, installation access control
combonative-runtime 53.7 KB Sub-process runtime: dex loading, stub components, resource loading
combonative-mini-sdk 23.9 KB Mini program development surface (provided by host at runtime)
Total ≈ 347 KB

The packaging plugin combonative-packer (165 KB) is not counted—it only runs at build time and does not enter the runtime APK.

5.2 Single Mini Program Package: As Low as 32.8 KB

Mini Program Form .cbp (release) .cbp (debug)
miniapp-ledgerbook Pure logic + lightweight UI (ledger) 32.8 KB 44.8 KB
miniapp-minihub Pure Compose UI 97.7 KB 141.7 KB
miniapp-imagelab Includes NDK native image processing 429.0 KB 497.0 KB

A fully functional ledger mini program is only 32.8 KB. ⚠️ But 'tens of KB' should not be taken as a constant indicator for all mini programs—size depends on code volume, UI complexity, whether NDK is included, etc. The above are measured values for specific examples, specific versions, and a specific caliber.

1_size_compare.png

Why can it be so small? The core is 'not re-packaging what the host already has': mini programs use compileOnly for the framework SDK (provided by the host at runtime, not entering .cbp), and do not re-package common dependencies like Compose / AndroidX that the host already possesses. The .cbp only contains its own unique code and resources. It's like moving into a fully furnished apartment—you only need to bring your own luggage.

5.3 Host Whole Package: 7.74 MB, Framework Accounts for Less Than 5%

Build Type Host APK Size
release (R8) 7.74 MB
debug 11.02 MB

The vast majority of this 7.74 MB is conventional dependencies like Compose / Material3 / AndroidX (which any Compose application must carry). The four framework runtime AARs total about 347 KB, accounting for less than 5%. In other words, ComboNative adds almost no 'weight' to the host; the overall package size is mainly determined by your own business UI dependencies.


六、Ten-Minute Hands-On: From Host Integration to Running Your First Mini Program

The following code is all usage of the public SDK (not internal implementation of the main library) and can be operated against the open-source sample repository. Prerequisites: JDK 17, AGP 8.12+, Kotlin 2.2+, minSdk 24 / compileSdk 36.

6.1 Configure the Host (One-Time)

Host App module build.gradle.kts:

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("org.jetbrains.kotlin.plugin.compose")
    id("io.github.lnzz123.combonative-packer") version "0.1.0-alpha01"
}

dependencies {
    implementation("io.github.lnzz123:combonative-engine:0.1.0-alpha01")
}

comboNativeHost {
    miniApps {
        enabled.set(true)                                       // Automatically package mini programs into assets/miniapps/
        buildType.set(com.combonative.packer.PackageBuildType.DEBUG)
        dir.set("miniapps")
    }
    slots {
        enabled.set(true)
        count.set(3)          // Maximum number of concurrently running mini programs
        servicesPerSlot.set(2)
    }
}

Root project build.gradle.kts declares the mini programs to package and the signature:

plugins { id("io.github.lnzz123.combonative-packer") version "0.1.0-alpha01" }

combonativePacker {
    modules { module(":my-miniapp") }
    signing {
        keystorePath.set("/path/to/your.jks")
        keystorePassword.set("..."); keyAlias.set("..."); keyPassword.set("...")
    }
}

Initialize the engine with the generated slot list (Application.onCreate):

class HostApp : Application() {
    override fun onCreate() {
        super.onCreate()
        ComboNative.initialize(
            application = this,
            config = ComboNativeConfig(
                slots = com.combonative.generated.GeneratedProcessSlots.SLOTS,
                hostApiProviders = listOf(/* see 6.3 */),
                enforceSignature = false, // Can be turned off during debugging; must be true in production
            ),
        )
    }
}

Stub components are injected into the merged manifest by the packer, no manual writing needed; the host manifest only needs to declare android:name=".HostApp" and its own launcher.

6.2 Develop a Mini Program (Standard Android Library)

// :my-miniapp/build.gradle.kts
android {
    namespace = "com.example.mymini"    // ← This is the mini program's unique ID
    compileSdk = 36
    defaultConfig { minSdk = 24 }
    buildFeatures { compose = true }
}
dependencies {
    compileOnly("io.github.lnzz123:combonative-mini-sdk:0.1.0-alpha01") // Golden rule: compileOnly
    implementation("androidx.compose.material3:material3:<version>")
}

Entry class inherits MiniApp, the base class is UI-neutral:

class MyMiniApp : MiniApp() {
    override fun onCreateView(context: Context): View =
        ComposeView(context).apply {
            setContent { MiniTheme { Text("Hello from my first MiniApp!") } }
        }
}

Manifest metadata (reuses standard manifest fields: package name = unique ID, label = display name, versionCode/Name = version):

<manifest android:versionCode="1" android:versionName="1.0.0">
    <application android:label="My First Mini Program">
        <meta-data android:name="combonative.miniapp.entryClass"
            android:value="com.example.mymini.MyMiniApp" />
        <meta-data android:name="combonative.miniapp.hostApis"
            android:value="account.getToken;ui.toast" />  <!-- Requested host APIs, semicolon separated -->
    </application>
</manifest>

6.3 (Optional) Host Exposes Capabilities to Mini Programs

The capability catalog is entirely defined by the host; the framework does not have built-in ones:

// Host side
private class AccountTokenProvider : HostApiProvider {
    override val apiId = "account.getToken"
    override fun handle(request: HostApiRequest) =
        HostApiResponse(HostApiResponse.ResultCode.OK, "token-for-${request.callerMiniId}".toByteArray())
}
// Mini program side
val token = hostApi.invoke("account.getToken", apiVersion = 1).payload?.decodeToString()

Authorization is divided into three levels: NORMAL / DANGEROUS (requires user authorization) / HOST_SIGNATURE (only available to same-signature callers).

6.4 Build, Install, Run

./gradlew :<your host module>:assembleDebug   # Automatically injects .cbp into host assets
unzip -l <host APK path> | grep -i cbp    # Verify .cbp is in the APK

In the host, release → install → launch:

ComboNative.installFromPackage(file, forceOverwrite = true)  // Force overwrite during debugging
ComboNative.launch(activity, "com.example.mymini")          // miniId = package name

Production semantics should use ComboNative.showInstallConfirm(context, file) to bring up an installation confirmation page (displaying name/version/requested APIs/signature risks), and only write to disk after user confirmation.

After running: The system launches the :mp0 sub-process to render the UI; in the system's 'Recent Tasks', the host and the mini program each occupy a card; swiping away the mini program card, the task sentinel reliably reclaims that sub-process.

At this point, you have run through the complete loop—you will find that developing a mini program is almost no different from developing a regular Android Library module.


七、Real Security Boundaries and Applicable Scenarios

Rather than claiming security is omnipotent, it's better to clearly state 'what it can do and what it cannot do'.

7.1 Three-Layer Security Model

7.2 Boundaries That Must Be Honestly Stated

7.3 Suitable and Not Suitable

✅ Suitable for: Controlled terminals like in-vehicle systems / smart cockpits, smart TVs, industry PDAs, Kiosks, vending machines; custom ROMs, controlled sideloading platforms; first-party / partner ecosystems needing code asset protection and dynamic delivery of native features; semi-trusted mini program ecosystems governed by signature whitelist + review access.

❌ Not suitable for now: Open markets where anonymous arbitrary third parties upload native modules; scenarios requiring iOS / cross-platform; scenarios dynamically loading executable code from outside Google Play within Google Play.

A few examples of why scenarios fit: In-vehicle systems run for long periods without reboot, process isolation + close-to-reclaim cures leaks, a single module crash does not affect the main HMI; OTA only needs to deliver a few tens of KB .cbp, no full package upgrade needed; custom ROMs build a semi-trusted ecosystem with a signature whitelist, first-party modules are independently hardened to protect code assets.


八、Selection: ComboLite or ComboNative

Dimension ComboLite (Single-Process) ComboNative (Multi-Process)
Call Overhead Direct in-process call, extremely low Cross-process IPC, serialization/Binder overhead
Obfuscation & Hardening Difficult to independently obfuscate Independently obfuscate and harden
Crash Impact Affects host Isolation + circuit breaker
Memory Unloading prone to leaks Close means kill process, reclamation
Concurrency/Resources Not limited by slots, more memory-efficient Limited by slot count, higher resident memory
Maturity Relatively mature, open source Alpha, main library not yet open source

One-sentence judgment method: What do you fear most, 'performance overhead' or 'isolation and code protection'?

For existing ComboLite users: No need to rush to migrate; it will continue to be maintained as open source. But if you have already hit the walls of 'want to harden but it crashes upon obfuscation / memory continuously grows / one plugin crash brings down the entire App', that is precisely the problem ComboNative aims to solve from the architectural level—you can first use the open-source sample to evaluate whether the 'cross-process overhead' is acceptable, then decide to adopt.


九、Frequently Asked Questions (FAQ)

Q1: Multi-process sounds heavy. One process per mini program, can the memory handle it? A: Processes do have a resident memory cost, which is exactly why the design of 'concurrency limit = slot count' exists—you pre-declare an appropriate number of slots based on device resources, rather than opening processes infinitely. What you get in return is 'close means kill process, overall memory reclamation', which is actually more stable for long-running devices. Reduce the slot count when resources are constrained; compared to single-process, it trades controllable memory for isolation and no leaks, a clear trade-off.

Q2: Will the overhead of cross-process IPC slow down the mini program? A: UI rendering and business logic all execute natively within the mini program's own process, not crossing a bridge; only 'calling host capabilities' triggers a single IPC. That is, overhead only occurs at explicit capability call points, not every frame or every interaction. Large data also has the fd bypass as a fallback. For scenarios with extremely frequent interactions and high latency sensitivity, refer to the selection advice in Section 8.

Q3: Can mini programs use Compose? Can they use NDK / .so? A: All can. Mini programs are native Android Libraries; Compose, traditional Views, Kotlin/Java, NDK native code are all supported. The sample miniapp-imagelab includes NDK image processing.

Q4: Can .cbp be installed directly like an APK? A: No, and it shouldn't be. .cbp is a loading format specific to the ComboNative host; it can only be installed, loaded, and run by the host. The system will not treat it as an independent App. This is by design.

Q5: The main library is not open source. Will I be 'locked in' if I integrate it? A: The framework's four libraries are published on Maven Central and can be depended on normally; the sample repository is fully open source, and the integration methods, DSL, and APIs are all public. Additionally, the previous generation ComboLite is fully open source, with principles and implementations readable. You can clearly see 'how to integrate, how to use'; there is no black-box lock-in.

Q6: How is it different from Google's official Dynamic Feature Module? A: Official dynamic modules are aimed at Google Play distribution, delivered by Play at install time/on demand, still run in the same process, within the same App, do not provide process isolation or independent hardening, and depend on the Play channel. ComboNative is aimed at controlled terminals / self-distribution, provides process-level isolation and independent hardening, and is self-governed by the host at runtime, not dependent on Play.

Q7: It's Alpha now, can it go into production? A: It is recommended to first trial it on non-critical paths or internal projects, evaluate it, and welcome participation in the public test feedback. APIs may still adjust during the Alpha phase; the process towards 1.0 will aim to converge and provide migration notes.

Q8: Does it support iOS / cross-platform? A: No, ComboNative is Android-only. If you need cross-platform, consider JS / cross-platform container solutions.


十、Current Status

If you find issues, feel free to raise an Issue (with a minimal reproduction), or contact me through the channels at the end of the article. Your feedback will directly influence what it looks like heading towards 1.0.


十一、Early Access and Resources

The main library is not yet open source, but has been uploaded to Maven, allowing direct integration and experience. You are welcome to trial it in real projects, provide feedback on issues and needs, and help me polish it to production readiness.

One-click reproduction of the size data in the article:

git clone --recurse-submodules https://github.com/lnzz123/combonative-samples.git
cd combonative-samples
export ANDROID_HOME=/path/to/android-sdk

./gradlew :host-demo:assembleRelease         # Host whole package
./gradlew buildAllReleaseMiniApps            # Each mini program .cbp (release / R8)
ls -la build/outputs/mini-app-packages/release/*.cbp

If this 'native + multi-process + independently hardenable' approach fits your scenario, welcome to try it, raise Issues, and give a Star; if you are more interested in understanding the principles behind it, or even building your own, you are also welcome to start reading from the open-source ComboLite. Every piece of your feedback will directly influence what it looks like heading towards 1.0.

📌 All data in this article is based on 0.1.0-alpha01, release/R8 caliber measured values, and may change with version evolution; APIs may still adjust during the Alpha phase.

Comments

Top 1 from juejin.cn, machine-translated. The original thread is authoritative.

lotosbin同学

Thumbs up