Android Architecture Refactoring: Decoupling, Layered Design, and Incremental Migration
The article describes a large‑scale Android architecture refactor that introduces interface‑driven programming, dependency injection, clear layer and role separation, and incremental migration across iterations to improve modularity, compile speed, maintainability, and horizontal scalability.
For a major architecture refactor we adopt a cautious, incremental approach, embedding the changes into each iteration to gradually improve code structure. The motivation is the growing size of the mobile development team and project complexity, which made the original architecture increasingly tangled and costly to maintain.
Goals
The refactor focuses on decoupling and sets the following objectives:
Clearly define the role of each module.
Establish a well‑defined architectural hierarchy.
Enhance horizontal scalability of the architecture.
Improve compilation efficiency, especially given heavy use of Kotlin and AOP.
Enable independent module development with interface‑based programming.
Increase overall maintainability.
Current Situation
Before the refactor the app consisted of two layers: an application layer and a library layer. Common libraries were maintained by a dedicated team, while each business line also maintained its own libraries. Applications directly depended on the required libraries.
Because there was no clear hierarchical separation, the number of libraries grew and their inter‑dependencies became increasingly complex, leading to strong coupling, version conflicts, unknown call risks, high upgrade costs, and difficulty adapting to changing implementation choices.
Refactoring Plan
The core idea is to achieve decoupling through interface‑driven programming and dependency injection, then define horizontal roles and vertical layers, and finally use AOP for cross‑cutting concerns such as testing and debugging tools.
Interface‑Driven Programming & Dependency Injection
We expose only the intended functionality via interfaces, preventing accidental usage of internal methods and reducing compatibility issues during library upgrades. Dependency injection further abstracts the concrete implementations, allowing callers to interact solely with interfaces.
Layer & Role Division
The architecture is divided into three layers:
Base Layer : Consists of an interface layer, a basic‑class layer, and an SDK extension layer. It provides abstracted Android components and SDK extensions.
Component Layer : Includes functional components (e.g., networking, image loading), business components (common business features and line‑specific sub‑businesses), and view components (shared UI elements).
Application Layer : Mainly integrates all sub‑business modules into a complete app (splash, home, etc.) and contains minimal business logic.
A dedicated DI control module handles injection of concrete implementations; it can later be extracted into an independent framework similar to Spring’s configuration files.
Vertical & Horizontal Interface Segmentation
Vertical segmentation separates common interfaces (e.g., generic network request APIs) from business‑specific interfaces, allowing each line to define its own extensions without affecting others.
Horizontal segmentation decides whether all interfaces reside in a single library or are split into separate interface and implementation libraries, balancing ease of use against dependency complexity.
Message Communication
An internal messaging protocol is introduced for lightweight, flexible communication between modules, allowing listeners to receive messages on any thread.
AOP
AOP was initially adopted to handle Android 6.0 permission issues without touching existing code, and is also used to inject debug‑only utilities such as testing tools and quick‑debug panels.
Implementation Process
The refactor is woven into each iteration, with the following major steps:
Start from the Base Layer
Extract Common Utils and SDK extension into independent libraries.
Separate Base View and Base Class into their own libraries.
Extract Functional Modules
Define interfaces for each functional module and provide implementations.
Inject these implementations into the main project.
Replace direct calls with interface calls and remove old dependencies.
Extract Shared View Modules
Identify truly shared views, move them to a common view library (or multiple libraries if needed).
Extract Shared Business Modules
Define interfaces for shared business modules and implement them.
Complete dependency injection and replace calls.
Eliminate previous direct dependencies.
Extract Line‑Specific Business Modules
Follow the same steps as for shared business modules.
Encapsulate DI Framework
Build a configurable DI framework that can later be packaged as an independent module.
Key Considerations
Be aware of hidden pitfalls such as type‑casting issues in libraries.
When moving Kotlin view files, ensure layout XML class names are updated, possibly via scripts.
Adjust ProGuard rules after directory changes.
Preserve Git history when extracting existing packages into separate libraries.
Introduce new sub‑modules as project‑level dependencies first, then migrate to Maven artifacts once stable.
Establish clear version‑release and upgrade processes for the growing number of sub‑modules.
Hujiang Technology
We focus on the real-world challenges developers face, delivering authentic, practical content and a direct platform for technical networking among developers.
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.