Modular Architecture and ViewDelegate Design for QQ Music Playback Page on Android
To tame the growing complexity of QQ Music’s Android playback page, the team replaced the monolithic MVC/SubController design with a clean, layered architecture that uses lightweight, lifecycle‑aware ViewDelegates and LiveData, enabling on‑demand module loading, clear separation of concerns, reduced memory usage, and reusable UI components.
The playback page is the most exposed secondary page in QQ Music, serving as the main entry for song information display, playback control, and recommendation promotion. As QQ Music grew rapidly, the playback page evolved from a simple control screen to a complex page with many business modules and varied UI, requiring continuous evolution of its Android code.
Background
MVC
In the early stage, the playback page used a classic MVC architecture: View (XML layout and PlayerActivity ) handled UI, Controller (also PlayerActivity ) managed business logic and data binding, and Model was implemented by various internal Managers.
As features grew, PlayerActivity became bloated. To mitigate this, SubControllers were introduced to split responsibilities: the Activity kept common broadcast handling, while each SubController encapsulated a specific business UI, routing, and model updates.
Initially this reduced code size and kept dependencies simple (N:1 between UI and controller). However, with increasing business modules, SubControllers began to interfere with each other, leading to tangled dependencies, performance degradation, and maintenance difficulties.
Problems of the SubController Approach
Inter‑module data flow became chaotic, with multiple controllers calling each other and UI widgets being updated from many places, making the code hard to understand and bug‑prone.
All SubControllers were created and destroyed with the Activity lifecycle, causing unnecessary memory, I/O, and CPU consumption, especially for modules that are only needed under certain conditions (e.g., portrait mode, album view).
Each SubController required the Activity to know its exact API usage timing, increasing the learning curve and risk of misuse.
UI‑specific logic was tightly coupled to SubControllers, preventing reuse across different page variations.
Layout XML grew continuously because every possible view had to be pre‑inflated, leading to longer inflate times and higher memory usage.
Changes and Goals
To address the above issues, the team set three main goals:
Modularize different business functions within the same page to achieve decoupling and reuse.
Separate view, business, and data layers, providing clear lifecycle callbacks and on‑demand loading to improve performance.
Improve code readability and extensibility.
Code Design – ViewDelegate
A lightweight modular unit called ViewDelegate was proposed. It abstracts Android component lifecycles into four callbacks: onBind , onVisible , onInvisible , and onUnbind . ViewDelegate is not tied to any UI widget; it can manage zero, one, or many UI elements, or even operate without a UI (e.g., background tasks).
Three usage patterns are supported:
Inject existing UI widgets (e.g., an ImageView for play/pause) via the constructor.
Inject a parent container and let the ViewDelegate inflate its own layout on demand.
Inject only a Context for non‑UI modules such as dialogs or tips.
ViewDelegate can be nested arbitrarily, and parent ViewDelegates automatically manage the lifecycle of their children.
Reactive Programming with LiveData
To make modules self‑contained, ViewDelegates listen to external state changes via LiveData . This eliminates the need for explicit API calls between modules, achieving dependency inversion and reducing coupling.
The team evaluated three reactive options:
RxJava – powerful but complex and requires manual subscription management.
EventBus – simple but lacks data‑flow concepts and suffers from memory‑leak risks.
LiveData – lightweight, lifecycle‑aware, and easy to integrate; chosen as the data source for ViewDelegates.
Layered Architecture
The overall codebase follows Clean Architecture: Presentation, Domain (Business Logic), and Data Access layers. Presentation adopts MVVM, where:
View = XML + ViewDelegate.
ViewModel converts Domain models to UI‑ready LiveData states.
Domain layer uses Repository & UseCase patterns.
Implementation and Results
After refactoring, each business feature is a separate ViewDelegate. Only the required modules are instantiated based on the current PlayerStyle , dramatically reducing memory consumption and start‑up time. The modular approach has been adopted across many QQ Music pages (live streaming, group listening, etc.), resulting in nearly 200 ViewDelegates in production.
Key benefits include:
Fine‑grained on‑demand loading of UI modules, avoiding unnecessary resource usage.
Clear separation of concerns, making the codebase easier to maintain and extend.
Improved performance on low‑end devices by loading only essential ViewDelegates during startup.
Reusable UI components such as the favorite button, implemented once as FavorButtonViewDelegate and used in many page variations.
Reflection
The team emphasizes that no architecture is a silver bullet. Simple pages may work with basic MVC or MVP, but as complexity grows, SOLID‑guided modular designs like the presented ViewDelegate approach become essential.
Tencent Music Tech Team
Public account of Tencent Music's development team, focusing on technology sharing and communication.
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.