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.
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.
Bilibili Tech
Provides introductions and tutorials on Bilibili-related technologies.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.