After the previous three articles, I believe that you have a certain degree of understanding of componentization.
One component infrastructure has been highlighted throughout the process: routing! Without it, componentization can be said to be impossible. In this article, we will talk about those thoughts in the birth process of a componentized routing framework.
1. Why is routing needed
In fact, we have talked about this problem before, and those of you who have practiced or tried componentization must feel it personally. Make it clear that each business module will not be isolated from each other but must interact with each other.
- Module A needs to jump to Module B, but we usually use A strong reference to the Class explicitly call;
- Module A needs to call the method provided by Module B, for example, other modules call the user Module login method;
These two forms of call are easy to understand, and in normal development people do not hesitate to call. But we had a big problem with componentized development:
- Module B’s Activity Class is in its own Module B, which must not be referenced by Module A.
- Similarly, calling a Module method directly doesn’t work either;
Hence: there must be an interaction that supports componentization requirements, providing UI jumps and method invocation capabilities.
2. What does a routing library need to satisfy
First, the routing library is also a technical component, residing in the Lib layer as a base library in the overall componentized hierarchy of design. Then the routing library not only needs to meet its own capabilities, but also must meet a condition that the base library should have:
- Api friendly, simple access, low cost;
- Ability of UI jump and method call;
- Function stability;
- Customizable;
3, eliminated the way
Any system or framework that looks perfect in a high release is not, in fact, perfect from the beginning, and is improved step by step through practice and iteration to its current state of relative perfection. For example, we’ve thought about this before:
3.1. Based on implicit intent
As you all know, there are two ways to open an Activity in Android: to display intent and implicit intent. Since explicit intents cause strong references, we can use implicit intents to start the Activity without causing strong references between modules.
Evaluation: This method can indeed complete the UI jump function of routing, but it depends on the modification of the Manifest file. At the same time, parameters are inconvenient to transfer, so it is not recommended.
3.2 Event-based, using broadcast or EventBus
It’s also easy to think that since you can’t interact directly, you can implicitly send notifications where you need to interact, and then the recipient will do different things depending on the notification type.
/** * Public class InteractEvent {public inttype; public String param; Public ParamObject ParamObject; / / eg: * @subscribe (threadMode = threadmode.main) public void * @subscribe (threadMode = threadmode.main) public void onMessageEvent(InteractEvent event) {if(event.type == EventType.JUMP_LOGINACTIVITY){
Intent intent = new Intent(mContext,LoginActivity.class);
intent.putExtra("param",event.param);
intent.putExtra("paramObject",event.paramObject); mContext.startActivity(intent); }}Copy the code
Evaluation: This interactive mode is feasible, but it is obvious that it is complicated and requires high access and maintenance costs for scenarios with many interface jumps.
3.3. Call a fixed method
We add methods to the classes that need to interact, with a fixed signature, and label the interaction class. So that when we need this interaction class in another component, we call this fixed method with the tag. The idea is that the following provides pseudocode in one way, with different implementations.
public interface IDoAction{
void doAction(HashMap hashMap);
}
public class LoginActivity extends Activity implements IDoAction{
public void doAction(HashMap hashMap){// 1. Obtain parameters and TypehashMap.get(); // call dispatcher.get (url).doaction (hashMap);
Copy the code
Note the reason for using HashMap as a parameter: Each interaction class requires different parameters, and the method signature must be fixed to be invoked through the interface. Passing HashMap as a parameter can contain multiple different types of parameters.
Evaluation: least recommended, cumbersome to use, too intrusive, great cost of transformation.
A good routing practice
To sum up the above bad practices, they are highly intrusive and cost high access and maintenance. The reverse is that a good route needs to be low – intrusive, easy to access, automation and other features. In the third scheme mentioned above, we can absorb a label for each interaction class to record the mapping relationship, which is convenient to obtain in other modules.
Eg: easyRouter ://main/Detail ———— MainDetailActivityCopy the code
Thinking down the road, this map holds the label and the Activity, so all you need to know to start an Activity is the label. For example, tag A corresponds to ActivityA, so whenever we encounter tag A we know that it wants to open ActivityA. At the same time, if we can handle the parameter transfer required to start the Activity, it will be a big step away from automation.
The question is reduced to two:
- Mapping, we can use String strings as labels to ensure both universality and uniqueness. Use a Map to store the mapping between the string and the Activity. This ensures that other modules can retrieve the Activity from the string.
- Parameter passing and support for Activity features (leveraging animation, onActivityResult lifecycle);
The second problem is to parse as much of this string as possible into as much data as Android needs, such as parameter passing, animation, life cycle, etc. There are two ways to parse this:
- The format of this parameter is Json. After reaching the target interface, the target interface will parse it.
- Make certain rules through routing to solve the problem, to the target interface directly like normal Android development to obtain;
eg: easyrouter://routertest? name=liuzhao&company=mycompanyCopy the code
5. Implementation of method calls
Method invocation seems to be possible through the above methods: event-based, calling a fixed method, etc., but it must be extremely complicated to use, and the interaction between modules is not only difficult to modify, but also expensive to maintain.
Note that each Module must provide different methods: different method signatures are required. And from modification and maintenance cost considerations, it is best to call directly as in a Module, IDE can automatically prompt method parameters.
Then we want to condense the methods provided by Module into a class, exposing only this class, referred to as the Module interaction service class. This makes it as easy and convenient for other Modules to call the methods of ordinary classes directly.
That leaves us with one question: How do other modules get your interaction service class? It’s easy to imagine the mapping mentioned above, but is it really ok to use strings for keys in this scenario? If a string is used as a Key, the Value received by another Module can only be identified as a Class. The specific Class type is not clear, and calling a specific method, especially when prompted by the IDE, is impossible. The problem then becomes how do we know what methods are in the Class we’re getting?
After many times of thinking, we finally came up with a solution: Module needs to expose methods, we defined by an Interface, which is defined in the Lib layer, that is, each Module can access, and we also use this Interface to save the Key mapping relationship. So the mapping table holds:
moduleInteracts = new HashMap<>(); private static Map<Module > InterfaceImpl> moduleInteracts = new HashMap<>();Copy the code
Other modules can get the service class directly from the Interface defined in the Lib layer, then generically convert it to the Interface, and then call the corresponding method as if it were a normal method:
public static <T extends IBaseModuleService> T getModuleService(Class<T> tClass) {
if (moduleInteracts.containsKey(tClass)) {
return (T) moduleInteracts.get(tClass);
}
returnnull; } call: EasyRouter. GetModuleService (BaseModuleService. ModuleInteractService. Class). RunModuleInteract (MainActivity. This);Copy the code
Best practices for routing
6.1 compile-time annotations
In sections 4 and 5 we know that routing is relatively good practice, but can we also automate the process? In fact, you can use the compile-time annotation technology to automatically generate the mapping table, so that it is more simple and convenient when accessing. You only need to put a annotation on the corresponding Activity and configure the corresponding string, and the mapping table will be automatically generated.
@DisPatcher("easyrouter://routertest") public class MainActivity extends Activity { ...... @override public void initActivityMap(HashMap<String, Class> activityMap) {activitymap.put ("easyrouter://routertest", MainActivity.class);
}
Copy the code
6.2 interceptors
6.2.1 unified judgment
In the actual development, we often encounter some unified operations, such as some applications need users to log in first, so in the next operation after the user browsed the user to click all kinds of need to judge whether to log in, not log in jump to the login interface, after logging in release.
Normally, we need to make a judgment at every click, but it is obviously time-consuming and laborious. Since we have done routing, all interface jumps need to go through us, so we can naturally make a unified judgment, judge when routing is distributed, and intercept when interceptor conditions are met.
6.2.2 redirection
If we need to do A/BTest of App functionality, how do we do it? There are certainly many ways, but they are not necessarily universal. Note that we already have A route, it is more convenient to do A/BTest with the route:
- First, we add an interceptor to the route. Each jump is judged by the interceptor.
- Interface jump is realized through routing. In the process of routing resolution, we identify module A that needs to jump.
- The interceptor determines that if A/BTest hits module B, the route is redirected to module B.
Note: There are more benefits to redirection, such as replacing hot fixes in emergencies with H5 interfaces.
6.3 Process monitoring
Listen to the process of starting an Activity, for example
- Data preparation before opening;
- Open callback;
- The target Activity is not matched to degrade, etc.
This article mainly introduces an Android routing framework in the birth process of thinking, in the next article will specifically recommend a routing framework.
AD time
Toutiao Android client team recruitment is in hot progress, all levels and fresh interns are needed, business growth is fast, daily work is high, the challenge is big, the treatment is powerful, you don’t miss it!
Bachelor degree or above, infrequent job-hopping (such as two jumps in two years), welcome to add my wechat details: KOBE8242011