Android’s native routing scheme is to implement both explicit and implicit Activity jump schemes through intents. Explicit intents need to be directly applied to the target Activity, leading to direct coupling between different pages. Implicit intents are centrally configured in the Manifest as actions. Unmanageable problems. And in the componentalized development, each module cannot reference each other directly, and the problem of route jump management becomes a problem that must be solved.
ARouter is an open source Android routing framework of Ali, which is developed to solve the problem of route jump in componentized development.
A framework to help componentize Android Apps — support routing, communication, decoupling between modules
GitHub project address: ARouter
A brief introduction to the principle of routing framework
We take componentized development Activity jump as an example, a simple talk about the implementation principle of the routing framework. Regardless of how the upper framework encapsulates it, the low-level jump to an activity is always implemented through startActivity(), which requires obtaining an instance or path to the target activity. To achieve decoupling between modules without directly referencing the target Activity, the simplest way is to give the target Activity a simple alias and then maintain the relationship between the alias and the Activity through the mapping table Map
>. Then the implementation of the mapping table can only sink into the base module that all modules reference, such as base. The process is clear:
The Activity injects the mapping into the Map in advance. When AActivity initiates a request to jump to B, the base module will look up the corresponding Activity instance in the mapping table and jump. If the corresponding Activity instance cannot be found, you can send back the jump result to avoid exceptions.
The use of ARouter
Add the dependent
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
// Replace the latest version with the API
// To be used with compiler, use the latest version to ensure compatibility
compile 'com.alibaba:arouter-api:x.x.x'
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'. }// Old gradle plugin (< 2.2), you can use apt plugin. See 'other #4' at the end of this article.
// Kotlin configuration reference at end of article 'other #5'
Copy the code
The latest release (December 7, 2021) is version 1.5.2. The following is based on the release notes.
confusion
Add obfuscation rules (if using Proguard)
-keep public class com.alibaba.android.arouter.routes. * * {*; } -keeppublic class com.alibaba.android.arouter.facade. * * {*; } -keepclass * implements com.alibaba.android.arouter.facade.template.ISyringe{*; } # if byType is used to obtain Service, add the following rule to protect interface -keepinterface * implements com.alibaba.android.arouter.facade.template.IProviderIf single-class injection is used, no interface implementation is definedIProviderAdd the following rule to protect implementation # -keep class * implements com.alibaba.android.arouter.facade.template.IProvider
Copy the code
Initialize the
The official recommendation is to initialize it as early as possible and put it in the Application
if (isDebug()) {
// These two lines must be written before init, otherwise these configurations will not be valid during init
ARouter.openLog(); // Prints logs
ARouter.openDebug(); // Enable debug mode (if running in InstantRun mode, debug must be enabled! The online version needs to be closed, otherwise there is a security risk.
}
ARouter.init(mApplication); // As early as possible, it is recommended to initialize in Application
Copy the code
Note that you need to call the openDebug() method during development and debugging; otherwise, the Activity may not be found.
Add annotations
Add Router annotations to the target Activity/Fragment where path is the routing path
@Router(path = "/app/MainActivity")
public class MainActivity extends Activity{
protected void onCreate(a){.../ / injection
ARouter.getInstance().inject(this)}}Copy the code
The path must have a slash and at least two levels and needs to be injected in onCreate.
A routing
-
The Activity to jump
ARouter.getInstance().build("/app/MainActivity").navigation(); Copy the code
-
Fragment
aFragment = (AFragment) ARouter.getInstance().build("/fragment/AFragment").navigation(); Copy the code
The ginseng
-
Basic data types
Pass parameters:
ARouter.getInstance().build(path).withchar ()"CharKey".'a') .withShort("ShortKey".1) .withInt("IntKey".11) .withLong("LongKey".12l) .withFloat("FloatKey".1.1 f) .withDouble("DoubleKey".1.1 d) .withBoolean("BooleanKey".true) .withString("StringKey"."value") .navigation(); Copy the code
Parsing parameters with annotations:
Note that the private keyword cannot be used.
// The pseudo-code is as follows public class TestActivity extends Activity{ // Annotate with Autowired @Autowired(name = "IntKey") int i; @Autowired(name = "StringKey") String str; / /... Omit the other onCreate(){ ... Log.d("IntKey = " + i + " StringKey = "+ str); }}Copy the code
During parameter passing, you can omit nameARouter to automatically match parameters based on type. However, you are advised to specify name to avoid exceptions.
-
Serialized object
Delivery:
ARouter.getInstance().build("Path") .withSerializable("SerializableKey".new TestObj()) .withParcelable("ParcelableKey".new TestObj()) .navigation(); Copy the code
Pass-through docking requires the Serializable and Parcelable interfaces to be implemented, respectively, invoking methods that are much the same as the basic data types, which is easy to understand.
Resolution:
Automatic parameter resolution is also done through annotations.
// Support parsing custom objects, using JSON pass in URL @Autowired(name = "ParcelableKey") TestObj obj; Copy the code
It is also recommended to specify the name, although omitting the name is also supported.
-
Custom object
Custom objects are passed only if the object does not implement the Serializable or Parcelable interfaces.
Delivery:
ARouter.getInstance().build("Path") .withObject("ObjectKey".new TestObjectBean()) .navigation(); Copy the code
Resolution:
@Autowired(name = "ObjectKey") TestObjectBean object; Copy the code
In addition to the above two steps, passing a custom object must create a new class that implements SerializationService and annotates it with the @Route annotation.
ARouter is designed to allow users to choose their own Json parsing methods. (Optionally specify a path that does not duplicate.)
@Route(path = "/app/custom/json") public class JsonSerializationService implements SerializationService { Gson gson; @Override public <T> T json2Object(String input, Class<T> clazz) { return gson.fromJson(input,clazz); } @Override public String object2Json(Object instance) { return gson.toJson(instance); } @Override public <T> T parseObject(String input, Type clazz) { return gson.fromJson(input,clazz); } @Override public void init(Context context) { gson = newGson(); }}Copy the code
In addition to the init method, the other methods handle json and object conversions.
Because the entire custom object passing process goes through the following steps:
- withObhect(“”, obj)
- Call the 2JSON method of SerializationService to convert obj to a STRING JSON object.
- Pass the JSON object of string to the target page.
- The target page invocation calls the 2Object method of SerializationService to convert the JSON object into an OBJ object.
You can also see the conversion steps in the withObject source code:
/** * Set object value, the value will be convert to string by 'Fastjson' * * @param key a String, or null * @param value a Object, or null * @return current */ public Postcard withObject(@Nullable String key, @Nullable Object value) { serializationService = ARouter.getInstance().navigation(SerializationService.class); mBundle.putString(key, serializationService.object2Json(value)); return this; } Copy the code
Note that since the parameter resolution process is handled automatically by type matching, the List and Map objects passed with withObject() cannot be received with a List and Map implementation class that implements Serializable (ArrayList, HashMap, etc.). The object that receives withObject arguments cannot be an implementation class of Serializable or Parcelable. (Specifying an implementation class for Serializable or Parcelable affects the serialization type.)
Routing management
The jump process above inevitably need to specify a lot of routing path (such as Activity path, etc.), in order to facilitate the management and processing, usually define a constant to maintain and manage all routing path, and for each module can be normal reference needs to be constant class down to the base module (such as BaseModule), Although this breaks the independence of each module to some extent (you must rely on constant-class modules for routing hops) and increases the coupling between the business module and the base module, it is still necessary for code maintenance.
/** * Route management */
public class ARouterPath {
/** push module */
public static final String SERVICE_PUSH_RECEIVER_URL = "/push/PushRegisterActivity";
/** Display Module */
public static final String ACTIVITY_DISPLAY_DISPLAY_URL = "/display/DisplayActivity";
public static final String SERVICE_DISPLAY_UPLOAD_URL = "/display/PushReceiver";
}
Copy the code
The interceptor
As mentioned earlier in the introduction to OkHttp, the OkHttp interceptor design is a particularly classic and exciting part of the framework’s implementation. Also added in ARouter is the implementation of an interceptor that intercepts and handles routing processes.
@interceptor (priority = 8, name = "test Interceptor ")
public class TestInterceptor implements IInterceptor {
private static final String TAG = "Interceptor";
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
String threadName = Thread.currentThread().getName();
Log.i(TAG, "Interceptor starts execution, thread name:" + threadName +
"\n postcard = " + postcard.toString());
// Intercept the route operation
callback.onInterrupt(new RuntimeException("There is an exception. No route."));
// Continue routing operations
callback.onContinue(postcard);
}
@Override
public void init(Context context) {
Log.i(TAG, "TestInterceptor interceptor initialization"); }}Copy the code
Interceptors can be added to multiple interceptors. The @interceptor () annotation is used to register interceptors. Priority is used to define the priority of interceptors.
- Multiple interceptors cannot have the same priority.
- with
name
Property specifies a name for the interceptor (which can be omitted). init()
Method is called automatically when the interceptor is initialized.process()
When a routing operation is initiated, the route can pass as requiredonInterrupt()
Intercept the route, or pass itonContinue()
To continue the routing operation, note that either method must be invoked; otherwise, the route will be lost and the operation will not continue.
The interceptor is used to judge login events in classic applications. Some pages in the app can only be redirected after the user logs in. In this way, the interceptor can be used for login check to avoid repeated checks on the target page.
Jump result monitor processing:
ARouter.getInstance().build("Path")
.navigation(this.new NavigationCallback() {
@Override
public void onFound(Postcard postcard) {
// Route discovery
}
@Override
public void onLost(Postcard postcard) {
// Route lost
}
@Override
public void onArrival(Postcard postcard) {
/ / to
}
@Override
public void onInterrupt(Postcard postcard) {
/ / intercept}});Copy the code
The NavigationCallback is specified to listen for jumps. You can also simplify this by specifying the abstract class NavCallback if you only want to listen for incoming events.
Note that only activities trigger interceptors, fragments and iProviders do not support interception.
Looking at the source code, you can see that interception is handled in the navigation() method of _ARouter.
if(! postcard.isGreenChannel()) {// It must be run in async thread, maybe interceptor cost too mush time made ANR.
// Handle interceptors
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
/**
* Continue process
*
* @param postcard route meta
*/
@Override
public void onContinue(Postcard postcard) {
_navigation(postcard, requestCode, callback);
}
/ / to omit
}
Copy the code
Whether it is intercepted depends on the postcars.isgreenChannel () value, which is assigned in the Completion () method of LogisticsCenter:
switch (routeMeta.getType()) {
case PROVIDER: // if the route is provider, should find its instance
// Its provider, so it must implement IProvider
Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
IProvider instance = Warehouse.providers.get(providerMeta);
if (null == instance) { // There's no instance of this provider
IProvider provider;
try {
provider = providerMeta.getConstructor().newInstance();
provider.init(mContext);
Warehouse.providers.put(providerMeta, provider);
instance = provider;
} catch (Exception e) {
logger.error(TAG, "Init provider failed!", e);
throw new HandlerException("Init provider failed!");
}
}
postcard.setProvider(instance);
// The Provider does not need interception
postcard.greenChannel(); // Provider should skip all of interceptors
break;
case FRAGMENT:
//Fragment does not need to be intercepted
postcard.greenChannel(); // Fragment needn't interceptors
default:
break;
}
Copy the code
Routemeta.gettype () is clearly the route type, although ARouter defines a number of types:
/**
* Type of route enum.
*
* @author Alex <a href="mailto:[email protected]">Contact me.</a>
* @version 1.0
* @since16/8/23 22:33 * /
public enum RouteType {
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");
/ / to omit
}
Copy the code
RouteType is defined as Service, ContentProvider, and Boardcast, but ARouter only supports route jumps: RouteType is defined as Service, ContentProvider, and Boardcast. There are three types of Activity, Fragment, and IProvider. The other types are not supported (based on version 1.5.2). IProvider is ARouter’s service component and will be mentioned later.
grouping
Above mentioned can log in by interceptors, and intercepting page, but we need if we do in the heart of the interceptor judging all need to jump after the login page, this problem can be simplified and grouping function, will all need to jump after the login page to specify the same group, you just need to judge grouping in the interceptor.
// Specify a group using the group keyword
@Route(path = "/app/MainActivity", group = "app")
public class MainActivity extends AppCompatActivity {
/ / to omit
}
// Intercept judgment
@interceptor (priority = 8, name = "test Interceptor ")
public class TestInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
// Get the grouping
String group = postcard.getGroup();
/ / to omit
}
/ / to omit
}
Copy the code
Note that if you want to jump to the target page of the group specified by the group keyword, you need to display the specified group; otherwise, the corresponding route cannot be found.
ARouter.getInstance().build("/app/TextActivity"."test").navigation();
Copy the code
Of course, if the grouping is not specified by group, the first/after the path is used as the group by default, which is why the path must have at least two levels of //.
Declare more information for the target page
// We often need to configure some properties in the target page, such as "need to log in" or not"
// This can be extended by the Extras attribute in the Route annotation, which is an int value. In other words, a single int has 4 bytes, or 32 bits, and can be configured with 32 switches
// The rest can be left to your own devices. 32 switches can be identified by byte manipulation, and some attributes of the target page can be identified by the switch, which can be retrieved in the interceptor for business logic judgment
@Route(path = "/test/activity", extras = Consts.XXXX)
Copy the code
In simple terms, can be specified as the target page simple attributes, such as the official said the login management functions, but it is not all pages need to log in, of course, we can through the way of grouping above, will need to log in to determine the login page is divided into unified group, and then judge grouping in the interceptor, according to the current state of login to intercept.
You can also set the extrAS to 1 for pages that require login verification, and 2 or no for pages that do not require login verification.
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
String threadName = Thread.currentThread().getName();
Log.i(TAG, "Interceptor starts execution, thread name:" + threadName +
"\n postcard = " + postcard.toString());
// Simulate the login state
boolean isLogin = false;
int extras = postcard.getExtra();
// Login verification is required, and unlogged pages are blocked
if(extras == 1 && !isLogin){
callback.onInterrupt(new RuntimeException("Please log in first."));
} else{ callback.onContinue(postcard); }}Copy the code
An int extras has 4 bytes (32 bits) and can be configured with 32 switches.
startActivityForResult
ARouter also supports startActivityForResult() to get the return value of the target page.
// Specify a RequestCode,123, to redirect a route
ARouter.getInstance().build("/AModule/AModuleActivity").navigation(this.123);
Copy the code
The target page needs to return ResultCode and data using the setResult method upon exit
// Target page implementation, omit other code
@Override
public void onBackPressed(a) {
Intent data = new Intent();
data.putExtra("name"."I am AModuleActivity");
setResult(321, data);
super.onBackPressed();
}
Copy the code
The onActivityResult () method needs to be overridden on the jump page. The whole process is basically the same as startActivityForResult().
// jump page implementation, other code omitted
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 123 && resultCode == 321){
String name = data.hasExtra("name")? data.getStringExtra("name") : "";
Log.d(TAG, "onActivityResult: name = "+ name); }}Copy the code
Dynamically Adding routes
Routing for all the pages mentioned above is added automatically by the ARouter framework when the code is compiled via the annotation keyword @route and injection method Inject (). ARouter also supports the addition of dynamic routes.
ARouter.getInstance().addRouteGroup(new IRouteGroup() {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/dynamic/activity".// path
RouteMeta.build(
RouteType.ACTIVITY, // Routing information
TestDynamicActivity.class, // Target Class
"/dynamic/activity".// Path
"dynamic".// Group, try to keep the same as the first paragraph of path
0.// Priority, not currently used
0 // Extra, for page marking)); }});Copy the code
This method applies to some plug-in development scenarios. The jump process after adding routes is the same as before.
service
The IProvider service component is defined by implementing the IProvider interface to meet the invocation requirements of other components. In plain English, AModule exposes functionality through services that other Modules can call.
-
Implement the IProvider interface
Define the ARouter service through the IProvider interface.
// Declare the interface through which other components invoke the service public interface HelloService extends IProvider { String sayHello(String name); } // Implement the interface @ the Route (path = "/ yourservicegroupname/hello," name = "test services") public class HelloServiceImpl implements HelloService { @Override public String sayHello(String name) { return "hello, " + name; } @Override public void init(Context context) {}}Copy the code
In componentized development, it is common to define an interface class HelloService in the underlying module (for example, in the Base module) and implement the interface functions in the concrete module.
Note: the init method is triggered only when the service is in use.
-
The discovery service
-
Dependency injection
public class Test { @Autowired HelloService helloService; @Autowired(name = "/yourservicegroupname/hello") HelloService helloService2; public void fun(a){ // You can use the service directly // 1. (recommended) Use dependency injection to discover services. Annotate fields and use them // The Autowired annotation will use byName to inject the corresponding field, without setting the name attribute. By default, it will use byType to discover the service (when multiple implementations of the same interface must use byName). helloService.sayHello("1"); helloService2.sayHello("2"); }}Copy the code
-
Rely on to find
public class Test { @Autowired HelloService helloService; @Autowired(name = "/yourservicegroupname/hello") HelloService helloService2; public void fun(a){ // 2. Use the dependency lookup method to discover and use the service. The following two methods are byName and byType respectively helloService3 = ARouter.getInstance().navigation(HelloService.class); helloService4 = (HelloService) ARouter.getInstance().build("/yourservicegroupname/hello").navigation(); helloService3.sayHello("Vergil"); helloService4.sayHello("Vergil"); }}Copy the code
-
Pre-processing service
// Implement the PretreatmentService interface with an annotation of the Path content @Route(path = "/xxx/xxx") public class PretreatmentServiceImpl implements PretreatmentService { @Override public boolean onPretreatment(Context context, Postcard postcard) { // Preprocess the jump. If you need to process the jump yourself, return false } @Override public void init(Context context) {}}Copy the code
-
summary
We’ve basically covered all the features and usage of ARouter. Similar routing frameworks exist
- Meituan: WMRouter WMRouter
- DRouter DRouter
If you’re interested, check it out, but at the moment ARouter is used more often.