1. Routing schemes
Disadvantages of native routing schemes:
Explicit: Direct class dependency, heavy coupling
Implicit: centralized rule management, difficult collaboration
Manifest has poor extensibility
The jump process is uncontrollable
Failure cannot be downgraded
Advantages of ARouter:
Automatic registration of mapping relationships and distributed routing management using annotations
Processing annotations and generating mapping files during compilation without using reflection does not affect runtime performance
Mapping relationships are grouped, multilevel managed, and initialized on demand
Flexible downgrade strategy, which calls back the results of each jump to avoid throwing operational level exceptions if StartActivity() fails
Custom interceptors, custom intercept order, can intercept routes, such as login judgment and buried point handling
Supports Dependent Injection, which can be used independently as a Dependent Injection Framework for cross-module API calls
Supports direct parsing of standard URL s for jumps and auto-injection of parameters into target pages
Support for getting Fragment s
Support multi-module use and component development
…….
With all these benefits, it's time to get to know ARouter.
There are advanced Android development materials and interview materials at the end of the article ~~
2. ARouter Framework
The above diagram is a basic framework diagram organized according to ARouter's basic routing and navigation process, involving the main processes, which are described in detail below.
3. Routing Management
1. Registration
With annotations, annotated classes or variables are collected at compile time and managed uniformly by Android Process Tool processing.
There are three notes @Autowired, @Interceptor, @Route.
@Route
Annotation Definition
String path();//Path URL String String group() default "";//Group name, default to first-level path name; once set, jump must be assigned String name() default "undefined";//The name of the path used to generate JavaDoc int extras() default Integer.MIN_VALUE;//Switch information for additional configuration; e.g. whether some pages require network checking, login checking, etc. int priority() default -1;//Priority of the path
Implement the @Route annotation
BlankFragment @Route(path = "/test/fragment") Test1Activity @Route(path = "/test/activity1")
This annotation is mainly used to describe the path URL information in a route. Classes labeled with this annotation are automatically added to the routing table.
@Autowired
Annotation Definition
boolean required() default false; String desc() default "No desc.";
Implement the @Autowired annotation
@Autowired int age = 10; @Autowired HelloService helloService;
This comment is used to pass parameters when a page is jumped.Variables that use this annotation flag in the target Class automatically assign the passed parameter values after calling inject() when the page is routed open.
@Interceptor
Annotation Definition
int priority();//Priority of the interceptor String name() default "Default";//The name of the interceptor used to generate JavaDoc
Implement the @Interceptor annotation
The implementation class generally applied to IInterceptor is the interceptor in the routing jump process, regardless of module, and applies globally.
@Interceptor(priority = 7) public class Test1Interceptor implements IInterceptor { @Override public void process(final Postcard postcard, final InterceptorCallback callback) { ............ } }
2. Collection
Mapping files are generated automatically during compilation, and arouter-compiler implements some annotation processors with the goal of generating mapping files and auxiliary files.
All three types of annotation processors implement AbstractProcessor with the following main functions:
First sweep the labeled class file through the annotation processor
Classify by different types of source files
Generate mapping files in a fixed named format
This allows the mapping file to be loaded with a fixed package name at runtime initialization.
Source code for comment processing Ali Routing Framework--Compiler for ARouter Source Resolution.
Take the official demo for example, by annotating the processor, a mapping file is generated in a fixed named format.
Take ARouter$$Root$$app for example, and look at the content of the class file generated by the annotation processor:
public class ARouter$$Root$$app implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("service", ARouter$$Group$$service.class); routes.put("test", ARouter$$Group$$test.class); } }
Load the group class files it manages into the collection by calling the loadInto() method to facilitate subsequent routing lookups.
3. Loading
The previous collections are all acquired by compiler processing, so the load is at runtime.To avoid memory and performance depletion, ARouter proposed a "group management, load on demand" approach.During the previous compilation process, corresponding mapping files have been generated for different types.
In the case of official demo, an app module has a Root node that manages groups of Groups, with multiple interfaces under each Group, and an Interceptor node and a provider node under the app module.
The Interceptor node corresponds to a custom interceptor, and the provider node corresponds to an IOC for cross-module API calls.
ARouter only loads all root nodes at once during initialization, not any Group nodes, which greatly reduces the number of nodes loaded during initialization.When a page under a Group is visited for the first time, all pages of the entire Group are loaded.
Initial Loading
ARouter is actually a proxy class, and all its function implementations are handed over to _ARouter, both of which are singleton patterns.
public static void init(Application application) {//Initialization of static functions, independent of objects if (!hasInit) { logger = _ARouter.logger; //Hold a global static scalar for log printing _ARouter.logger.info(Consts.TAG, "ARouter init start.");//Print ARouter initialization log hasInit = _ARouter.init(application);//Deliver_ARouter to Initialize if (hasInit) { _ARouter.afterInit(); } _ARouter.logger.info(Consts.TAG, "ARouter init over.");//Print ARouter initialization log } }
Continue to look at the _ARouter initialization method
protected static synchronized boolean init(Application application) { mContext = application;// Context of Application LogisticsCenter.init(mContext, executor);//Transfer the Logical Center for initialization and pass in the Line City Pool object logger.info(Consts.TAG, "ARouter init success!");//Print Log 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; }
Move on and see how LogisticsCenter initializes
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException { mContext = context; //Statically hold the context of the Application executor = tpe;//Static hold line city pool try { // These class was generate by arouter-compiler. // By specifying the package name com.alibaba.android.arouter.routes, find the class name (without the loading class) in the routes directory that all compile-time generations generate. List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE); for (String className : classFileNames) {//Group List 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().newInstance())).loadInto(Warehouse.groupsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {//List of interceptors in the module com.alibaba.android.arouter.routes.ARouter$$Interceptors // Load interceptorMeta ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {//IOC Action Routing List com.alibaba.android.arouter.routes.ARouter$$Providers // Load providerIndex ((IProviderGroup) (Class.forName(className).getConstructor().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() + "]"); } }
With the above code, the way of "group management, load on demand" is implemented, and the nodes managed in the classes generated by the corresponding three annotation processors are loaded into the routing collection.
The memory warehouse Warehouse caches the list of groups for global application, the list of IOC action routes, the list of interceptors in modules, and three map objects.
class Warehouse { // Cache route and metas static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();//The list of groups contains the mapping of the group name to the routing list Class within the corresponding group static Map<String, RouteMeta> routes = new HashMap<>();//The routing list within the group contains the mapping relationship between the routing URL and the target object Class under the corresponding grouping // Cache provider static Map<Class, IProvider> providers = new HashMap<>(); //Cache IOC target class es and created objects static Map<String, RouteMeta> providersIndex = new HashMap<>();//IOC's Action Routing List contains a mapping relationship between a class's routing URL and class using a dependent injection method // Cache interceptor //The list of interceptors within a module contains the mapping of interceptors to priorities under a module 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 }
4. Routing lookup
ARouter.getInstance().build("/test/activity2").navigation();</pre>
Take the example above to see the ARouter routing lookup process.First, take a look at the build process
1.build()
public Postcard build(String path) { return _ARouter.getInstance().build(path); } protected Postcard build(String path) { if (TextUtils.isEmpty(path)) { throw new HandlerException(Consts.TAG + "Parameter is invalid!"); } else { PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class); if (null != pService) { path = pService.forString(path); } return build(path, extractGroup(path)); } }
It uses build() of the proxy class _ARouter and builds and returns PostCard objects.A Postcard object corresponds to a routing request and acts on the whole process of the routing.
This part of the code mainly contains two parts:
- Use IOC byType() to find the implementation class for the PathReplaceService.class interface, which is used to implement "Runtime Dynamic Routing Modification".
- Continue with this routing navigation
First, let's look at the PathReplaceService.class interface:
public interface PathReplaceService extends IProvider { /** * For normal path. * * @param path raw path */ String forString(String path); /** * For uri type. * * @param uri raw uri */ Uri forUri(Uri uri); }
It mainly contains forString() and forUri methods, which preprocess routes to achieve "dynamic modification of routes during runtime".
Next, continue routing navigation through build (path, extractGroup (path), where extractGroup() gets the default grouping information from the path.
The build() method then returns a Postcard object and passes the corresponding path and grouping information to it.
After analyzing the above process, let's take a closer look at the navigation() method in PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class); which actually calls the navigation (Class<? Extends T> service) method of the proxy class_ARouter.
2.navigation(Class<? extends T> service)
protected <T> T 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; } }
LogisticsCenter.buildProvider(service.getName()) first finds and constructs a PostCard object based on the information stored by Warehouse in providersIndex
Then execute LogisticsCenter.completion(postcard), which perfects the postcard object based on the route information of routes saved by Warehouse. This method will also appear below and will be described in detail when
Going back to the previous section, ARouter.getInstance().build("/test/activity2").navigation(), returning the PostCard object, starts calling the corresponding navigation() method.
3.navigation()
Observing this method in PostCard
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); } public void navigation(Activity mContext, int requestCode) { navigation(mContext, requestCode, null); } public void navigation(Activity mContext, int requestCode, NavigationCallback callback) { ARouter.getInstance().navigation(mContext, this, requestCode, callback); }
Finally, the navigation() method in ARouter is called, in which the navigation() method in _ARouter is actually called.
This method includes callback lookup, demotion, and interceptor handling specific routing operations.
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) { try { LogisticsCenter.completion(postcard); } 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);//Triggering route lookup failed } else { // No callback for this invoke, then we use the global degrade service. DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class); if (null != degradeService) { degradeService.onLost(context, postcard); } } return null; } //Routing meta information found, triggering callback for route lookup if (null != callback) { callback.onFound(postcard); } //Green Channel Check Requires Interception Processing if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR. //Call the interceptor section controller, traverse the custom interceptor in the memory warehouse, and execute the interceptor function in an asynchronous thread interceptorService.doInterceptions(postcard, new InterceptorCallback() { /** * Continue process * * @param postcard route meta */ @Override public void onContinue(Postcard postcard) { _navigation(context, postcard, requestCode, callback); } /** * 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); } return null; }
The two most important methods are LogisticsCenter.completion() and _navigation(), which are described in more detail below.
public synchronized static void completion(Postcard postcard) { if (null == postcard) { throw new NoRouteFoundException(TAG + "No postcard!"); } //Obtaining path meta-information from path URL RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath()); if (null == routeMeta) { // Maybe its does't exist, or didn't load. //The list path within the group may not be loaded, getting the corresponding group from the list of groups Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta. if (null == groupMeta) { throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]"); } else { //Add the list of in-group lists for this group to the memory warehouse and remove the group 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(); iGroupInstance.loadInto(Warehouse.routes); Warehouse.groupsIndex.remove(postcard.getGroup()); 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); // Trigger perfect logic again } } else { postcard.setDestination(routeMeta.getDestination());//Target class postcard.setType(routeMeta.getType());//Routing Class postcard.setPriority(routeMeta.getPriority());//Routing Priority postcard.setExtra(routeMeta.getExtra());//Additional configuration switch information Uri rawUri = postcard.getUri(); if (null != rawUri) { // Try to set params into bundle. Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri); Map<String, Integer> paramsType = routeMeta.getParamsType(); if (MapUtils.isNotEmpty(paramsType)) { // Set value by its type, just for params which annotation by @Param for (Map.Entry<String, Integer> params : paramsType.entrySet()) { setValue(postcard, params.getValue(), params.getKey(), resultMap.get(params.getKey())); } // Save params name which need auto inject. postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{})); } // Save raw uri postcard.withString(ARouter.RAW_URI, rawUri.toString()); } 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) { throw new HandlerException("Init provider failed! " + e.getMessage()); } } postcard.setProvider(instance); postcard.greenChannel(); // Provider should skip all of interceptors break; case FRAGMENT: postcard.greenChannel(); // Fragment needn't interceptors default: break; } } }
This method is to perfect PostCard to achieve a routing navigation.
Next, another method, navigation(), is introduced.
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://Intent jump if Acitvity // 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 (!(currentContext instanceof 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 ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version. ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim()); } if (null != callback) { // Navigation over. callback.onArrival(postcard); } } }); break; case PROVIDER://If IOC, return target object instance return postcard.getProvider(); case BOARDCAST: case CONTENT_PROVIDER: case FRAGMENT://If it is a 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; }
So far we have completed a route jump.
I am an Android engineer who has been engaged in seven years of development. Many people ask me privately how to learn Android Advancement in 2019. Is there any way?
Yes, I spent more than a month compiling the learning materials at the beginning of the year, hoping to help those friends who want to improve their Android development but don't know how to do so.[Including advanced UI, performance optimization, architect courses, NDK, Kotlin, architecture technical materials such as ReactNative+Weex, Flutter, etc.) I hope to help you review and find a good job before the interview, and also save you time to search for information on the web to learn.