Related Articles:
Incredible! 74 Complete APP Source Codes!
Android Architecture Analysis: What Happens from APP Launch to Main Page Display?
Two Latest Android Open Source Libraries from Alibaba: A Boon for Android Developers
Source: http://blog.spinytech.com/2016/12/28/android_modularization/
Regarding the issue of modularization, I believe every developer has thought seriously about it. As the project develops, the business continues to grow, and more and more business modules are added, the coupling between various modules becomes increasingly severe. At the same time, some projects (like our company) also involve the separate packaging and promotion of sub-applications, as well as the release of shadow applications, etc., making it urgent to readjust the architecture. Today, let’s talk about modularization, and this article is also my understanding of project architecture over the past few years.
The Initial Ultra-Small Project
When we first started working on Android projects, most people did not consider the project architecture. Let’s take a look at a diagram.
A small project developed in 2012
This sub-package structure looks familiar, with various components all packed into one package, completely lacking a hierarchical structure, and business, interface, and logic are all coupled together. This was a small project I developed when I first started learning Android at the end of 2012. Six months later, a colleague joined, and we developed together, leading to daily disputes over who modified whose code to no avail.
Architecture Improvement: Small Projects
Later, as we developed the App, the number of people increased, so we couldn’t do it the old way; we had to refactor. So I extracted the common code to create an SDK base library, encapsulated individual functions into Library packages, and divided different businesses into different modules using a sub-package structure, allowing each team member to develop their own module. At first, it was still easy and pleasant, with parallel development and a harmonious scene, as shown in the following figure.
Architecture after refactoring
After making a few minor changes
As we can see, with the increase of business in several versions, the coupling between various business modules became increasingly severe, leading to code that was difficult to maintain and update, let alone write test code. Although we later introduced a unified broadcasting system, which improved the problem of mutual references between modules to some extent, the limitations and coupling were still high, making it impossible to cure this issue. By the end, this architecture had poor extensibility and maintainability, and it was also difficult to test, so it was ultimately abandoned by the historical process.
Medium and Small Projects: Routing Architecture
Implementation Principle
Let’s first look at the routing architecture diagram
Routing Architecture
As seen in the figure, we created a Router
in the basic Common library, with n modules Module
in between. These Module
are actually the modules in Android Studio, and all these Module
are Android Library Modules, with the top Module Main being a Runnable Android Application Module.
All these Module
reference the Common library, while the Main Module also references modules A, B, and N. After this processing, the mutual calls between all Module
disappear, reducing coupling, and all communications are handled by the Router for dispatching, while the registration work is initialized by the Main Module. This architectural idea is actually very similar to the Binder’s concept, adopting a C/S pattern, isolating modules, and passing data through a shared area. Modules only expose open Actions, so it also embodies the interface-oriented programming philosophy.
The red rectangles in the figure represent Actions, Action
is the specific execution class, and its internal invoke
method contains the specific execution logic. If concurrent operations are involved, locks can be added in the invoke
method, or the synchronized
keyword can be applied directly to the invoke
method.
The yellow rectangles represent Providers, Provider
, each Provider
contains one or more Action
, and its internal data structure uses a HashMap to store Actions. The time complexity for querying a HashMap is O(1), meeting our requirements for calling speed, and since we register uniformly, there are no concurrency issues during writing, and any concurrency issues during reading are handled by the Action’s invoke. In each Module
, there will be one or more Providers (if there are no Provider
, then this Module
cannot provide services to other Module
).
The blue rectangles in the diagram represent Routers, Router
, each Router
contains multiple Provider
, and its internal data structure also uses a HashMap to store Provider
, and the principle is the same as that of Provider
. The reason for using HashMap twice is twofold: first, it makes it less likely to cause Action
name collisions; second, during registration, only registering Provider
reduces registration code and improves readability. And since the query time complexity of HashMap is O(1), two lookups won’t waste too much time. When the corresponding Action
cannot be found, the Router will generate an ErrorAction
, informing the caller that there is no corresponding Action
, and the caller can decide how to handle it next.
Request Flow
The specific flow of calling through the Router is as follows:
Router Sequence Diagram
-
Any code creates a
RouterRequest
, containingProvider
andAction
information, and requests theRouter
. -
Router
receives the request, looks up the correspondingProvider
in its internal HashMap using theProvider
information fromRouterRequest
. -
Provider
receives the request and finds the correspondingAction
information in its internal HashMap. -
Action
calls the invoke method. -
Returns the
ActionResult
generated by the invoke method. -
Wraps the
Result
into aRouterResponse
and returns it to the caller.
Reduced Coupling
All mutual dependencies between Module
have vanished, allowing us to cancel any Module
references in the main app without affecting the overall App’s compilation and operation.
Canceling dependency on Module N
As shown in the figure, we canceled the dependency on Module N
, and the overall application can still run stably. If there is a call to Module N
, it will return a Not Found prompt. In actual development, specific handling can be done according to requirements.
Enhanced Testability
Since each Module
does not depend on other Module
, we can focus on developing our module during the development process and can create a test App for white-box testing.
Testing Module A
Enhanced Reusability
Regarding reusability, the industry I work in is investment promotion, which requires developing many shadow apps around the main business to expand coverage (similar to 58->58 rental, 58 recruitment, Meituan->Meituan takeaway, etc.). At this point, the reusability of this architecture becomes apparent; we can decompose the business and write a wrapper App to generate an independent shadow App. This shadow App can reference whichever Module
it needs, allowing for rapid development, and if there are changes in the Module
business later, there is no need to modify all the code, reducing code duplication. For example, we once separated the IM module and the investment consulting module, wrote some interfaces and styles, and generated the “Investment Broker” App.
Support for Parallel Development
The entire architecture is quite similar to the Git Branch concept, based on the main line, with branches developed separately, and finally merged back into the main line. Here, the thought process is similar to branches, but in actual development, each module can be a branch or a repository. Each module needs its own version control to facilitate issue management and tracing. The main project can reference each module directly, export AAR references, or upload to JCenter Maven, etc. However, the thought process remains unified: inherit common -> independent development -> merge back into the main line.
Multi-Process Considerations: Medium Projects
As the project expands, the App’s memory consumption during operation also increases, and sometimes online bugs can lead to overall crashes. To ensure a good user experience and reduce consumption of system resources, we began to consider re-architecting the program using multiple processes, loading on demand, and releasing promptly to achieve optimization.
Advantages of Multi-Process
The advantages and usage scenarios of multi-process have been introduced in the previous article “Android Multi-Process Usage Scenarios”, and the main advantages are as follows:
-
Improving the stability of each process, preventing a single process crash from affecting the entire program.
-
More controllable memory, allowing manual process release to achieve memory optimization.
-
Based on independent JVMs, each module can be fully decoupled.
-
Retaining only daemon processes will make the application last longer and less likely to be reclaimed.
Potential Issues
However, enabling multiple processes means the Router system becomes invalid. Router is a JVM-level singleton and does not support cross-process access. This means that all your Provider
and Action
in the background process are registered to the background Router. When you call from the foreground process, you cannot access the Action
of other processes.
Solution
Actually, the solution is not complicated. The original routing system can continue to be used; we can imagine the entire architecture as the Internet. Now multiple processes have multiple routers, and we just need to connect multiple routers together, so the entire routing system can still operate normally. Therefore, we refer to the original Router
as the local router LocalRouter
, and now we need to provide an IPS and DNS provider, so we create a process whose function is to register routes, connect routers, and forward messages, which we call the wide-area router WideRouter
.
Let’s take a look at the routing connection architecture diagram
Routing Connection Architecture
As shown in the figure, in the vertical direction, each column represents a process, separated by dashed lines, including Process WideRouter, Process Main, Process A, …, Process N. The light yellow represents WideRouter
, and the dark yellow represents the service guardian of WideRouter
. The light blue represents each process’s LocalRouter
, and the dark blue represents the service guardian of each LocalRouter
. WideRouter
binds to each process’s LocalRouter
guardian service through AIDL, and each LocalRouter
is also bound to the guardian service of WideRouter
through AIDL, achieving the goal of all routers being interconnected.
Event Dispatching
URL: xxxDomain/xxxProvider/xxxAction?data1=xxx&data2=xxx
This is very similar to an HTTP request. The benefit of doing this is that it allows subsequent WebView to conveniently call local Action
.
JSON:
|
JSON format is simple and clear and can serve as an interface return value sent from the server to the client.
Next, let’s discuss how an event is transmitted during a cross-process request:
Event Transmission Diagram
From the diagram, we can clearly see that we mainly divide the event dispatching and transmission into two main parts.
-
The first part is to determine whether the target
Action
is an asynchronous program across processes. -
The second part is to execute the target
Action
across processes.
As for local event dispatching, it still follows the previous Router pattern; from Step 17 to Step 21, they are all the same as the single-process synchronous Router dispatch mechanism without any changes.
Multi-Process Application Logic Dispatching
In a multi-process environment, each time a new process is started, a new Application is created, so we need to separate the application logic of each process and choose different application logic for processing based on the Process Name
.
The actual Application startup process is as follows:
Multi-Process Application Startup Process
Conclusion
This article’s project address: ModularizationArchitecture, welcome everyone to star, fork, and provide suggestions.
Or directly introduce it into the project:
|
Did you gain something from this article? Please share it with more people
Java and Android Architecture
Welcome to follow us and discuss technology together. Scan and press the QR code below to quickly follow us. Or search for the WeChat public account: JANiubility.
Public Account:JANiubility