Introduction to the

There are many different routing frameworks out there. And I won’t belabor what is routing? What the meaning of the routing framework is.

features

  • Security: The route is being started. Catch the exception and notify the user. Don’t worry about crash at all.
  • Powerful interceptor capabilities: unlike most routes. Three route interceptor mechanisms are provided for different services.
  • Convenient: use APT annotation to generate routing table, easy configuration, easy maintenance.
  • Flexible: The routing table can be configured in various ways, which can be used under any conditions.
  • Supports two types of routing: page routing and action routing.
  • Route restart: After a route is intercepted. The restart route can be restored seamlessly with a single line of code. This can be useful in login checking.
  • Highly customizable: perfect support for single, componentized, and plug-in environments. It can also be customized.

usage

Click to go to the Github page

This article describes how to use the Router in a single product or component environment, and how to use the Router in a plug-in environment. Please refer to the following article:

Router: How do you perform routing adaptation in any plug-in environment

Rely on

  • Add the JitPack repository
allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io'}}}Copy the code
  • Add the dependent
compile "Com. Making. Yjfnypeu. The Router, the Router - API: server"
annotationProcessor "Com. Making. Yjfnypeu. The Router, the Router - the compiler: server"
Copy the code

The routing table

Definition of a routing table

Understand what a routing table is. The following definitions need to be made clear first:

Route mapping: Mapping a set of specific urls to a specific page. For example, www.baidu.com maps to Baidu pages.

Routing table: Also known as routing mapping table, a container used to store all route mappings.

Route: Indicates that a url is used to match the corresponding page in the routing table. And complete the startup process. This is called a single route.

Here is a simple routing process:

So for Android. A routing table can be thought of as a set of mappings between a particular URL and a particular Activity

Creating a Routing Table

Unlike many other routing frameworks. The routing framework does not use automatic routing table registration. Because automatic registration of routing tables lacks flexibility.

There are two ways to create routing tables: manually creating routing tables and dynamically generating routing tables at compile time using annotations.

This section mainly introduces the creation method of dynamic generation by using annotations. The use scenario of manually creating routing tables is recommended only in a partially compiled environment that does not support runtime annotations.

As mentioned above, a route map is a mapping between a specific set of urls and a specific page. So by specifying the page. Add annotations to the specified URL on the configuration. A set of corresponding route mappings can be obtained:

@RouterRule("haoge://page/user")
public class UserActivity extends Activity {... }Copy the code

After adding this annotation. You can trigger a compile on the project. Make it automatically generate the corresponding routing table class:

An automatically generated routing table named RouterRuleCreator:

// This class is automatically generated for compile-time annotations. public class RouterRuleCreator implements RouteCreator { @Override public Map<String, ActivityRouteRule>createActivityRouteRules() {
    Map<String,ActivityRouteRule> routes = new HashMap<>();
    routes.put("haoge://page/user", new ActivityRouteRule(UserActivity.class));
    return routes;
  }

  @Override
  public Map<String, ActionRouteRule> createActionRouteRules() { Map<String,ActionRouteRule> routes = new HashMap<>(); .returnroutes; }}Copy the code

PS: If the current environment does not support compile time annotations. You can manually create this RouteCreator routing table instance class for use.

Registering a Routing Table

After a specific routing table class is generated. You can register the routing table with the following code:

RouterConfiguration.get().addRouteCreator(new RouterRuleCreator());
Copy the code

After successful registration. Then start the system in the following ways:

Router.create(url).open(context);
Copy the code

This is the simplest route configuration and usage. The following steps will go into more detail

More than a pair of

For the same page. You can configure multiple distinct route links:

@RouterRule({url1, url2, url3})
public class ExampleActivity extends Activity {... }Copy the code

Page to get the startup URI

So all of the route start events, they’re going to store the url link that was started in the bundle for delivery. It can be read with the following key value:

Uri uri = getIntent().getParcelableExtra(Router.RAW_URI);
Copy the code

Configuration baseUrl

In general: an app defines the route URL to use will have a specific prefix. If you’re in a plug-in environment. It is also recommended to define a unique route prefix for each plug-in. The reasons will be explained in the next article on plug-in environment routing configuration.

The framework provides the RouteConfig annotation. A Module can only be configured once and must be configured on an Application subclass:

@RouteConfig(baseUrl="haoge://page/")
public class App extends Application {... }Copy the code

The following table shows the matching relationship between the route prefix and the route address. The horizontal bar indicates the route address configured by RouteRule, and the vertical bar indicates the baseUrl.

Automatically resolve URL parameters

Automatic parameter resolution of the Router. The Parceler framework is used in conjunction with the Parceler framework. For more information about the Parceler framework, see the link below:

Parceler: This is the elegant way to use bundles for data access!

Note that the Parceler framework is not a framework that the Router must depend on. It’s just that by adding this framework to use, you can use more powerful features.

If you don’t use it. All url parameters. Are parsed as strings by default and passed.

  1. Automatic parameter conversion:

In the first place. We first configure the injection entry in the Activity base class. The corresponding data is automatically read from the intent. Insert into subclass member variables annotated by Arg:

public class BaseActivity extends Activity {

	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Parceler.toEntity(this,getIntent()); }}Copy the code

Suppose we currently have the following page:

@RouterRule("haoge://page/example")
public class ExampleActivity extends BaseActivity {
	@Arg
	String name;
	@Arg
	long id;
	@Arg
	booleanisLogin; . }Copy the code

You can see that. This page has three properties. And added the Arg annotation. At this point, we can use the following link to skip the passargument:

Router.create("haoge://page/example? name=haoge&id=10086&isLogin=false").open(context);
Copy the code

The parameters in the link will be automatically converted according to the type of the Arg annotation. After the transformation, it is transferred to an Intent for delivery. That is equivalent to the following operations:

String url = "haoge://page/example? name=haoge&id=10086&isLogin=false";
Uri uri = Uri.parse(url);
Bundle bundle = new Bundle;
bundle.putString("name", uri.getQueryParameter("name"))
bundle.putLong("id", 
	Long.parseLong(uri.getQueryParameter("id")));
bundle.putBoolean("isLogin", 
	Boolean.parseBoolean(uri.getQueryParameter("isLogin")));

// Start the route and pass the bundle.Copy the code

The parameters of this type are automatically converted. Only basic data types are supported. If the attribute type annotated by the Arg is not a basic data type. Instead of triggering automatic conversion, the String read is stored directly in the Intent.

  1. Passing complex parameters

But many times we pass more than just basic data types. For example, ordinary entity beans. Let’s say it’s a list. So this is Parceler’s time to shine!

Parceler provides JSON data conversion. For non-basic data types passed. You can pass this type of JSON string as an argument: that’s why the Parceler framework is recommended for use: small but powerful!

Complex parameters need to use Parceler’s conversion function on the data converter. Also described in the above article. Here I’m using the FastJson converter:

Parceler.setDefaultConverter(FastJsonConverter.class);
Copy the code

Let’s assume that we have this page and we need to pass the common entity class User:

@RouterRule("usercenter")
public class UserCenterActivity extends BaseActivity{
	@Arg
	User user;
}

public class User {
	public String username;
	publicString password; . }Copy the code

Then you can jump through the following link:

// For the sake of understanding, I have not directly assembled the link. User user = new User("router"."123456"); String json = JSON.toJSONString(user); // The JSON string needs to be url encoded first. String encodeJson = URLEncoder.encode(json); String url = String.format("haoge://page/usercenter? user=%s", encodeJson);
Router.create(url).open(context);
Copy the code

As you can see, in this way, you can pass the concrete JSON data directly. Note the JSON data in the link. Make sure you do encode first. Avoid internal URI parsing exceptions.

Because the user type corresponding to the target page is not a basic data type. So the value referred to by user will be directly referred to here. It is automatically decoded and passed directly to the target page in an Intent. In the target page, this JSON transformation is automatically parsed into the User class using Parceler. Complete the transmission of complex parameters

Action routing

As described above. All through one link. A corresponding page is displayed. This route hop is called a page route.

The Router also supports another type of route: action route. This type of route does not have page hops. It’s just for special operations.

For example: add to cart, empty cart data, log out, etc.

@RouterRule("shopcar.clear")
public class ClearShopcarAction extends ActionSupport {
	@Override
    public void onRouteTrigger(Context context, Bundle bundle) {
        // TODO Empty the shopping cart}}Copy the code

You can then trigger the empty cart action with the following link:

Router.create("haoge://page/shopcar.clear").open(context);
Copy the code

Additional request parameters

The example given above. All data is passed directly through a URL. But a lot of times. We are required based on this URL. Add some extra data to pass. Such as transition animation configuration, requestCode configuration, Intent. Flags configuration, and so on.

Router.create(url)
	.addExtras(bundle) // Add additional bundle data parameters
	.requestCode(code) / / for startActivityForResult
	.setAnim(enterAnim, exitAnim)// Transfer animation
	.addFlags(flag)// intent.addFlags(flag);
	.addInterceptor(interceptor)// Add an interceptor
	.setCallback(callback)// Set the route callback
	.open(context);
Copy the code

Add a route callback

As mentioned in the lecture on routing table, routing is a special startup process, and startup may not be successful. So it’s natural to have a routing callback interface.

The RouteCallback interface is of class RouteCallback:

Public interface RouteCallback {// When routing fails. Trigger this callback void notFound(Uri Uri, NotFoundException e); // When the route is started successfully. Trigger this callback void onOpenSuccess(Uri Uri, RouteRule rule); // When the route fails to be started. Triggers this callback. Void onOpenFailed(Uri Uri, Throwable e); }Copy the code

There are two types of route callback configurations:

  • Global route callback: all route start events. Will trigger this callback
RouterConfiguration.get().setCallback(callback);
Copy the code
  • Local route callback: only triggered by the current route.
Router.create(url)
	.setCallback(callback)
	.open(context);
Copy the code

Route callbacks can be a very useful feature when making a page jump burial point.

Log print

When router. DEBUG is set to true (the default is false). The framework will enable internal log output. It is recommended to use buildconfig. DEBUG for log switch control to output logs only during development:

Router.DEBUG = BuildConfig.DEBUG
Copy the code

Logs can be filtered through RouterLog

The interceptor

As the name suggests, the interceptor is used to perform a series of checks in advance during the route startup process. If the check does not meet the rules, the route startup will fail.

The classic example is the login check

This can cause you to write a lot of log-in logic locally when you don’t use a route to jump. It’s a lot of maintenance. And it’s not very flexible, but using an interceptor to do the login check is very convenient:

Here is a simple implementation of login interception:

// Implement the RouteInterceptor interface
public class LoginInterceptor implements RouteInterceptor{
    @Override
    public boolean intercept(Uri uri, RouteBundleExtras extras, Context context){
    	// Intercept when no login
        return! LoginChecker.isLogin(); }@Override
    public void onIntercepted(Uri uri, RouteBundleExtras extras, Context context) {
    	// After interception, skip to the login page and transmit routing information to facilitate recovery after login
        Intent loginIntent = new Intent(context,LoginActivity.class);
        // The URI is the route link
        loginIntent.putExtra("uri",uri);
        // The extras are loaded with all the additional configuration data
        loginIntent.putExtra("extras",extras); context.startActivity(loginIntent); }}Copy the code
public class LoginActivity extends BaseActivity {

	@Arg
	Uri uri;
	@Arg
	RouteBundleExtras extras;
	
	void onLoginSuccess(a) {
		if(uri ! =null) {
			// Login succeeded. Use this method to directly and seamlessly restore route startupRouter.resume(uri, extras).open(context); } finish(); }}Copy the code

The interceptor functionality is the focus of the Router framework and has been iterated over a long period of time. The Router currently offers three interceptors that you can use according to your needs. Be flexible about what type of interceptor to use.

1. Global default interceptor:

Set: RouterConfiguration.get().setInterceptor(Interceptor);

Scope: This global default interceptor. Will be triggered by all enabled routing events.

Recommended application scenarios:

Some checks that require global judgment, such as login checks. And it is best to configure the interceptor here. Add the corresponding switch control.

For example, if you do login checking, you can only enable login checking if there is a requestLogin parameter. Give control of login checking to the place where the URL is provided.

2. Interceptors specially configured for a route

Router-create (URL).addInterceptor(Interceptor);

Scope: Triggered only by the route created here.

Recommended application scenarios: Some checks need to be triggered only when the route is enabled here, for example, checking whether the external link of Deeplink is valid.

3. The interceptor is specially configured for a target route

Setting mode: On the target class with RouterRule configured. Add the @routeInterceptor () annotation. Add the annotation Class that needs to be configured:

@RouteInterceptors(CustomInterceptors.class)
@RouterRule(rule)
public class ExampleActivity extends Activity {}
Copy the code

Scope: Triggered when the destination route matched by the route URL is this route

Recommended usage scenario: Check the redirect of the page. For example, filter the passed parameters to avoid unforeseeable exceptions caused by invalid data.

The three interceptors are triggered in the following order: global default > a route > a target route, and when the route has been intercepted by an interceptor. No further interceptors will be triggered

The Router is used in a componentized environment

Use the Router framework for componentization. See the componentization demo on Github here

You can view the above demo together with the description below. To achieve more convenient understanding of the role!

Using a routing framework in a componentized environment. The following points need to be considered:

Register multiple routing tables

The Router itself does not use automatic registration to register the routing table. Instead, the system provides corresponding interfaces and methods for manual registration. This configuration method itself can achieve the effect of dynamically registering multiple routing tables:

RouterConfiguration.get().addRouteCreator(routeCreator);
Copy the code

So it’s used in componentization. Just find a way to register routing tables generated by multiple components themselves.

Activate the annotation handler for the business component

The generation of the routing table class is automatically generated at compile time. The scope of the compile-time generated framework is only in the current Module. Therefore, you need to add the Router annotation handler configuration to each business component, so that each business component can use the annotations to generate its own routing table class:

annotationProcessor "Com. Making. Yjfnypeu. The Router, the Router - the compiler: server"
Copy the code

And componentization, for the use of the annotation handler. It is recommended to pull all annotation handlers that need to be used into a single gradle script. Then apply directly by each component:

Create processor. Gradle:

dependencies {
    annotationProcessor "Com. Making. Yjfnypeu. The Router, the Router - the compiler: server".// All annotation handlers are placed in this configuration
}
Copy the code

Then apply the script directly to build.gradle in the component using the Apply from method.

There are several advantages to this approach:

  1. Annotation processor due to compile time annotations. Is supplied directly to the IDE for use. The corresponding code is not packaged into the APK when repackaged. So don’t worry about introducing extra unwanted code into APK.
  2. Facilitates unified control over version upgrades

Avoid routing table conflicts generated by multiple components

When used in a single product environment. It is described that a specific routing table class, RouterRuleCreator, is generated at compile time. This class generates a package name that is written dead by default: com.lzh.router.

Therefore, in a multi-component environment, you need to specify a different package name for each module to generate the routing table class. To avoid duplicate class conflicts:

@RouteConfig(pack="com.router.usercenter")
public class UCApp extends Application {... }Copy the code

The RouteConfig annotation provides more than just the baseUrl method used in a single product for route prefix configuration. Pack methods are also provided. Specifies the specific package name of the routing table generated by this Module. So for a componentized environment. Just specify different package names for different components!

The recommended way to register

Because of componentization, all components are loaded by the app shell. Loading on demand is not the case with pluginization. Therefore, in this environment, it is recommended to use reflection to register multiple routing tables and load all valid components at one time:

private void loadRouteRulesIfExist(a) {
	// This packs generates a set of package names for the routing table classes defined in all components.
	String[] packs = ComponentPackages.Packages;
	String clzNameRouteRules = ".RouterRuleCreator";
	for (String pack : packs) {
	    try{ Class<? > creator = Class.forName(pack + clzNameRouteRules); RouteCreator instance = (RouteCreator) creator.newInstance(); RouterConfiguration.get().addRouteCreator(instance); }catch (Exception ignore) {
	    	// ignore}}}Copy the code

Because you’re using reflection registration. So don’t forget to add the obfuscation configuration:

-keep class * implements com.lzh.nonview.router.module.RouteCreator
Copy the code

For details, see the componentization demo on Github

pluggable

See the link below for an article that describes how to use it in a plug-in environment

Router: How do you perform routing adaptation in any plug-in environment

If your current plugins are using Small or RePlugin. There are also two demos you can use

The Router is used in the Small environment

The Router is used in the RePlugin environment