- 1. Demand Background
- 1 Shortcomings of the Android native solution
- 2 Customize the application scenarios of the routing framework
- 3 Ideas about the custom routing framework
- Overview of ii ARouter
- Introduction and use of the three ARouter
- Iv Source code analysis
- Note 1 arouter – an annotation
- 11 Route Comments about routes
- 12 Interceptor Interceptor annotation
- 13 Autowired automatically loads annotations
- 14 RouteMeta Meta information of the route
- 2 Arouter -compiler Annotation compiler
- 21 Route Annotation processing
- 211 Project name Group Indicates the route list of the Group name
- 212 Project Name List of the Root module name group
- 213 Project name Providers Module name Action route list of Ioc
- Autowired annotation processing
- 23 Interceptor Processing annotations
- 21 Route Annotation processing
- 3 arouter- API Route control
- 31 ARouter init Initialization process analysis
- ARouter runtime API call process analysis
- The implementation of IOC, also known as IProviderbyType NavigationClass, is used to obtain the routing target instance
- 322 A simple route navigation
- 323 it object and LogisticsCentercompletion
- 324 the facade analysis
- Note 1 arouter – an annotation
- Five other
- 1 The singleton feature of IOC provided by Arouter
- 2. Interpretation of source code in Compiler
Are you still confused about how to solve cross-module interface jumps and API calls after app componentization? Do you want to realize the operation background free configuration of a URL sent to the APP after being clicked, jump to where you want to jump to where? Do you want to solve the h5 web page free jump to the full application of arbitrary interface? …
Then you really need to read this article
First, demand background
What is a routing framework? Simple point is the mapping page jump relationship, of course, it also contains all the functions related to the jump.
1.1 Shortcomings of the Android native solution
The native routing schemes we use are generally implemented through explicit intent and implicit intent, both of which have defects in a certain sense:
- An explicit intent, such as intent intent = new intent (activity, xxactivity.class);
- The need to hold the corresponding class directly leads to strong dependencies and improved coupling
- Implicit inten, such as Intent it = new Intent(); It. SetAction (” com. Android. Activity. MY_ACTION “);
- Properties such as action are defined in Manifest, resulting in poor scalability
- The centralized management of rules makes collaboration very difficult
- The problem with native routing schemes is that the jump process is out of control, because once StartActivity() is used there is nothing left to do but the system to manage, which causes an operational exception to be thrown instead of being degraded in the event of a jump failure
1.2 Application scenarios of user-defined Routing frameworks
In the previous paper, we used “defects in a certain sense” to describe the deficiency of the native routing scheme. In fact, for most application scenarios, Andoird native routing scheme is enough to meet the requirements, and there is no need to introduce a custom routing framework
However, we also need to be aware of the significance of custom routing frameworks:
-
Dynamic jump: in some complex business scenarios (such as e-commerce), page jump requires strong flexibility, and many functions are dynamically configured by operation personnel. For example, we do not know the specific target page before sending an active page, and we expect to automatically select the page and jump according to the delivered data
-
Componentization: With the continuous growth of business volume, APP will continue to expand, and the size and workload of the development team will gradually increase. Facing the derived 64K problems, collaborative development problems, etc., APP will generally move towards componentization. Componentization is to divide the APP into multiple component modules according to certain functions and services, and develop different components independently. Componentization can not only improve the team’s work efficiency, but also improve the application performance. The premise of componentization is decoupling, so the first thing we need to do is decouple the dependencies between pages
-
Problems between Native and H5: Currently, few apps are pure Native, and few are pure H5. In general, the two are combined. In this case, a very convenient and unified jump scheme is needed, because StartActivity() cannot be used to jump to the Native page in H5, and the jump from Native to the H5 page can only be realized by configuring the browser
- other
If the development team is facing any of these problems, then it’s time to choose a practical custom routing framework
Of course, if the above problems are not of a certain scale, then it is ok to use alternative implementations, but we should also be aware of their shortcomings
- Scheme 1: Text record Class full path, use reflection mode instance Class to realize jump
- Run-time reflection can lead to performance degradation, especially if a jump is a high-incidence action
- Text records of the Class full path increase maintenance costs
- Solution 2: Configure the Action for all activities and record the text to realize the jump
- Textual records of actions can increase maintenance costs
- The carried parameters cannot be dynamically customized
1.3 Assumption of custom routing framework
“Figure 1”
We expect the custom routing framework to address the shortcomings of the Android native solution, such as:
- URL index can solve the problem of class dependence.
- Distributed management page configuration can solve the problem of centralized management of Path in implicit intents.
- Implementing the entire routing process yourself can also have good scalability
- AOP is adopted to solve the problem that the jump process cannot be controlled
- Can provide a very flexible way to degrade
In general, a routing framework should meet at least the following three functions:
- Distribution: To allocate a URL or request to a service or page for processing according to certain rules. This process is distribution. Distribution is the most basic function of the routing framework, which can also be understood as a simple jump.
- Management: components and pages are managed according to certain rules, which provide search, load, modify and other operations during distribution. This part is management, which is also the basis of the routing framework. The upper functions are built on management.
- Control: just like a router, there will be speed limiting, shielding and other control operations in the process of routing. The routing framework also needs to make some customized extensions to routing operations in the process of routing. For example, THE AOP mentioned just now, the function update in the later period is also around this part to do.
Ii. Overview of ARouter
ARouter not only implements routing to the underlying components, but it also has Ioc functionality to route actions, namely cross-module API calls!
ARouter can do all the things that a Class can do with a URL.
When we choose an open source framework, we need to consider the quality of the development team, post-maintenance, functional requirements and many other aspects, and after comprehensive comparison, ARouter stands out
As a product of Ali development team, ARouter appeared at the end of 2016, and was publicly shared by Ali Cloud senior development engineer Liu Zhilong at the Technology summit in March 2017. At present, it has 2k+ star on GitHub, and the quality of ARouter’s development team and late maintenance are relatively stable
ARouter’s advantages:
- By using annotations, automatic registration of mapping relationship and distributed routing management are realized
- Annotations are processed during compilation and mapping files are generated without reflection and without impact to runtime performance
- Mapping relationships are classified by group, managed at multiple levels, and initialized as required
- Flexible demotion strategy that calls back the jump result on every jump, avoiding StartActivity() that would throw an operational exception if it failed
- Custom interceptor, custom interception order, you can intercept routes, such as login judgment and buried point processing
- Dependency injection support can be used as a standalone dependency injection framework to route actions across module API calls ==
- Supports direct parsing of the standard URL to jump, and automatically inject parameters into the target page
- Obtaining Fragments
- Support multi-module use, support componentized development
- Supports a variety of ways to configure transition animation
- Support for MultiDex(Google solution)
ARouter’s disadvantages:
- Plugins not supported [included in development plan]
- No support for generating readable mapping documents [included in development plan]
- Multipathing is not supported
- Manual route allocation is not supported
- User-defined route matching rules are not supported
Introduction and use of ARouter
The official documentation is clear and easy to understand, see github.com/alibaba/ARo…
Here are a few examples:
- Interface jump class
ARouter.getInstance(a).build("/test/activity2").navigation(a);
ARouter.getInstance(a).build("/test/activity2").navigation(this, requestCode);
Path * -build (URi) resolves the URi to path. And put the current URI into the PostCard * - build(String) The constructed PostCard does not store URI*/
Uri testUriMix = Uri.parse("xx://xxx/test/activity2");
ARouter.getInstance(a).build(testUriMix)
.withString("name"."Wang")
.withInt("age".18)
.withBoolean("boy", true)
.withLong("high".180)
.withString("url"."https://a.b.c")
.withParcelable("pac", testParcelable)
.withObject("obj", testObj)
.navigation(a);
ARouter.getInstance(a).build("/test/activity2").navigation(Context mContext, int requestCode, NavigationCallback callback);Copy the code
- Ioc dependency injection classes
// [ByName] Inject HelloService ((HelloService) ARouter) with the name of the annotation.getInstance(a).build("/service/hello").navigation()).sayHello("mike");If there are multiple implementations of the same interface, the service ARouter must be found by byName.getInstance(a).navigation(HelloService.class).sayHello("mike");// The target class is injected directly into ARouter.getInstance(a).navigation(SingleService.class).sayHello("Mike");Copy the code
- Degradation strategy class
[1] Provides a general degradability for all devices to achieve a general degradability.
// [2] NavigationCallbackCopy the code
- other
ARouter.openLog(a);
ARouter.openDebug(a); // Impact InstantRun + Prints routing tables
ARouter.init(getApplication());
ARouter.getInstance(a).destroy(a);Copy the code
4. Source code analysis
It is strongly requested that you read the official documentation carefully before reading this article: Open Source Best Practices: ARouter, the Page routing framework for the Android platform, and understand the following concepts:
- The compiler processes annotations to generate mapping files, which are loaded at runtime to implement routing
- Bootstrapping, Extensibility and Simple & Enough
- Compile time: automatic page registration – annotations & annotation handlers
- Runtime: Dynamic load – group management, load on demand
- Global interceptor
- Dependency injection
- Dynamically modifying routes during runtime
- Degradation problems
Route = path URL: destination Class
- Arouter – Annotations: All annotations used by the Arouter routing framework, and their associated classes
- Arouter-compiler: The annotation compiler introduces “arouter-annotation”, which generates a mapping file for the annotated target class in the compiler
- Arouter – API: implements route control
In general, it is arouter-annotation that implements the definition of routing table structure. Arouter-compiler completes the creation of logic for constructing routing table at compile time. Arouter-api loads logic to build routing table at run time and realizes routing control
4.1 annotations arouter – an annotation
【 figure 4.4.1 】
ARouter’s idea of “distributed control” is achieved through annotations for different target classes. Let’s take a look at the annotations ARouter has defined:
4.1.1@route Route Annotations
@route is ARouter’s most important annotation and the basic node of the Route. This annotation is used to describe the path URL information in the Route. Classes annotated with this annotation are automatically added to the routing table.
It’s worth noting that ARouter doesn’t just provide routing for page activities; it can also be used to route interfaces that modules want to expose to other module calls.
That is, @Route can be used not only in the Activity class, but also in the implementation class of the module’s external interface to implement aiDL-like functionality, namely IOC
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
// Path URL string
String path();
// Group name, default level 1 pathname; Once set, the jump must be assigned
String group(a)default "";
// The name of the path used to generate JavaDoc
String name() default "undefined";
// Additional configuration switch information; For example, whether some pages need network verification, login verification, etc
int extras() default Integer.MIN_VALUE;
// The priority of the path
int priority() default -1;
}Copy the code
4.1.2 @interceptor Interceptor annotations
Interceptor is an Interceptor annotation. Interceptors are application-wide, not module specific, and work when integrated into APK
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Interceptor {
// The priority of the interceptor
int priority();
// The name of the interceptor used to generate JavaDoc
String name() default "Default";
}Copy the code
4.1.3@autowired Automatically loads annotations
@autoWired is used to pass parameters when a page jumps. Variables that use this annotation flag in the target Class will automatically assign the value of the passed parameter after the Inject(‘) is called when the page is opened by routing
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface Autowired {
// Mark param's name or service name.
String name() default "";
// If required, app will be crash when value is null.
// Primitive type wont be check!
boolean required() default false;
// Description of the field
String desc() default "No desc.";
}Copy the code
4.1.4 RouteMeta Routing meta information
If all routing information is considered to be a table, then a RouteMeta is a row in the table, representing a meta-piece of routing table information.
public class RouteMeta {
private RouteType type; // Route type:
private Element rawType; // Raw type of route
privateClass<? > destination;/ / the target Class
private String path; / / URL path
private String group; / / group
private int priority = -1; // Priority of the route
private int extra; // Additional configuration switch information for the target Class; For example, whether some pages need network verification, login verification, etc
private Map<String, Integer> paramsType; // The parameter name of the target Class to be injected: parameter type TypeKind
}
public enum RouteType {// Route type
ACTIVITY(0."android.app.Activity"),
SERVICE(1."android.app.Service"),
PROVIDER(2."com.alibaba.android.arouter.facade.template.IProvider"),
CONTENT_PROVIDER(-1."android.app.ContentProvider"),
BOARDCAST(-1.""),
METHOD(-1.""),
FRAGMENT(-1."android.app.Fragment"),
UNKNOWN(-1."Unknown route type");
int id;
String className;
}
public enum TypeKind { // The parameter type of the target Class to inject
// Basic type
BOOLEAN,
BYTE,
SHORT,
INT,
LONG,
CHAR,
FLOAT,
DOUBLE,
// Encapsulate type
STRING,
PARCELABLE,
OBJECT; // Use Json parsing
}
Copy the code
4.2 Arouter -compiler Annotation compiler
This module implements some annotation processors, aiming at generating mapping files and auxiliary files (creating routing table logic).
Open source best Practice: “Page Registration: Annotations & Annotation Processor” in ARouter, a page-routing framework for Android platform. First, the annotated class file is scanned through the annotation processor. 2. Then classified according to different types of source files, this is because ARouter is a framework, it can provide the function of very much, so not only provides the jump function, it can also realize decoupling between modules, in addition to ARouter can also provide a lot of functions, as just mentioned, the interceptor can realize automatic registration of All components in ARouter are automatically registered. After sorting by different kinds of source files, the mapping files can be generated in a fixed naming format (project name +$$+Group+$$+ Group name), which means that the compile time part is over
In general, the class files generated by arouter-compiler are as follows:
-
The XXX$$project name $$Autowired. In the project directory is the auto-loading auxiliary class, and the core is inject function, which realizes the assignment of value to the member variables of the target object
-
Routes directory
-
$$Group$$Group name (Map< String, RouteMeta> atlas) (Map< String, RouteMeta> atlas contains the mapping between the route URL of the corresponding Group and the target object Class; Note If there is no group in the Router annotation, the first xx in /xx/xx is the group name by default
-
Project name $$Root$$$module name Map< String, Class
> Routes extends IRouteGroup>> Routes contains a mapping between the group name and the routing list Class in the corresponding group. The mapping is an implementation of Arouter’s group Management, load on Demand. The ARouter will load all the root nodes at once and will not load any of the Group nodes. This greatly reduces the number of nodes to be loaded at initialization. When a page is accessed for the first time in a group, all the pages in the entire group are loaded -
$$Providers$$module name Map< String, The RouteMeta> providers routing node list contains the route URL mapping for a class that uses dependency injection (a direct subclass of the IProvide interface) The target Class implements the IProvider interface to implement partial routing to this list. Note: Ioc action Route list is actually a special use of Route annotation. In general, it is also a mapping relationship between URL and target class. In fact, dependency injection is nothing more than specifying the target class of the target interface, and then assigning values after instantiation. The URL is the specification
-
$$Interceptors = Map< Integer, Class
> interceptors contains a module of the interceptor with priority mapping all the interceptors under a module contains in the class, there is no grouping features, so named after the module name class files directly
-
- The generation of mapping files is not arbitrary, but follows certain rules. Temple is created by referring to the Temple interface of the API Module
- It can be regarded as a person who recorded the name of the interface, the name and parameters of the function in API Module-temple, and then wrote the interface that inherited these functions by borrowing certain tools
- Note: in the above description, we say “write the interface”, which is similar to writing some text in the TXT document. There is no reference, which means that we do not need to rely on the API Module, and also indicate that the Template in the API Module Template cannot be changed at will
4.2.1 Route Annotation processing
To distinguish the type of routing is through “for the Activity, IProvider, a subclass of Service” to judge, see “com.alibaba.android.arouter.com piler. Processor. RouteProcessor”
The routing paths defined in app are as follows:
@Route(path = "/test/activity1")
public class Test1Activity extends AppCompatActivity {
/ / ` ` `
}
@Route(path = "/test/activity2")
public class Test2Activity extends AppCompatActivity {
/ / ` ` `
}
@Route(path = "/test/activity3")
public class Test3Activity extends AppCompatActivity {
/ / ` ` `
}
@Route(path = "/test/activity4")
public class Test4Activity extends AppCompatActivity {
/ / ` ` `
}
/ * * * * * * * * * * * * * * * * * * * * the following for the application of the Ioc, implement the IProvider interface * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
@Route(path = "/service/hello")
public class HelloServiceImpl implements HelloService {
/ / ` ` `
}
@Route(path = "/service/json")
public class JsonServiceImpl implements SerializationService {
/ / ` ` `
}
@Route(path = "/service/single")
public class SingleService implements IProvider {
/ / ` ` `
}Copy the code
Let’s look at the mapping file generated by the app that processes the official Demo using annotationProcessor ‘com.Alibaba: arouter-Compiler :1.0.3’ :
(figure 2)
ARouter$$Group$$service and ARouter$$Group$$test are the list of in-group routes for service and test, respectively. ARouter$$Root$$app is a list of groups, we can also think of it as a list of classes, there are two classes, 1 and 2.
4.2.1.1 project name$$Group$$Group name [Routing list within a group]
public class ARouter\ \ $Group\ \ $service implements IRouteGroup {
public ARouter\$\$Group\$\$service() {}public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/service/hello", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello"."service", (Map)null, -1, -2147483648));
atlas.put("/service/json", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json"."service", (Map)null, -1, -2147483648));
atlas.put("/service/single", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/service/single"."service", (Map)null, -1, -2147483648)); }}public class ARouter\ \ $Group\ \ $test implements IRouteGroup {
public ARouter\$\$Group\$\$test() {}public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1"."test".new HashMap() {
{
this.put("pac", Integer.valueOf(9));
this.put("obj", Integer.valueOf(10));
this.put("name", Integer.valueOf(8));
this.put("boy", Integer.valueOf(0));
this.put("age", Integer.valueOf(3));
this.put("url", Integer.valueOf(8)); }} -1, -2147483648));
atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2"."test".new HashMap() {
{
this.put("key1", Integer.valueOf(8)); }} -1, -2147483648));
atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3"."test".new HashMap() {
{
this.put("name", Integer.valueOf(8));
this.put("boy", Integer.valueOf(0));
this.put("age", Integer.valueOf(3)); }} -1, -2147483648));
atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4"."test", (Map)null, -1, -2147483648));
atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment"."test", (Map)null, -1, -2147483648));
atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview"."test", (Map)null, -1, -2147483648)); }}Copy the code
4.2.1.2 project name$$Root$$$Module name [List of groups]
public class ARouter\ \ $Root\ \ $app implements IRouteRoot {
public ARouter\$\$Root\ $\$app() {}public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("service", service.class);
routes.put("test", test.class); }}Copy the code
4.2.1.3 project name$$Providers$$Module name [Ioc action Route List]
In particular, note that routing nodes of the PROVIDER type exist both in the corresponding grouping and in the manifest list of that type
It’s as if some of the students are Young Pioneers, every class may have young pioneers, and the school has a master list of young pioneers. That means there are two ways to find an identifiable young Pioneer.
So there are two ways ARouter can get the PROVIDER routing node
- ByName: Inject HelloService according to the name of the annotation via the intra-group routing list (HelloService) ARouter. GetInstance (). The build (/ service/hello). Navigation ()). The sayHello (” mike “);
- ByType: Available only if there is only one implementation of the HelloService interface via Ioc’s action Routing List = injection by classType Implementation when there are multiple implementations of the same interface, The service ARouter.getInstance().navigation(helloService.class).sayHello(” Mike “) must be found using byName;
Note that the name is (a direct subclass implementing the IProvide interface) – The name of HelloServiceImpl is its parent HelloService, HelloService implements the IProvide interface – JsonServiceImpl name is its parent SerializationService, SerializationService implements the IProvide interface – SingleService is its own SingleService and SingleService implements the IProvide interface
public class ARouter\ \ $Providers\ \ $app implements IProviderGroup {
public ARouter\$\$Providers\$\$app() {}public void loadInto(Map<String, RouteMeta> providers) {
providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello"."service", (Map)null, -1, -2147483648));
providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json"."service", (Map)null, -1, -2147483648));
providers.put("com.alibaba.android.arouter.demo.testservice.SingleService", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/service/single"."service", (Map)null, -1, -2147483648)); }}Copy the code
4.2.2 Autowired annotation processing
Figure 4.2.1 does not show the auto-loaded helper class, because the Autowired annotation is not used in test-module-1. Finally, we give the map file structure of APP
“Figure out”
@Route(path = "/test/activity1")
public class Test1Activity extends AppCompatActivity {
@Autowired
String name;
@Autowired
int age;
@Autowired(name = "boy")
boolean girl;
@Autowired
TestParcelable pac;
@Autowired
TestObj obj;
private long high;
@Autowired
String url;
@Autowired
HelloService helloService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test1);
ARouter.getInstance().inject(this);
// No more getter ```
// name = getIntent().getStringExtra("name");
// age = getIntent().getIntExtra("age", 0);
// girl = getIntent().getBooleanExtra("girl", false);
// high = getIntent().getLongExtra("high", 0);
// url = getIntent().getStringExtra("url");
String params = String.format(
"name=%s,\n age=%s,\n girl=%s,\n high=%s,\n url=%s,\n pac=%s,\n obj=%s",
name,
age,
girl,
high,
url,
pac,
obj
);
helloService.sayHello("Hello moto.");
((TextView)findViewById(R.id.test)).setText("I am "+ Test1Activity.class.getName()); ((TextView)findViewById(R.id.test2)).setText(params); }}public class Test1Activity\ \ $ARouter\ \ $Autowired implements ISyringe {
private SerializationService serializationService;
public Test1Activity\$\$ARouter\$\$Autowired() {}public void inject(Object target) {
this.serializationService = (SerializationService)ARouter.getInstance().navigation(SerializationService.class);
Test1Activity substitute = (Test1Activity)target;
substitute.name = substitute.getIntent().getStringExtra("name");
substitute.age = substitute.getIntent().getIntExtra("age".0);
substitute.girl = substitute.getIntent().getBooleanExtra("boy".false);
substitute.pac = (TestParcelable)substitute.getIntent().getParcelableExtra("pac");
if(null! =this.serializationService) {
substitute.obj = (TestObj)this.serializationService.json2Object(substitute.getIntent().getStringExtra("obj"), TestObj.class);
} else {
Log.e("ARouter::"."You want automatic inject the field \'obj\' in class \'Test1Activity\' , then you should implement \'SerializationService\' to support object auto inject!");
}
substitute.url = substitute.getIntent().getStringExtra("url"); substitute.helloService = (HelloService)ARouter.getInstance().navigation(HelloService.class); }}public class BlankFragment\ \ $ARouter\ \ $Autowired implements ISyringe {
private SerializationService serializationService;
public BlankFragment\$\$ARouter\$\$Autowired() {}public void inject(Object target) {
this.serializationService = (SerializationService)ARouter.getInstance().navigation(SerializationService.class);
BlankFragment substitute = (BlankFragment)target;
substitute.name = substitute.getArguments().getString("name");
if(null! =this.serializationService) {
substitute.obj = (TestObj)this.serializationService.json2Object(substitute.getArguments().getString("obj"), TestObj.class);
} else {
Log.e("ARouter::"."You want automatic inject the field \'obj\' in class \'BlankFragment\' , then you should implement \'SerializationService\' to support object auto inject!");
}
if(null == substitute.obj) {
Log.e("ARouter::"."The field \'obj\' is null, in class \'" + BlankFragment.class.getName() + "!"); }}}Copy the code
Test1Activity$$ARouter$$Autowired Inject (Object Target)
- SerializationService is injected in ByType mode of IOc of Arouter routing framework, which is the utility class for Json transformation
- Note that the assistant uses the ByType approach, which is to instantiate and inject the only class that implements the SerializationService interface. If there are more than one class that implements the SerializationService interface, this can cause problems
- Therefore, only one class that implements the SerializationService interface can exist in all modules of the global application
- Gets the target object instance
- Using the corresponding value transfer mode of the target object, the member variables in the instance of the target object are assigned
- Acitivty uses getIntent() – as determined by the framework’s own parameter passing, see section 4.3API
- Fragment uses getArguments() — depending on the framework’s own parameters, as detailed in section 4.3API
- OBJ objects are instantiated and transformed using JSon helper classes — see section 4.3API, which encapsulates the object as a JSon string when passing parameters
- IOc dependency injection object. ByType is used by default. If name is identified in the Autowired annotation, the class instance referred to by name is used and assigned
4.2.3 Interceptor Annotation Processing
@Interceptor(priority = 7)
public class Test1Interceptor implements IInterceptor {
/ / ` ` `
}
public class ARouter\ \ $Interceptors\ \ $app implements IInterceptorGroup {
public ARouter\$\$Interceptors\ $\$app() {}public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
interceptors.put(Integer.valueOf(7), Test1Interceptor.class); }}Copy the code
4.3 Arouter – API Routing Control
[FIG. 4.3.1 FIG. 4.3.2]
- The top layer is the Launcher layer, which developers can use directly. In fact, all the apis are in this layer.
- The next layer in the Launcher layer is the Frossard layer. As you can see from the figure above, the Frossard layer is also green, indicating that this layer can be called externally. The Frossard layer actually contains three parts: Service, Callback, and Template.
- The concept of Service is similar to the concept of Service on the client side, but it is different from the concept of Service on the Android component. The concept of Service is abstracted by ARouter. In a sense, it encapsulates certain functions and components into interfaces and provides external capabilities.
- Template is a Template. The Compiler module generates some mapping files during compilation, and the generation rules of these mapping files are generated according to Template. The code of loading logic is generated by recording the related interface function name + parameters of Template. This generates mapping files according to certain rules and constraints and makes it easier for Route to read them at runtime.
- The next layer is the full internal implementation of the SDK, which includes Ware House, Thread, Log, Exception, and Class tools.
- Ware House stores configuration files and mappings loaded by ARouter during runtime.
- Threads, on the other hand, provide a Thread pool, because multiple interceptors and jumps need to be executed asynchronously;
- The Class tool is designed to address compatibility issues between different types of APKs.
- The next layer is Logistics Center, which literally translates as Logistics Center. The flow and internal calls of the whole SDK will eventually sink to this layer, and of course it will be divided according to functional modules.
4.3.1 Analysis of ARouter Init Initialization Process
From the previous analysis, we can almost conclude that the most important step in ARouter’s initialization process must be to load the route list file generated by the previous compilation into memory, forming a routing table for later route look-up.
Class ARouter is a proxy Class. All of its functions are implemented by Class _ARouter. Both are singletons
Let’s look at arouter – the com API. Alibaba. Android. Arouter. The launcher. Arouter
/**
* Init, it must be call before used router.
*/
public static void init(Application application) {// Static functions are initialized, independent of objects
if(! hasInit) { logger = _ARouter.logger;// Holds a global static scalar for log printing
_ARouter.logger.info(Consts.TAG, "ARouter init start.");// Prints the ARouter initialization log
hasInit = _ARouter.init(application);// hand over _ARouter to initialize
if (hasInit) {
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");// Prints the ARouter initialization log}}Copy the code
Now that the code has been handed over to _ARouter to initialize, let’s look again
protected static synchronized boolean init(Application application) {
mContext = application;// Application context
LogisticsCenter.init(mContext, executor);// Pass to the logical center for initialization, and pass to the line city object
logger.info(Consts.TAG, "ARouter init success!");// Prints logs
hasInit = true;// indicates whether initialization is complete
// It's not a good idea.
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
// }
return true;
}Copy the code
Continue down to LogisticsCenter for initialization
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context; // Statically holds the Application context
executor = tpe; // Static hold line cityTry {// These classes were generate by arouter-compiler. // By specifying the package namecom.alibaba.android.arouter.routesList<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);For (String className: classFileNames) {//com.alibaba.android.arouter.routes.ARouter\$\$Root
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
// This one of root elements, load root.
((IRouteRoot) (Class.forName(className).getConstructor(a).newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS) {//com.alibaba.android.arouter.routes.ARouter\$\$Interceptors
// Load interceptorMeta
((IInterceptorGroup) (Class.forName(className).getConstructor(a).newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS) {//com.alibaba.android.arouter.routes.ARouter\$\$Providers
// Load providerIndex
((IProviderGroup) (Class.forName(className).getConstructor(a).newInstance())).loadInto(Warehouse.providersIndex);
}
}
if (Warehouse.groupsIndex.size() = =0) {
logger.error(TAG, "No mapping files were found, check your configuration please!");
}
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
}
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");}}Copy the code
First of all, we have to have a concept. As mentioned earlier, we concluded that the initialization operation is to load the mapping information into memory, so where to store it? The concept of a memory Warehouse is introduced here, which is actually a Map object holding multiple Statice objects, the source of which is given below.
We continue to analyze LogisticsCenter. The init (), the first is based on the package name com. Alibaba. Android. Arouter. Routes found underneath all the classes, the class? All modules under these classes:
【 figure 4.3.1.1 】
These classes are created “manually” at compile time, inheriting IRouteRoot, IInterceptorGroup, and IProviderGroup interfaces, respectively, and implementing their loadInto() methods
Next, after instantiating the class based on reflection, loadInto() is called to load the corresponding routing information into the memory repository
It is important to note that the initialization phase only loads the Root Group list, and does not load the list of routing nodes contained in each Group. This is consistent with ARouter’s official instructions: The mapping relationship is classified by Group, multi-level management, and initialized on demand. Only when a specific Group is used, the corresponding Group list will be loaded.
class Warehouse {
// Cache route and metas
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();// [group list] contains the mapping between the group name and the routing list Class in the corresponding group (here only stores the group that does not import routes in each keyboard)
static Map<String, RouteMeta> routes = new HashMap<>();// [intra-group routing list] contains the mapping between the route URL of the corresponding group and the target object Class;
// Cache provider
static Map<Class, IProvider> providers = new HashMap<>(); // Cache the IOC target class with the created object TODO? Do global applications share an IOc dependency injection object?
static Map<String, RouteMeta> providersIndex = new HashMap<>();// [Ioc action Route list] contains the mapping between the route URL and the class of a class that uses dependency injection
// Cache interceptor
// The list of interceptors in a module contains the mapping between interceptors under a module and their priorities
static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
static List<IInterceptor> interceptors = new ArrayList<>();// Sorted interceptor instance object
/ / ` ` `
}Copy the code
Finally, don’t forget the initial _arouter.afterinit (), which uses ioc.byname () to retrieve the interceptor interface. Note that the interceptor is not the interceptor we defined, but the interceptor logic implemented by Arouter that holds the interceptor we defined. It can be understood as the interceptor section Controller.
static void afterInit() {
// Trigger interceptor init, use byName.
interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}Copy the code
At this point, the initialization work is complete, where the memory Warehouse caches the global application [group list], [Ioc action route list], [module interceptor list], three Map objects ending with _index
4.3.2 ARouter runtime API call process analysis
Entry:
ARouter.getInstance(a).build("/test/activity2")
.navigation(a);Copy the code
Let’s look at the final call to Build. It uses the build() of the _ARouter proxy class to build and return the PostCard object. Let’s briefly mention that one PostCard object corresponds to a routing request
public Postcard ARouterbuild(String path) {
return _ARouter.getInstance().build(path);
}
protected Postcard _ARouter.build(String path) {
if (TextUtils.isEmpty(path)) {// If path is null
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);// Find the dynamically modified routing class using ARouter's Ioc method (IProvider ByType())
if (null! = pService) { path = pService.forString(path);// If the global application implements the PathReplaceService. Class interface, run time dynamic route modification logic is executed. Generate the transformed route
}
returnbuild(path, extractGroup(path)); }}Copy the code
The entire build process is divided into two sequential parts:
- Use Ioc byType() to find the implementation class of the PathReplaceService. Class interface to implement the “runtime dynamic change path”.
- The route navigation is normal
4.3.2.1 Dynamically modifying the implementation of PathReplaceService during running (IOC is the implementation of IProvider. ByType () = Navigation (class), used to obtain the target instance of the route)
ARouter’s T navigation(Class) is called in _ARouter’s build()
protected <T> T _ARouter.navigation(Class<? extends T> service) {
try {
Postcard postcard = LogisticsCenter.buildProvider(service.getName());
// Compatible 1.0.5 compiler sdk.
if (null == postcard) { // No service, or this service in old version.
postcard = LogisticsCenter.buildProvider(service.getSimpleName());
}
LogisticsCenter.completion(postcard);
return (T) postcard.getProvider();
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
return null; }}//1. Find the routing meta information corresponding to Name from [Ioc action Route List] in the memory repository
//2. Generate the Postcard object according to the route meta information and assign the path URL and group name information of the Postcard
public static Postcard LogisticsCenter.buildProvider(String serviceName) {
RouteMeta meta = Warehouse.providersIndex.get(serviceName);
if (null == meta) {
return null;
} else {
return newPostcard(meta.getPath(), meta.getGroup()); }}Copy the code
After find it, perform LogisticsCenter.com pletion (it) function, this function is a crucial function, its object is to perfect it, and then call its getProvider () to obtain the target object instance. The details are covered in 4.2.2.2
4.3.2.2 A simple route navigation
Entry: _arouter.build (path, extractGroup(path)), where _arouter.extractGroup (string) is found from the path, the default group, i.e. the first level path, e.g. “/11/22” — 11 can be seen, The function also performs the “4.3.2.1 Dynamic route modification at runtime” logic once, because the function can be called separately
// Build the PostCard object based on the pathname and group name
protected Postcard _ARouter.build(String path, String group) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null! = pService) { path = pService.forString(path); }return new Postcard(path, group); }}Copy the code
After getting the Postcard object, call its navigation() function. Let’s look at the Postcard class again
public Object navigation() {
return navigation(null);
}
public Object navigation(Context context) {
return navigation(context, null);
}
public Object navigation(Context context, NavigationCallback callback) {
return ARouter.getInstance().navigation(context, this, -1, callback);
}Copy the code
As a result, the navigation method in ARouter is called:
public Object ARouter.navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
}
// The final call function of a route jump, including the lookup callback call, interceptor processing, green channel verification, and specific route operations
protected Object _ARouter.navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
LogisticsCenter.completion(postcard);// [1] How about postcard? Currently, there are only Path and group
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
if (debuggable()) { // Show friendly tips for user.
Toast.makeText(mContext, "There's no route matched! \n" +
" Path = [" + postcard.getPath() + "]\n" +
" Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
}
if (null! = callback) { callback.onLost(postcard);// [2] The search fails
} else { // No callback for this invoke, then we use the global degrade service.
DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);// [2] Implement the global degradation strategy using ioc.bytype ()
if (null != degradeService) {
degradeService.onLost(context, postcard);
}
}
return null;
}
if (null! = callback) {[2] The route meta information is found, and the route lookup callback is triggered
callback.onFound(postcard);
}
if(! postcard.isGreenChannel()) {// [3] Green channel check
// It must be run in async thread, maybe interceptor cost too mush time made ANR.
interceptorService.doInterceptions(postcard, new InterceptorCallback() {// Call the interceptor section controller, traverse the custom interceptor in the memory repository, and execute the interceptor function in the asynchronous thread
/**
* Continue process
*
* @param postcard route meta
*/
@Override
public void onContinue(Postcard postcard) {
_navigation(context, postcard, requestCode, callback);[4] Perform specific routing operations according to the route type
}
/**
* Interrupt process, pipeline will be destory when this method called.
*
* @param exception Reson of interrupt.
*/
@Override
public void onInterrupt(Throwable exception) {
if (null! = callback) { callback.onInterrupt(postcard); } logger.info(Consts.TAG,"Navigation failed, termination by interceptor : "+ exception.getMessage()); }}); }else {
return _navigation(context, postcard, requestCode, callback);[4] Perform specific routing operations according to the route type
}
return null;
}Copy the code
Specific routing operations are assigned to _arouter._navigation (” ‘)
// Perform routing operations based on the route type
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = null == context ? mContext : context;
switch (postcard.getType()) {
case ACTIVITY: [1] If the Intent is Acitvity, the Intent is Intent
// Build intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// Set flags.
int flags = postcard.getFlags();
if (-1! = flags) { intent.setFlags(flags); }else if(! (currentContextinstanceof Activity)) { // Non activity, need less one flag.
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
// Navigation in main looper.
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
if (requestCode > 0) { // Need start for result
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
} else {
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
}
if ((0! = postcard.getEnterAnim() ||0! = postcard.getExitAnim()) && currentContextinstanceof Activity) { // Old version.
((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
}
if (null! = callback) {// Navigation over.callback.onArrival(postcard); }}});break;
case PROVIDER: // [2] If it is Ioc, return the target object instance
return postcard.getProvider();
case BOARDCAST: // [4] If it is board, return the instance
case CONTENT_PROVIDER: // [5] if Cp, return instance
case FRAGMENT:// [6] If it is Fragment, return the instance and populate the bundle
Class fragmentMeta = postcard.getDestination();
try {
Object instance = fragmentMeta.getConstructor().newInstance();
if (instance instanceof Fragment) {
((Fragment) instance).setArguments(postcard.getExtras());
} else if (instance instanceof android.support.v4.app.Fragment) {
((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
}
return instance;
} catch (Exception ex) {
logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
}
case METHOD://
case SERVICE://
default:
return null;
}
return null;
}Copy the code
Ok, now we have completed the basic jump
4.3.2.3 it object and LogisticsCenter.com pletion ()
A Postcard object corresponds to a routing request. The object is used in the whole routing process
public final class Postcard extends RouteMeta {
// Base
private Uri uri; //Nullable, which has a value only if the jump is based on the Uri
private Object tag; // There is an abnormal tag in the interceptor
private Bundle mBundle; // The Bundle stored in the Intent to convey information
private int flags = -1; // Flags in Intenet, such as Clear_TOP
private int timeout = 300; // timeUnunit. Second Time threshold for interceptor
private IProvider provider; // Nullable When using Ioc, the result is that this variable is assigned to the target object instance
private boolean greenChannel; // Whether to skip the interceptor
private SerializationService serializationService;// a tool for converting objects to JSON, withObject is assigned by ByType in the build argument
// Animation
private Bundle optionsCompat; // The transition animation of activity
private int enterAnim;
private int exitAnim;
} Copy the code
Let’s look at completion(Postcard)
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
}
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());// The path meta information was obtained according to the path URL
if (null == routeMeta) { // Maybe its does't exist, or didn't load. If the path meta information is not obtained, it may be because the [in-group list] of the corresponding group is not loaded or indeed not
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route metaGet the group list creation logic for the corresponding group from the group listif (null= =groupMeta) {// If it is empty, an exception is thrown and no exception is found
throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
} else {
// Load route and cache it into memory, then delete from metas.
try {
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
}
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();// instantiate [group list creation logic]
iGroupInstance.loadInto(Warehouse.routes);// Add the group list to the memory store
Warehouse.groupsIndex.remove(postcard.getGroup());// Remove the current group from the group list
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath())); }}catch (Exception e) {
throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
}
completion(postcard); // Reload triggers the perfect logic again}}else {
postcard.setDestination(routeMeta.getDestination());//【3】 target class
postcard.setType(routeMeta.getType());// [4] Route type
postcard.setPriority(routeMeta.getPriority());// [5] Route priority
postcard.setExtra(routeMeta.getExtra());// [6] Additional configuration switch information
Uri rawUri = postcard.getUri();
if (null! = rawUri) {// Try to set params into bundle. [7] If there is a URI, the route meta information "parameter name of target Class parameter to inject: Parameter Type TypeKind paramsType" and "URI of? Parameter "is assigned
Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);/ / "URI? Parameters"
Map<String, Integer> paramsType = routeMeta.getParamsType();// "Parameter name of the target Class parameter to be injected: Parameter Type TypeKind"
if (MapUtils.isNotEmpty(paramsType)) {
// Set value by its type, just for params which annotation by @Param
for (Map.Entry<String, Integer> params : paramsType.entrySet()) {// Parameter name of the destination Class parameter to be injected: Parameter Type TypeKind ", which puts the corresponding carried data to the postcard's bundle
setValue(postcard,
params.getValue(),// Parameter type TypeKind
params.getKey(),// Parameter name
resultMap.get(params.getKey()));/ / parameter values
}
// Save params name which need autoinject. The bundle stores the character array wmHzgD4lOj5o4241: list of parameters to be injected
postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
}
// Save the raw URI to the string NTeRQWvye18AkPd6G: URI
postcard.withString(ARouter.RAW_URI, rawUri.toString());
}
switch (routeMeta.getType()) {
case PROVIDER: // If the route is provider, should find its instance [8]
// Its provider, so it must be implememt IProvider
Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination(a); / / targetclass
IProvider instance = Warehouse.providers.get(providerMeta); / / targetclassWhether the instance object already existsif (null= =instance) { // There's no instance of this provider. Does TODO Ioc cause global sharing of an object?
IProvider provider;
try {
provider = providerMeta.getConstructor().newInstance();
provider.init(mContext);
Warehouse.providers.put(providerMeta, provider);
instance = provider;
} catch (Exception e) {
throw new HandlerException("Init provider failed! " + e.getMessage());
}
}
postcard.setProvider(instance);// [8.1 Instantiate and hold the target class]
postcard.greenChannel(); Provider should skip all of interceptors
break;
case FRAGMENT:
postcard.greenChannel(); 【9】 If you need to modify the interface, you need to reset the interface
default:
break; }}}Copy the code
4.3.2.4 facade analysis
figure
-
CallBack
- InterceptorCallback Is the callbck of the interceptor
- NavigationCallback All callbacks to the NavCallback downgrade logic
-
Service
- AutowiredService implements “load and call the helper classes in order to realize automatic loading”, the IOC. The byName (/ arouter/service/autowired) method is called _ARouter
- ClassLoaderService unused
- The Survival of eservice implements global degradation. Ioc. byType is called by _ARouter
- InterceptorService implements the interceptor section logic, IOC byName (/ arouter/service/interceptor) method is called _ARouter
- PathReplaceService implements dynamic routing logic. Ioc. byType is called by _ARouter
- SerializationService This global JSON conversion tool class for Object. The IOC.byType method is called by PostCard
Five, the other
5.1 Singleton features of IOC provided by Arouter
Providers hold the mapping between classes and object instances, which causes global applications to share an instance object for Ioc dependency injection
【 figure 5.1.1 】
Based on this, we also consulted the designers at Arouter
【 figure 5.1.2 】
Iprovider refers to services that provide apis externally.
5.2 Interpretation of source code in Compiler
Read the above article, basically all understand