Mobile Development 36 min read

Componentization and Modular Architecture Refactoring for a Mobile Video Editing App (B‑Cut)

The B‑Cut video‑editing app was transformed from a monolithic single‑project codebase into a component‑based modular architecture—introducing clear layers, Gradle‑driven unified configuration, independent library/application modules, strict dependency and resource conventions, and a fast‑compile system—thereby cutting build times, eliminating merge conflicts, and enabling reusable components across teams.

Bilibili Tech
Bilibili Tech
Bilibili Tech
Componentization and Modular Architecture Refactoring for a Mobile Video Editing App (B‑Cut)

Preface

The original B‑Cut codebase used a single‑project structure. While this worked for a small prototype, the product quickly grew into a professional video‑editing platform with many features, a large codebase, and an expanding development team. The monolithic architecture began to cause serious problems such as merge conflicts, uncontrolled code permissions, high coupling, long incremental compile times, and difficulty reusing common components across different product lines.

1. Terminology

Single‑Project Development Model : One Gradle project corresponds to one APK; all business modules are organized as packages within the same project.

Modularization : Following the seven SOLID principles to achieve high cohesion, low coupling, and high reuse. Modules are packaged into separate module directories, similar to building blocks.

Componentization : An evolution of modularization where a module can be built either as a library ( library mode) or as an independent application ( application mode). This enables independent debugging, faster compilation, and easier reuse across teams.

2. Pain Points of the Single‑Project Model

Low Compile Efficiency : Every code change triggers a full project compile, often taking 4‑5 minutes per incremental build. The incremental build scans the entire project’s dependency graph, leading to long compile times.

Low Team Collaboration Efficiency : All developers can modify any part of the codebase, causing frequent merge conflicts and extensive coordination effort. The team often merges code late in the day, risking missed release deadlines.

Component Reuse Barrier : Identical features (e.g., overseas editing app, fan‑edition modules) cannot be shared as platform‑level components, resulting in duplicated effort.

3. Componentization General Architecture

The following diagram (omitted) illustrates a typical component‑based architecture: a top‑level shell app, business modules, common service layer, framework layer, and libraries layer. Each layer can be independently compiled and packaged.

4. Componentization Development Guidelines

Rule 1 – Dynamic Configuration of Component Type

Use a Gradle file (e.g., app_config.gradle ) to define debugModuleName . When the variable is empty, the component builds in integration mode; otherwise it builds as an independent app. Example:

class InitApplyPluginTask extends InitBaseApplyPluginTask {
    @Override
    void input(Object ob) { super.input(ob) }
    @Override
    void doTask() {
        if (PluginInfoManager.getInstance().getProject().getName().contains(PluginInfoManager.getInstance().getProjectConfig().getDebugModuleName())) {
            initApplicationPlugin()
        } else {
            initLibraryPlugin()
        }
    }
    @Override
    Object output() { return super.output() }
    private void initApplicationPlugin() { PluginInfoManager.getInstance().getProject().apply plugin: "com.android.application" }
    private void initLibraryPlugin() { PluginInfoManager.getInstance().getProject().apply plugin: "com.android.library" }
}

Rule 2 – No Direct Dependency Between Components

Components should only depend on APIs exposed by other components. Page navigation and service calls are implemented via a router (e.g., bilibiliRouter ) that generates code at compile time.

Rule 3 – Resource Naming Convention

All resource files must be prefixed with the module name (e.g., bgm_activity_main.xml , bgm_anim_bgm_list_detail_sheet_hide.xml ) to avoid collisions.

Rule 4 – Service API Exposure

Define service interfaces in an export_* module, implement them in the concrete component, and let the router inject the implementation. Example interface:

interface IExportDemoService {
    val DemoServiceName: String?
    fun DemoToolsReset()
    fun DemoExtractNotifyDataChange()
}

Implementation:

@Services(IExportDemoService::class)
@Named("ProxyDemoService")
class ProxyDemoService : IExportDemoService {
    override val DemoServiceName: String get() = "DemoModule_ProxyDemoService"
    override fun DemoToolsReset() { DemoVideoExtractTools.getInst().reset() }
    override fun DemoExtractNotifyDataChange() { DemoVideoExtractTools.getInst().notifyDataChange() }
}

Invocation:

BLRouter.INSTANCE.get(IExportDemoService::class.java, "ProxyDemoService").DemoExtractNotifyDataChange()

Rule 5 – Third‑Party and Self‑Developed Base Components

All third‑party SDKs and internal base libraries are packaged as separate Maven modules and consumed only by the framework layer, preventing upward dependency cycles.

Rule 6 – Data Storage

Each component manages its own SharedPreferences or MMKV file, using a business‑specific prefix to avoid cross‑component data conflicts.

Rule 7 – Component Lifecycle Distribution

Define a lifecycle interface ( IApplicationLifecycle ) and a manager ( ApplicationLifecycleManager ) that registers and invokes lifecycle callbacks for each component. Example:

public interface IApplicationLifecycle {
    LifeCyclePriority getPriority();
    void onCreate(Context context);
    void onLowMemory();
    void onTrimMemory(int level);
}

Manager snippet:

public class ApplicationLifecycleManager {
    private static List
APPLIFELIST = new ArrayList<>();
    private static boolean INITIALIZE = false;
    public static void init(Context context) {
        if (INITIALIZE) return;
        INITIALIZE = true;
        loadModuleLife();
        Collections.sort(APPLIFELIST, new AppLikeComparator());
        for (IApplicationLifecycle appLife : APPLIFELIST) { appLife.onCreate(context); }
    }
    // loadModuleLife and registration methods omitted for brevity
}

Rule 8 – Documentation

Every component must maintain a README covering functionality, usage, version history, and cautions.

5. Gradle Unified Configuration (GradleEngine)

A custom Gradle plugin centralizes common configuration (SDK versions, plugins, BuildConfig fields, lint options, resource prefixes, etc.) and automatically injects them into each module. Example task that sets SDK versions:

class InitBaseSdkVersionTask extends AbstractTask
{
    @Override
    void input(Object ob) { super.input(ob) }
    @Override
    void doTask() {
        System.out.println("GradleEngine || InitBaseSdkVersionTask doTask start");
        PluginInfoManager.getInstance().getProject().android {
            compileSdkVersion BaseConstant.compileSdkVersion
            buildToolsVersion BaseConstant.buildToolsVersion
        }
        PluginInfoManager.getInstance().getProject().android.defaultConfig {
            minSdkVersion BaseConstant.minSdkVersion
            targetSdkVersion BaseConstant.targetSdkVersion
        }
    }
    @Override
    Object output() { return super.output() }
}

The plugin distinguishes between shell app, business modules, and base libraries, applying the appropriate pipelines (e.g., executeBusinessModulePipeline() , executeBaseLibLayerPipeline() ).

6. Fast‑Compile System (Quick‑Build)

By leveraging componentization, the build system can compile only the changed modules, reducing full‑build time from ~10 min to ~4 min and incremental build from ~3 min to ~1.2 min. Statistics are collected via a TaskExecutionListener and displayed in a UI plugin.

7. Results

After refactoring, the architecture consists of five clear layers (Shell, Business, Common Service, Framework, Libraries). The new structure eliminates most cross‑layer dependencies, improves compile speed, and enables platform‑level component reuse across different teams.

Conclusion

Componentization follows the same fundamental ideas as modular design: high cohesion, low coupling, and clear boundaries. The B‑Cut migration demonstrates that a gradual, iteration‑driven approach—combined with unified Gradle tooling and lifecycle management—can significantly improve development efficiency for large Android projects.

Mobile DevelopmentandroidBuild OptimizationGradlecomponentizationLifecycle Managementmodular architecture
Bilibili Tech
Written by

Bilibili Tech

Provides introductions and tutorials on Bilibili-related technologies.

0 followers
Reader feedback

How this landed with the community

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