Exploration of ARouter Source

Posted by ultrasound0000 on Sun, 12 May 2019 14:13:49 +0200

Exploration of ARouter Source

1. Questions

  1. How do I support direct parsing of standard URL s for jumping and automatically injecting parameters into the target page?
  2. How do I support Multidex, InstantRun?
  3. How can mapping relationships be grouped, multilevel managed, and initialized on demand?
  4. How does Dependent Injection work?
  5. How do I support adding multiple interceptors and customizing the interception order?
  6. How do components such as pages, interceptors, services automatically register with the framework?
  7. How to support multi-module project use?
  8. How do I support getting Fragment s?

Source code is the best teacher, we take these questions to the source code to find the answer;

Actually, ARouter has the above functions. I summarize two key points: APT, Reflection

Here, we analyze the three aspects of compile time, ARouter initialization, and route

2. What was done during compilation?

https://github.com/alibaba/ARouter.git

Let's take the official source code as an example and download it and the terminal enters the following command

cd ARouter && ./gradlew clean assembleDebug

The directory structure you see after the build is successful is as follows

App: Main Project

arouter-annotation: Module for storing ARouter annotations

arouter-compiler: logic for dynamically generating code during compilation

Arouter-api: Most of ARouter's internal implementation designs

arouter-register: Support for third-party consolidation

module-java: Cross-module Call Demo

module-kotlin: supports Kotlin

Let's familiarize ourselves with the next few classes

Route annotation

You can see that the path in the Route annotation is the route of the jump, and the group is its corresponding grouping (loaded on demand); it is important to note that ARouter supports other types besides jumping activities

Jump Type of Arouter

It can be inferred from the above that Flip Activity, Service, ContentProvider, and Java interfaces should now be supported

APT (Annotation Processor)

The APT(Annotation Processing Tool), or Annotation Processing Tool, is a tool for processing annotations and, more precisely, a tool for javac that scans and processes annotations at compile time.The annotation processor takes java code (or compiled byte code) as input and generates a.java file as output.Simply put, at compile time, a.java file is generated from annotations.Students who have used annotation frames such as Dagger and EventBus can feel that it is very convenient to write some annotations with these frames; in fact, they all generate some code by reading annotations;

Auto-injection of Arouter's parameters, support for interception, path and how classes are mapped are all indispensable to annotation processors.

Let's see what code is automatically generated in the app project

I chose to group into test and service codes to see that the end of the following two class names are stitched together using their own group names; because ARouter loads routes in groups instead of one at a time during runtime, and the following two classes are generated based on this consideration; each class loads its own group routes only, and one class does only one thing

Since ARouter does group-loading routing, there must be a class that holds all the group maps to quickly locate the group classes. We find that there is an Arouter$$Root$app class, which is what we suspect. The key holds the group name and the value holds its corresponding mapping class

Interceptor Generation Class:

You can see that this class contains all the interceptor classes declared, and the key corresponds to its priority

Let's use a diagram to summarize what we did during compilation

There are two things the compiler does, as shown in the figure above

  1. APT scans java class files for specified annotations
  2. Generate corresponding java classes by scanning analysis

3. What did the initialization do?

A picture is summarized as follows:

Let's make a specific analysis below

//ARouter.java
/**
 * Init, it must be call before used router.
 */
public static void init(Application application) {
    if (!hasInit) {
        logger = _ARouter.logger;
        _ARouter.logger.info(Consts.TAG, "ARouter init start.");
        hasInit = _ARouter.init(application);

        if (hasInit) {
            _ARouter.afterInit();
        }

        _ARouter.logger.info(Consts.TAG, "ARouter init over.");
    }
}

//_ARouter.java
protected static synchronized boolean init(Application application) {
        mContext = application;
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;

        // It's not a good idea.
        // if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
        //     application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
        // }
        return true;
    }

We see that the initialization of Arouter was actually done by the LogisticsCenter class

Let's look directly after the first else statement at the source code below

//LogisticsCenter.java

//public static final String ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes";
//public static final String DOT = ".";
//public static final String SDK_NAME = "ARouter";

if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
    logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
    // These class was generated by arouter-compiler.

    //1,readcom.alibaba.android.arouter.routesAll classes under package name
    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

    if (!routerMap.isEmpty()) {

        //2,Save all classes under the specified package name locally,Easy to read directly from file next time
        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE)
            .edit()
            .putStringSet(AROUTER_SP_KEY_MAP, routerMap)
            .apply();
    }
    // Save new version name when router map update finishes.
    PackageUtils.updateVersion(context);    
} else {
    logger.info(TAG, "Load router map from cache.");

    //3,from sp Read class information directly in
    routerMap = new HashSet<>(context
                              .getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE)
                              .getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}

logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
startInit = System.currentTimeMillis();

//4,Load a class with the specified prefix's package name into memory(Warehouse)
for (String className : routerMap) {
    if (className.startsWith("com.alibaba.android.arouter.routes.ARouter$$Root")) {

        // This one of root elements, load root.
        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance()))
        .loadInto(Warehouse.groupsIndex);

    } else if (className.startsWith("com.alibaba.android.arouter.routes.ARouter$$Interceptors")) {
        // Load interceptorMeta
        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance()))
        .loadInto(Warehouse.interceptorsIndex);

    } else if (className.startsWith("com.alibaba.android.arouter.routes.ARouter$$Providers")) {

        // Load providerIndex
        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance()))
        .loadInto(Warehouse.providersIndex);

    }
}

To summarize the above code, there are two main things to do.

  1. Find all classes for the specified package name

    Reading the specified package name class from all dex through the thread pool explains why ARouter supports InstantRun, MultiDex

  2. Create objects through reflection and load them into memory (Warehouse)

    You can see that the initialization process is actually loaded into the Warehouse class with groupsIndex, interceptors Index, providersIndex

4. What does routing do?

ARouter jumps are generally like this

 ARouter.getInstance()
         .build("/test/activity")
         .navigation(this, new NavCallback() {
                 @Override
                 public void onArrival(Postcard postcard) {

                 }

                @Override
                public void onInterrupt(Postcard postcard) {
                Log.d("ARouter", "Intercepted");
                }
        });

Tracking code can see that ARouter.build was ultimately delegated to _Arouter.build, as is navigation.

//_ARouter.java
/**
* Build postcard by path and default group
*/
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));
    }
}

/**
     * Build postcard by uri
     */
protected Postcard build(Uri uri) {
    if (null == uri || TextUtils.isEmpty(uri.toString())) {
        throw new HandlerException(Consts.TAG + "Parameter invalid!");
    } else {
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        if (null != pService) {
            uri = pService.forUri(uri);
        }
        return new Postcard(uri.getPath(), extractGroup(uri.getPath()), uri, null);
    }
}

Finally, a Postcard object is returned, which can be interpreted as a package carried during the route process, including path, group, parameter, target class, and so on.

Let's focus on what's done inside _ARouter.navigation

protected Object navigation(final Context context,
                            final Postcard postcard, 
                            final int requestCode, 
                            final NavigationCallback callback) {
        try {
            //1. Enrich postcard data
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {

            //2. Exception handling
            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);
            } 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);
                }
            }

            //Go straight back
            return null;
        }

        //3. Callback event handling
        if (null != callback) {
            callback.onFound(postcard);
        }

        // It must be run in async thread, maybe interceptor cost too mush time made ANR.
        if (!postcard.isGreenChannel()) {   

            //4. Non-Green Channel Passes Interceptor
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /**
                 * Continue process
                 *
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                    //5. Real Jump Logic
                    _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;
    }

Summarize the process is still clear

  1. Enrich postcard

    If you know the target class to jump to, you first have to look in the root index class, extract the annotation-related data, and set it to postcard; these are the jobs of LogisticsCenter.completion(postcard)

  2. exception handling

    When your path is not found, call callback.onLost and return directly

  3. Callback event handling

    callback.onFound is called directly if path finds it

  4. Distribute to Interceptors

    When routing requests for non-green channels are pre-interceptors, the interceptor returns control and proceeds with the fifth process

  5. Last Jump Process

    Include Activity jumps, Fragment, Provider instance generation

Attach navigation main flow chart

Let's continue tracking subprocesses 1, 4, 5

4.1 Enrich postcard

//LogisticsCenter.java
public synchronized static void completion(Postcard postcard) {
    if (null == postcard) {
        throw new NoRouteFoundException(TAG + "No postcard!");
    }

    //Find if the change path has a route record from the route cache
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());

    // Maybe its does't exist, or didn't load.
    if (null == routeMeta) {    

        //If routes don't exist, try looking at the root index class entry (see how ARouter implements dynamic grouping loading?)
        Class<? extends IRouteGroup> groupMeta = 
            Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.

        if (null == groupMeta) {
            //No throw exception found and handed over to superior
            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()));
                }

                //Find the corresponding grouping class loaded into memory,
                IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                iGroupInstance.loadInto(Warehouse.routes);

                //Remove records from the root index class because the group has been added to memory
                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() + "]");
            }

            //This call to its own method actually executes the following else statement code
            completion(postcard);   // Reload
        }
    } else {
        //Enrich postcard object data (from annotation parsing)

        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());

        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
                //Here is the information carried by the parameter data in the url (eg:xxx/xxx?A=1&b=abc) combined with the annotations in the target class
                //Determine the data type of each parameter and set it into the bundle
                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 {
                        //Generate service interfaces to decouple
                        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;
        }
    }
}

So we know that the goal of the completion method is simply to set a number of parameters for postcard to serve the later jump

4.2 Distributed to Interceptors

From the code above, we know that routing events are distributed to interceptors through interceptorService.doInterceptions, so when was the interceptorService object created?Trace code discovery was triggered by the afterInit method, whereas afterInit was only called during ARouter initialization

//_ARouter.java
static void afterInit() {
        // Trigger interceptor init, use byName.
        interceptorService = (InterceptorService) ARouter.getInstance()
            .build("/arouter/service/interceptor")
            .navigation();
}

//ARouter.java
/**
 * Init, it must be call before used router.
 */
public static void init(Application application) {
    if (!hasInit) {
        logger = _ARouter.logger;
        _ARouter.logger.info(Consts.TAG, "ARouter init start.");
        hasInit = _ARouter.init(application);

        if (hasInit) {
            _ARouter.afterInit();
        }

        _ARouter.logger.info(Consts.TAG, "ARouter init over.");
    }
}

Now that we know when to initialize the interceptor service, let's look at the InterceptorServiceImpl implementation class for the interceptor service

Load all custom interceptors when the interceptor service is initialized to make the doInterceptions method work properly

@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {

    private static boolean interceptorHasInit;
    private static final Object interceptorInitLock = new Object();

    //Load all interceptors using a thread pool (considering reflection time)
    @Override
    public void init(final Context context) {

        LogisticsCenter.executor.execute(new Runnable() {
            @Override
            public void run() {

                //The interceptors Index type is UniqueKeyTreeMap
                //This also explains why interceptor priority can be customized
                if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
                    for (Map.Entry<Integer, Class<? extends IInterceptor>> entry 
                         : Warehouse.interceptorsIndex.entrySet()) {

                        Class<? extends IInterceptor> interceptorClass = 
                            entry.getValue();

                        try {
                            IInterceptor iInterceptor = 
                                interceptorClass.getConstructor().newInstance();
                            iInterceptor.init(context);

                            //All interceptors created are placed in the array
                            Warehouse.interceptors.add(iInterceptor);

                        } catch (Exception ex) {
                            throw new HandlerException(TAG  
                                          + "ARouter init interceptor error! name = [" 
                                          + interceptorClass.getName()
                                          + "], reason = [" 
                                          + ex.getMessage()
                                          + "]");
                        }
                    }

                    interceptorHasInit = true;

                    logger.info(TAG, "ARouter interceptors init over.");

                    synchronized (interceptorInitLock) {
                        //Wake up other waiting threads
                        interceptorInitLock.notifyAll();
                    }
                }
            }
        });
    }

    private static void checkInterceptorsInitStatus() {
        synchronized (interceptorInitLock) {
            while (!interceptorHasInit) {
                try {
                    interceptorInitLock.wait(10 * 1000);
                } catch (InterruptedException e) {
                    throw new HandlerException(TAG + "Interceptor init cost too much time error! reason = [" + e.getMessage() + "]");
                }
            }
        }
    }

    @Override
    public void doInterceptions(final Postcard postcard,
                                final InterceptorCallback callback) {

        if (null != Warehouse.interceptors 
            && Warehouse.interceptors.size() > 0) {

            //1. Determine if the interceptor service is initialized and not completed for 10 seconds
            checkInterceptorsInitStatus();

            if (!interceptorHasInit) {
                //2, 10s not yet finalized, go back and return directly
                callback.onInterrupt(new HandlerException("Interceptors"+
                                     "initialization takes too much time."));
                return;
            }

            LogisticsCenter.executor.execute(new Runnable() {
                @Override
                public void run() {
                   //3. CountDownLatch is used for interceptor sequential execution in multiple threads
                   //Using thread pools improves resource utilization
                    CancelableCountDownLatch interceptorCounter = 
                        new CancelableCountDownLatch(Warehouse.interceptors.size());

                    try {
                        //4. Execute all interceptors in order (priority)
                        _excute(0, interceptorCounter, postcard);

                        //Wait for _excute method execution to complete within a specified time
                        interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);

                        //5. Mainly handle the fallback event
                        // Cancel the navigation this time, if it hasn't return anythings.
                        if (interceptorCounter.getCount() > 0) {    

                            callback.onInterrupt(new HandlerException("The interceptor"+
                                                      "processing timed out."));

                        } else if (null != postcard.getTag()) {    
                            // Maybe some exception in the tag.
                            callback.onInterrupt(new HandlerException(postcard.getTag()
                                                                         .toString()));
                        } else {
                            callback.onContinue(postcard);
                        }
                    } catch (Exception e) {
                        callback.onInterrupt(e);
                    }
                }
            });
        } else {
            //No interceptor executes _navigation, which is the jump process described below.
            callback.onContinue(postcard);
        }
    }
}

To summarize, the interceptor service has mainly done a few periods

  1. Declared interceptors that execute interceptors in priority order throughout the thread (taking into account interceptor time)
  2. No interceptor, jump directly into the process

4.3 Last Jump

//_ARouter.java
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. Set up related parameters
            // 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);
            }

            //2. UI Thread Starts Activity
            // 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());
                    }

                    //3. Ending operations include animation and its callback handling
                    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:
            return postcard.getProvider();
        case BOARDCAST:
        case CONTENT_PROVIDER:
        case FRAGMENT:
            //From here we can see that ARouter creates fragment s by reflection
            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;
}

From the above we can see that currently ARouter can only support Activity, Fragment, Provider three ways; Start the service, ContentProvider does not currently support; the rich postcard operations that precede are paving the way for the last step.

The route process of ARouter has been explained by and large.

5. How to achieve automatic page injection parameters?

Next let's see how ARouter automatically injects parameters, taking Activity as an example

//Router Jump
TestObj testObj = new TestObj("Rose", 777);

ARouter.getInstance().build("/test/activity1")
    .withString("name", "King")
    .withInt("age", 18)
    .withBoolean("boy", true)
    .withObject("obj", testObj)
    .navigation();


//Automatic parameter injection
// Declare a field for each parameter and label it with @Autowired
// Parcelable type data cannot be passed in URL s, Parcelable objects can be passed through ARouter api
@Route(path = "/test/activity")
public class Test1Activity extends Activity {

   @Autowired
   public String name;

   @Autowired
   int age;

   @Autowired(name = "girl") // Mapping different parameters in URL s by name
   boolean boy;

   @Autowired
   TestObj obj;    // Support for parsing custom objects, json delivery in URL s, serialization and deserialization of service interface support

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       ARouter.getInstance().inject(this);
       // ARouter automatically assigns values to fields without taking the initiative
       Log.d("param", name + age + boy);
   }
}


// If you need to pass custom objects, you need to implement a Serialization Service and annotate it with the @Route annotation (to make it easier for users to choose the serialization method themselves), for example:
@Route(path = "/service/json")
public class JsonServiceImpl implements SerializationService {

    @Override
    public void init(Context context) {
    }

    @Override
    public <T> T json2Object(String text, Class<T> clazz) {
        return JSON.parseObject(text, clazz);
    }

    @Override
    public String object2Json(Object instance) {
        return JSON.toJSONString(instance);
    }
}

It's natural to think that the automatic parameter injection is ARouter.getInstance().inject(this); go in and see the devil

//ARouter.java
/**
 * Inject params and services.
 */
public void inject(Object thiz) {
    _ARouter.inject(thiz);
}

//_ARouter.java
static void inject(Object thiz) {
        AutowiredService autowiredService = 
            ((AutowiredService) ARouter.getInstance()
             .build("/arouter/service/autowired")
             .navigation());

        if (null != autowiredService) {
            autowiredService.autowire(thiz);
        }
    }

From above, you can see that what ARouter.inject does is delegated to _ARouter.inject; and u ARouter does not want to throw it directly into a class called AutowiredService; we look for its implementation class, AutowiredServiceImpl, which is the key code for automatic parameter injection.

@Route(path = "/arouter/service/autowired")
public class AutowiredServiceImpl implements AutowiredService {
    private LruCache<String, ISyringe> classCache;
    private List<String> blackList;

    @Override
    public void init(Context context) {
        classCache = new LruCache<>(66);
        blackList = new ArrayList<>();
    }

    @Override
    public void autowire(Object instance) {
        String className = instance.getClass().getName();
        try {
            if (!blackList.contains(className)) {
                //1. Find proxy class from cache
                ISyringe autowiredHelper = classCache.get(className);
                if (null == autowiredHelper) {  // No cache.
                    //If it is not found, try to create an object with the following object name rules
                    //eg:xxxx.Test1Activity$$ARouter$$Autowired
                    autowiredHelper = 
                        (ISyringe) Class.forName(instance.getClass().getName() 
                         + SUFFIX_AUTOWIRED).getConstructor().newInstance();
                }

                //2. Give it to the agent class to do it
                autowiredHelper.inject(instance);

                //3. Cache
                classCache.put(className, autowiredHelper);
            }
        } catch (Exception ex) {
            blackList.add(className);    // This instance need not autowired.
        }
    }
}

So the injects of _ARouter and AutowiredServiceImpl don't want to work and finally the Test1Activity$$ARouter$$Autowired class does it honestly. Here's the code for parameter auto-injection. The code is simple and nonsense. You can see that the custom object is accomplished using a deserialized service called serralizationService.

As to how the Test1Activity$$ARouter$$Autowired class was generated, interested students can see the internal implementation of the AutowiredProcessor class; there may be students who have questions about how custom object data is deserialized?I don't know if you notice that there is a setValue code in the rich postcard process. Go in and see

private static void setValue(Postcard postcard, Integer typeDef, String key, String value) {
    if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) {
        return;
    }

    try {
        if (null != typeDef) {
            if (typeDef == TypeKind.BOOLEAN.ordinal()) {
                postcard.withBoolean(key, Boolean.parseBoolean(value));
            } else if (typeDef == TypeKind.BYTE.ordinal()) {
                postcard.withByte(key, Byte.valueOf(value));
            } else if (typeDef == TypeKind.SHORT.ordinal()) {
                postcard.withShort(key, Short.valueOf(value));
            } else if (typeDef == TypeKind.INT.ordinal()) {
                postcard.withInt(key, Integer.valueOf(value));
            } else if (typeDef == TypeKind.LONG.ordinal()) {
                postcard.withLong(key, Long.valueOf(value));
            } else if (typeDef == TypeKind.FLOAT.ordinal()) {
                postcard.withFloat(key, Float.valueOf(value));
            } else if (typeDef == TypeKind.DOUBLE.ordinal()) {
                postcard.withDouble(key, Double.valueOf(value));
            } else if (typeDef == TypeKind.STRING.ordinal()) {
                postcard.withString(key, value);
            } else if (typeDef == TypeKind.PARCELABLE.ordinal()) {
                // TODO : How to description parcelable value with string?
            } else if (typeDef == TypeKind.OBJECT.ordinal()) {
               //Explains why custom objects can be serialized
                postcard.withString(key, value);
            } else {    // Compatible compiler sdk 1.0.3, in that version, the string type = 18
                postcard.withString(key, value);
            }
        } else {
            postcard.withString(key, value);
        }
    } catch (Throwable ex) {
        logger.warning(Consts.TAG, "LogisticsCenter setValue failed! " + ex.getMessage());
    }
}

So whether the custom object data is raw or string-hosted, wait until the target page to be processed by the deserialization service, and understand why the serialization service is needed for custom object parameters

So far the ARouter process has been basically combed out!

Topics: Java Fragment Android JSON