1. It resolved

  • Postcard, inherited from RouteMeta, acts as the location provider class in the routing process, and all jump message classes in a project are based on this class and provide some jump capability

RouteMeta

    private RouteType type;         		   // Route type, supported as follows:
                                                // 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");

    private Element rawType;        		   // The original type in the route
    privateClass<? > destination;// Jump to the target class
    private String path;            		   // Routing address
    private String group;           		   // Routing groups
    private int priority = -1;      		   // Priority. The smaller the number, the higher the priority
    private int extra;              		   // Additional data
    private Map<String, Integer> paramsType;  	/ / parameters
    private String name;					  / / name
    private Map<String, Autowired> injectConfig;// Cache injection configuration
Copy the code

Postcard

    // Base
    private Uri uri;									//Uri
    private Object tag;             				   	  // The tag is prepared for certain errors. This is an internal parameter. Do not use it
    private Bundle mBundle;         					  // Data conversion Bundele
    private int flags = 0;        						  // Routing flags
    private int timeout = 300;      					  // Timeout period
    private IProvider provider;     					  // If the PostCard is a Provider, it is used to pass data
    private boolean greenChannel;						  // Green channel, true, the interceptor is invalid
    private SerializationService serializationService;	    // Serialization service for internal use
    private Context context;       	 					  / / context
    private String action;								 / / action

    // Animation processing
    private Bundle optionsCompat;    
    private int enterAnim = -1;
    private int exitAnim = -1;
Copy the code

Postcard.navigation()

  • The PostCard also provides a series of hop navigation methods, which are actually called internally to the ** arouter.getInstance ().navigation(mContext, this, requestCode, callback)** method, It is finally implemented in the _arouter.getInstance ().navigation(mContext, Postcard, requestCode, callback) method

    _ARouter is actually the class that implements the functionality

  • _arouter.getInstance ().navigation()

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
        if (null! = pretreatmentService && ! pretreatmentService.onPretreatment(context, postcard)) {return null;
        }
        postcard.setContext(null == context ? mContext : context);
        try {
            // The Logistics center prefixes the Postcard and fully assembles it internally
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());
            //Debug specialized processing
            if (debuggable()) {
                // If the Postcard preprocessing fails, the user is prompted with a route mismatch
                runInMainThread(new Runnable() {
                    @Override
                    public void run(a) {
                        Toast.makeText(mContext, "There's no route matched! \n" +
                                " Path = [" + postcard.getPath() + "]\n" +
                                " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show(); }}); }// If there is a callBack implementation, the onLost callBack is called
            if (null! = callback) { callback.onLost(postcard); }else {
                // If there is no specific implementation, enable global degradation service for processing
                DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
                // Call the onLost callback if the degraded service is also not pre-declared
                if (null != degradeService) { degradeService.onLost(context, postcard); }
            }
            return null;
        }
    	// If the destination is found, the onFound callback is performed
        if (null! = callback) { callback.onFound(postcard); }// If it is not a green channel, the interceptor processing is performed
        if(! postcard.isGreenChannel()) { interceptorService.doInterceptions(postcard,new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(postcard, requestCode, callback);
                }
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null! = callback) { callback.onInterrupt(postcard); } logger.info(Consts.TAG,"Navigation failed, termination by interceptor : "+ exception.getMessage()); }}); }else {
            // Otherwise, do _navigation()
            return _navigation(postcard, requestCode, callback);
        }
        return null;
    }
Copy the code

If the destination exists, the final implementation is in the **_navigation(postcard, requestCode, callback)** method, whether or not the green channel exists

_navigation(postcard, requestCode, callback)

  • Methods are sorted internally by type and eventually called to the Android implementation, such as the startActivity method if the type is Activity.
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = postcard.getContext();
        switch (postcard.getType()) {
            // Activity type processing
            case ACTIVITY:
                / / assembly Intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());
                int flags = postcard.getFlags();
                if (0! = flags) { intent.setFlags(flags); }if(! (currentContextinstanceof Activity)) {
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }
                String action = postcard.getAction();
                if(! TextUtils.isEmpty(action)) { intent.setAction(action); }// Finally call startActivity on the main thread
                runInMainThread(new Runnable() {
                    @Override
                    public void run(a) { startActivity(requestCode, currentContext, intent, postcard, callback); }});break;
            // Provider type processing
            case PROVIDER:
                // Return directly
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
                / / get fragmentMetaClass<? > fragmentMeta = postcard.getDestination();try {
                    Object instance = fragmentMeta.getConstructor().newInstance();
                    // Ordinary Fragment processing
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    // V4 Fragment processing
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }
                    / / returns the fragments
                    return instance;
                } catch (Exception ex) {
                    // Cannot find the corresponding instance
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }
        return null;
    }
Copy the code

conclusion

  1. First, Arouter creates the PostCard based on the final destination and the corresponding jump parameters. The PostCard contains all the properties inside
  2. The final jump is through the PostCard’s navigation() method
    1. The navigation() method ends up calling the implementation of _arouter.getInstance ().navigation() internally
    2. The _arouter.getInstance ().navigation() interior will first implement the logistics pre-postcard via LogisticsCenter, and continue if it is a legal path
      1. If it is a green channel, no interceptor processing is performed
      2. If it is not a green channel interceptor processing is performed
    3. Finally, the _navigation() method is called
  3. The interior of _navigation() is differentiated by type
    1. If it is an Activity, then the Intent is loaded from the PostCard, and finally StartActivity() is called
    2. If it is a Provider, it returns the Provider in the PostCard directly
    3. If it is Fragment, it is V4 or ordinary Fragment and returns the instance. If no instance is found, an exception is displayed

2.Annotation handler

One of the great things about Arouter is that it automatically populates a lot of repetitive code with annotations, which are provided as follows:

  • Route: The marked page can be routed through the router
  • Interceptor: Marks an Interceptor for route interception
  • Autowired: Automatically implement parameter assembly

Every annotation must correspond to this annotation handler, so that all tags corresponding to the annotation class can automatically implement its capabilities

Both annotation handlers implement aspect oriented programming capabilities through Javapoet and auto-service

Route

Let’s look directly at the parseRoutes method in Process, leaving only the key parts in the code

private void parseRoutes(Set<? extends Element> routeElements) throws IOException {...if (CollectionUtils.isNotEmpty(routeElements)) {
        ......
        // All incoming elements marked with a Route are analyzed and a RouteMeta is returned to be added to the Map
        for (Element element : routeElements) {
            // Activity or Fragment
            if (types.isSubtype(tm, type_Activity) || types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
             	.....
                if (types.isSubtype(tm, type_Activity)) {
                    // Create a RouteMeta for the Activity type
                    routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
                } else {
                    // Create a RouteMeta for the Fragment type
                    routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
                }
                routeMeta.setInjectConfig(injectConfig);
            } else if (types.isSubtype(tm, iProvider)) {
                // Create a RouteMeta for the Provider type
                routeMeta = new RouteMeta(route, element, RouteType.PROVIDER, null);
            } else if (types.isSubtype(tm, type_Service)) {
                // Create a RouteMeta for the Service type
                routeMeta = new RouteMeta(route, element, RouteType.parse(SERVICE), null);
            } else {
                throw new RuntimeException("The @Route is marked on unsupported class, look at [" + tm.toString() + "].");
            }
            // Finally add the RouteMeta to the groupMap
            categories(routeMeta);
        }
        MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(providerParamSpec);

        Map<String, List<RouteDoc>> docSource = new HashMap<>();

        // Start generating JAVA code based on the groupMap list
        for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
         	......
            Set<RouteMeta> groupData = entry.getValue();
            for (RouteMeta routeMeta : groupData) {
                .....
                switch (routeMeta.getType()) {
                    case PROVIDER: 
                        .....
                        // If the type is Provider, cache it
                        break;
                    default:
                        break;
                }
                StringBuilder mapBodyBuilder = new StringBuilder();
                Map<String, Integer> paramsType = routeMeta.getParamsType();
                Map<String, Autowired> injectConfigs = routeMeta.getInjectConfig();
                if (MapUtils.isNotEmpty(paramsType)) {
                    List<RouteDoc.Param> paramList = new ArrayList<>();
                    for (Map.Entry<String, Integer> types : paramsType.entrySet()) {
                        // Start actually writing JAVA code
                        mapBodyBuilder.append("put(\"").append(types.getKey()).append("\",").append(types.getValue()).append("); ");
                        RouteDoc.Param param = newRouteDoc.Param(); Autowired injectConfig = injectConfigs.get(types.getKey()); param.setKey(types.getKey()); param.setType(TypeKind.values()[types.getValue()].name().toLowerCase()); param.setDescription(injectConfig.desc()); param.setRequired(injectConfig.required()); paramList.add(param); } routeDoc.setParams(paramList); } String mapBody = mapBodyBuilder.toString(); }... }}Copy the code

@ the Route summary

  1. Get all classes annotated Route, generate the corresponding RouteMeta, put the group into the groupMap, key is group name, value is put into the Set that supports sorting

  2. All routemetas that traverse all sets in the groupMap (so it’s a two-layer for loop) generate the corresponding code.

    The corresponding code generated is as follows:

    public class ARouter$$Group$$test implements IRouteGroup {
      @Override
      public void loadInto(Map<String, RouteMeta> atlas) {
        atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview"."test".null, -1)); }}Copy the code

Autowired

  • Autowired is actually implemented through the annotation processor. The code is relatively simple, and it actually generates the automatic assembly of Intent. GetInstance ().inject(this) code is generated as an automatic assembly of inject’s rewrite intent.

The generated code is as follows:

public class Test1Activity$$ARouter$$Autowired implements ISyringe {
  private SerializationService serializationService;
  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);
    Test1Activity substitute = (Test1Activity)target;
  	// Intents are automatically assembled
    substitute.age = substitute.getIntent().getIntExtra("age", substitute.age);
    substitute.height = substitute.getIntent().getIntExtra("height", substitute.height);
    substitute.girl = substitute.getIntent().getBooleanExtra("boy", substitute.girl);
    For example, to use serialization, implement SerializationService
    substitute.url = substitute.getIntent().getExtras() == null ? substitute.url : substitute.getIntent().getExtras().getString("url", substitute.url); substitute.helloService = ARouter.getInstance().navigation(HelloService.class); }}Copy the code

The @autowired summary

  • Annotate variables in the Activity. The annotation processor will scan variables annotated by Autowired during compilation and generate a Java code based on the class and variables. The main object of the annotation is the inject method, which is used to parse and assign values to related variables. Then call the corresponding inject method near the onCreate position of the Activity. The main purpose of this annotation is to eliminate the need to manually write code to parse Intent parameters.

Interceptor

  • The interceptor implementation is essentially two methods in the InterceptorServiceImpl class

doInterceptions

@Override
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
    if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
        checkInterceptorsInitStatus();
        if(! interceptorHasInit) {// If the initialization is not complete before the interception is handled, then the initialization time is too long
            callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
            return;
        }
        // The async thread calls _execute() one at a time
        LogisticsCenter.executor.execute(new Runnable() {
            @Override
            public void run(a) {
                CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                try {
                    _execute(0, interceptorCounter, postcard);
                    interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                    // Check whether the timeout period has expired
                    if (interceptorCounter.getCount() > 0) {   
                        callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                    // Exception handling
                    } else if (null! = postcard.getTag()) { callback.onInterrupt((Throwable) postcard.getTag());// Continue processing
                    } else{ callback.onContinue(postcard); }}catch(Exception e) { callback.onInterrupt(e); }}}); }else{ callback.onContinue(postcard); }}Copy the code

_execute

private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
        if (index < Warehouse.interceptors.size()) {
            IInterceptor iInterceptor = Warehouse.interceptors.get(index);
            iInterceptor.process(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    counter.countDown();
                    // Internally recurse the call, and adjust the index value
                    _execute(index + 1, counter, postcard);  
                }

                @Override
                public void onInterrupt(Throwable exception) {
                    postcard.setTag(null == exception ? new HandlerException("No message.") : exception); counter.cancel(); }}); }}Copy the code

@ Interceptor summary

  1. In the doInterceptions() method, an asynchronous thread implementation calls the _execute method
    1. There is also internal exception and timeout handling
  2. _execute() is called recursively and jumps to index to handle interceptors one by one