Mobile Development 25 min read

Modular Architecture and Componentization for Youzan Android Applications

Youzan refactored its Android Micro Mall and Retail apps from a monolithic codebase into a service‑oriented, componentized architecture where each business module inherits a BaseModule with its own lifecycle, common utilities are extracted into lightweight libraries, and a custom Gradle plugin enables independent building, publishing, and rapid iteration.

Youzan Coder
Youzan Coder
Youzan Coder
Modular Architecture and Componentization for Youzan Android Applications

1. Overview

The main work of Youzan's mobile side is on the two core business lines "Youzan Micro Mall" and "Youzan Retail". As the SaaS business grows, the client side continuously iterates to support more and more features.

With this rapid business growth, the overall mobile architecture is constantly being adjusted to ensure development efficiency and fast business iteration.

This article introduces the ideas and implementation of componentization for the Youzan Micro Mall Android project.

1.1 Current Situation

The client architecture has evolved from an "All‑IN‑ONE" mode (all code in a single app) to a multi‑module project structure:

1.2 Pain Points

Complex Common module with unclear dependencies, leading to redundant code and unmaintainable business logic.

Increasing number of business modules makes packaging time explode, slowing development rhythm.

Business modules are tightly coupled with the upper (App shell) and lower (Common) layers.

Business modules lack independent lifecycle, making isolation and control chaotic.

1.3 Problems to Solve

Lightweight Common module: extract common low‑level and UI components into separate dependencies.

Service‑oriented mobile business: decouple existing business, expose interfaces, and enable cross‑module calls.

Configurable single‑module or multi‑module packaging to avoid full‑app builds for every change.

Manage dependencies and publishing of business modules.

2. Architecture Adjustments

Although we have been working on modular development, the current modular framework is not thorough enough:

Modules are only a project‑structure concept; there is no logical module concept.

Modules have no lifecycle control.

Common services are centralized in the Common module.

Exposed services are opaque; they directly depend on internal code.

Modules cannot be packaged independently; any code change requires a full build.

To solve these issues, we need to adjust the existing architecture.

2.1 Module Abstraction

We abstract module functionality into base classes, forming a modular support component that provides:

Each business module implements its own module class inheriting from BaseModule and registers it in the App shell.

Modules have a lifecycle similar to Activities (register services, initialize data, etc.).

Modules can register services that are added to the App’s service center during registration.

2.2 Decentralizing the Common Business

Many apps eventually create a large "Common" module that becomes a catch‑all storage for utilities, UI components, shared business classes, and core libraries. This leads to:

Redundancy – duplicate utilities are added when needed.

High maintenance cost – all shared logic resides in Common, making impact analysis difficult.

2.2.1 What Exists in Common?

Utility classes

Shared UI components

Business classes used by multiple modules

Core component wrappers (image library, network library, WebView)

Base classes (BaseActivity, BaseFragment, etc.)

2.2.2 Solution

Extract shared business modules upward into individual service‑oriented modules.

Abstract basic components into a separate component.

Move foundational classes into a core library without business logic.

2.3 Business Module Service‑orientation

“Service‑oriented” is a term often used on the server side. In the client, it means dividing the app into multiple modules that interact by providing services.

Typical Android module dependencies are:

Module A directly depends on Module B and calls its code.

Common module holds shared code; both A and B depend on Common.

2.3.1 Implementation of Service Dependency

Backend services use Dubbo RPC; clients depend only on the API module (interfaces and data structures).

Mobile side can adopt a similar approach – e.g., a product module provides an API layer that exposes data structures and services.

2.3.2 API Layer Implementation

Service exposure methods:

Protocol style: e.g., "app://order/detail/get?id=100" with JSON data, similar to an HTTP request.

Interface style: like Dubbo, the order module provides a Maven dependency containing data and service interfaces.

2.3.3 Issues with Protocol Style

Changing the service provider requires manual updates across all callers, lacks version management, and needs manual data conversion.

2.3.4 Issues with Interface Style

Requires an additional Maven dependency (AAR) which adds publishing and dependency‑management overhead, but this can be handled by Gradle plugins.

We finally chose the interface approach because it offers better stability and version control; Gradle plugins can automate the publishing cost.

2.4 Basic Component Abstraction

2.4.1 Existing Basic Components

Many basic components (account library, network library, image loader, Web container, etc.) are currently packaged inside Common, causing:

Heavy Common module.

Strong coupling between business modules and basic components, making cross‑team component reuse difficult.

High upgrade cost; API changes require modifications in every consumer.

2.4.2 Implementation Idea

Extract frequently used basic components into an independent abstraction layer defining interfaces (image loading, Web container, JsBridge, account, etc.).

Provide concrete implementations in a separate dependency that the App registers; business modules depend only on the abstraction.

2.4.3 Dependency Structure

2.5 Single / Multi‑module Packaging

As business volume and complexity increase, and third‑party components are added, the codebase grows, leading to slow builds. Example:

After developing a feature in a product module, you must build the whole app to test it, which can take 5‑10 minutes per build. Repeating this ten times a day is painful.

Our solution supports building a single module or a selected set of modules by using a custom Gradle plugin that dynamically changes the Android Gradle plugin type during compilation.

Tests show a full clean build takes about 4 minutes, while a single‑module clean build takes about 1 minute – a reduction of over 70%.

2.6 Architecture Diagram

3. Implementation Plan

Our solution provides three basic component dependencies and one Gradle plugin:

modular‑core: provides lifecycle and module service registration.

modular‑support: abstracts third‑party package interfaces.

modular‑support‑impl: default implementations for third‑party interfaces.

modular‑plugin: generates API directories, creates the runtime environment, and manages module publishing.

3.1 modular‑core

3.1.1 Module Class Implementation

Business module classes need to inherit from BaseModule:

public class ModuleA extends BaseModule {

    @Override
    public void onInstalled() {
        registerBusinessService(ModuleAService.class, new CachedServiceFetcher() {
            @Override
            public ModuleAService createService(@NotNull ModularManage manager) {
                if (service == null) {
                    service = new ModuleAServiceImpl();
                }
                return service;
            }
        });
    }
}

3.1.2 Module Lifecycle

Modules have the following lifecycle callbacks:

onInstalled() – called when the module is registered in the App.

onCreate() – called the first time the module’s Activity starts.

onStart() – called when the module starts.

onStop() – called when no Activity of the module remains in the back stack.

3.1.3 Lifecycle Implementation

Lifecycle capture and listening rely on Android Architecture Components’ Lifecycle library. Modules register Activities and globally listen to Lifecycle.Event to track module Activity states.

3.2 modular‑plugin

The plugin extends Gradle, providing highly extensible features that simplify component development without intrusive changes.

3.2.1 Gradle Lifecycle

Many extension functions are implemented by hooking into various stages of Gradle’s lifecycle. The overall lifecycle is illustrated below:

3.2.2 Single‑module Packaging

Packaging an Android module as an APK requires:

Proper AndroidManifest.xml configuration (application tag).

Main Activity configuration.

3.2.2.1 Implementation Principle

Automatically generate a dedicated Application class for the module.

Read and modify the module’s AndroidManifest to make it packable as an app.

Dynamically change the SourceSet during packaging to use the generated files.

Dynamically switch the plugin type between com.android.application and com.android.library.

3.2.2.2 Gradle Configuration Example

modular {
    // Module package name
    packageName = "com.youzan.ebizcore.plugin.demoa"

    app {
        // Single‑module packaging switch
        asApp = true
        // Name of the generated app
        appName = "Module A"
        // Entry Activity
        launchActivity = "com.youzan.ebizcore.plugin.demoa.ModuleAActivity"

        requires {
            // Dependencies needed only for single‑module packaging
            require "com.squareup.picasso:picasso:2.3.2"
        }
    }
}

3.2.2.3 Generating the Runtime Environment

Running the createApp task automatically generates the required directory structure, e.g.:

./module_a
    --src
        ----main
            ------app   # generated app directory
                --------java   # generated Application class
                --------res    # generated resources
                --------AndroidManifest.xml   # generated manifest

3.2.2.4 Executing the Packaging Task

Running the runAsApp task builds the module into an APK and installs it on the device. If the module has contextual dependencies (e.g., login), they can be added to the requires section.

3.2.3 Module API Management

The module’s API layer provides interfaces and data structures that can be referenced internally. When exposing the module externally, it is packaged as an AAR and uploaded to Maven. The plugin provides two tasks to streamline development and publishing.

3.2.3.1 Adding API Configuration in build.gradle

modular {
    packageName = "com.youzan.ebizcore.plugin.demoa"
    api {
        // Enable API support (affects task generation)
        hasApi = true
        // Name of the API service class
        apiService = "ModuleAService"
        requires {
            require "com.google.code.gson:gson:2.8.2"
        }
    }
}

3.2.3.2 Generating API AAR

Running the createApi task generates the API source set, e.g.:

./module_b
    --src
        ----main
            ------service   # generated service directory for API interfaces and data objects
                --------java   # generated Application class
                --------AndroidManifest.xml   # generated manifest for AAR

3.2.4 Module Publishing

Publishing uses the maven-publish plugin. Developers only need to configure the publishing parameters:

modular {
    publish {
        active = true
        repo = "../release"
        groupId = "com.youzan.ebizmobile.demo"
        artifactId = "modular-a"
        moduleVersion = "0.1.4"
        apiVersion = "0.1.5"
        userName = ""
        password = ""
    }
}

Running the uploadModule task first builds and uploads the API AAR, then removes API classes from the module’s source set and adds the published API as a Maven dependency before packaging the business module.

3.3 modular‑support

3.3.1 Basic Component Abstraction Example

For an image component, we can define an interface like:

interface IImageLoadSupport {
    fun <IMAGE : ImageView> loadImage(imageView: IMAGE?, imgUrl: String)
    fun <IMAGE : ImageView> loadImage(imageView: IMAGE?, @DrawableRes drawableId: Int)
    fun <IMAGE : ImageView> loadImage(imageView: IMAGE?, imgUrl: String, callback: ImageLoadCallback<IMAGE>)
    fun imagePicker(activity: Activity?, selectedImgUris: List<Uri>)
    fun onImagePickerResult(requestCode: Int, resultCode: Int, intent: Intent?): List<String>?
}

3.3.2 Implementation of Basic Components

Implementations are registered in the App. If a single‑module needs Support functionality, a default implementation can be provided and stored in a global map:

fun <SUPPORT : Any, SUPPORTIMPL : SUPPORT> registerProvider(
    supportCls: Class<SUPPORT>,
    provider: SupportProvider<SUPPORTIMPL>
) {
    synchronized(Lock) {
        supportsProviderMap[supportCls] = provider
        if (supportsMap.containsKey(supportCls)) {
            supportsMap.remove(supportCls)
        }
    }
}

4. Planning

Introduce dependency injection in Modular‑Support to hide instance acquisition.

Provide native capabilities for Weex, RN, H5, Flutter via Modular‑Support.

Further reduce packaging time and increase flexibility with Modular‑Plugin.

Continue optimizing management and dependency packaging functions.

5. Conclusion

Componentization is an evolving journey. Continuous architectural adjustments aim to improve development efficiency and project stability. The ideas and solutions presented here hope to inspire teams exploring modularization.

Further Reading

1. A Brief Talk on Android Dex Files

2. Youzan's Weex‑Based Mobile Development Framework

-The End-

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.

modularizationAndroidpluginGradle
Youzan Coder
Written by

Youzan Coder

Official Youzan tech channel, delivering technical insights and occasional daily updates from the Youzan tech team.

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.