1. 1 – InterceptorProcessor ARouter to explain

  2. 2 – AutowiredProcessor ARouter interpretation

  3. 3 _routeprocessor ARouter interpretation

  4. 4 _core Arouter explanation

review

After the previous article, we know that the annotation processing after the generated file, let’s review again

  • Annotated by Route produces a class like the following

If it is of type IProvider, a class similar to the following is generated

  • If it is an IInterceptor, it generates a class similar to the following. IInterceptor is also a subclass of IProvider

  • You can use Class for routing because you generate classes like the following, where Path is the fully qualified name of the Class.

  • Collect classes for each group.

ARouter.init

Route initialization

ARouter.init(getApplication());
Copy the code
public static void init(Application application) {
    if(! hasInit) { logger = _ARouter.logger; _ARouter.logger.info(Consts.TAG,"ARouter init start.");
        hasInit = _ARouter.init(application); // initialize _ARouter

        if (hasInit) {
            _ARouter.afterInit();// After initialization, get InterceptorService, and handle intercepts in a unified manner
        }

        _ARouter.logger.info(Consts.TAG, "ARouter init over."); }}Copy the code
protected static synchronized boolean init(Application application) {
    mContext = application;
    LogisticsCenter.init(mContext, executor);
    logger.info(Consts.TAG, "ARouter init success!");
    hasInit = true;
    mHandler = new Handler(Looper.getMainLooper());

    return true;
}
Copy the code

LogisticsCenter.init

private static void loadRouterMap(a) {
    registerByPlugin = false;
    // auto generate register code by gradle plugin: arouter-auto-register
    // looks like below:
    // registerRouteRoot(new ARouter.. Root.. modulejava());
    // registerRouteRoot(new ARouter.. Root.. modulekotlin());
}
Copy the code

Before, in order to collect routing information, the program will scan dex file after starting, but this will affect the speed of the start, but later after arouter- Register plug-in pile processing, will scan jar package meet the conditions of the class, after collection will become the following

 private static void loadRouterMap(a)
  {
    registerByPlugin = false;
    register("com.alibaba.android.arouter.routes.ARouter$$Root$$modulejava");
    register("com.alibaba.android.arouter.routes.ARouter$$Root$$arouterapi");
    register("com.alibaba.android.arouter.routes.ARouter$$Interceptors$$modulejava");
    register("com.alibaba.android.arouter.routes.ARouter$$Providers$$modulejava");
    register("com.alibaba.android.arouter.routes.ARouter$$Providers$$arouterapi");
  }
Copy the code

Take a look at the Warehouse class below, which holds all the routing information.

class Warehouse {
    // Cache route and metas
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    static Map<String, RouteMeta> routes = new HashMap<>();

    // Cache provider
    static Map<Class, IProvider> providers = new HashMap<>();
    static Map<String, RouteMeta> providersIndex = new HashMap<>();

    // Cache interceptor
    static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
    static List<IInterceptor> interceptors = new ArrayList<>();

    static void clear(a) { routes.clear(); groupsIndex.clear(); providers.clear(); providersIndex.clear(); interceptors.clear(); interceptorsIndex.clear(); }}Copy the code

It is simple to register the class we specify.

private static void register(String className) {
    if(! TextUtils.isEmpty(className)) {try{ Class<? > clazz = Class.forName(className); Object obj = clazz.getConstructor().newInstance();// instantiate an object
            if (obj instanceof IRouteRoot) {
                registerRouteRoot((IRouteRoot) obj);
            } else if (obj instanceof IProviderGroup) {
                registerProvider((IProviderGroup) obj);
            } else if (obj instanceof IInterceptorGroup) {
                registerInterceptor((IInterceptorGroup) obj);
            } else {
                logger.info(TAG, "register failed, class name: " + className
                        + " should implements one of IRouteRoot/IProviderGroup/IInterceptorGroup."); }}catch (Exception e) {
            logger.error(TAG,"register class error:"+ className, e); }}}private static void registerRouteRoot(IRouteRoot routeRoot) {
        markRegisteredByPlugin(); // Changes the registerByPlugin to true so that the dex is not scanned later.
        if(routeRoot ! =null) {
            routeRoot.loadInto(Warehouse.groupsIndex); // It is easy to understand by looking at the picture above.}}private static void registerProvider(IProviderGroup providerGroup) {
        markRegisteredByPlugin();
        if(providerGroup ! =null) { providerGroup.loadInto(Warehouse.providersIndex); }}private static void registerInterceptor(IInterceptorGroup interceptorGroup) {
        markRegisteredByPlugin();
        if(interceptorGroup ! =null) { interceptorGroup.loadInto(Warehouse.interceptorsIndex); }}Copy the code

Now that all the routing information has been collected by the loadRouterMap() method, it’s time to route to other classes based on path.

navigation

public <T> T navigation(Class<? extends T> service) {
    return _ARouter.getInstance().navigation(service);
}
Copy the code

In the demo, there is HelloService. There is a direct subclass HelloServiceImpl. If you want to get HelloServiceImpl, This can be obtained using navigation(HelloService.class) or path.

public interface HelloService extends IProvider {
    void sayHello(String name);
}
Copy the code
@Route(path = "/yourservicegroupname/hello")
public class HelloServiceImpl implements HelloService {
    Context mContext;

    @Override
    public void sayHello(String name) {
        Toast.makeText(mContext, "Hello " + name, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void init(Context context) {
        mContext = context;
        Log.e("testService", HelloService.class.getName() + " has init."); }}Copy the code
protected <T> T navigation(Class<? extends T> service) {
    try {
        Postcard postcard = LogisticsCenter.buildProvider(service.getName());

        // Compatible 1.0.5 compiler sdk.
        // Earlier versions did not use the fully qualified name to get the service
        if (null == postcard) {
            // No service, or this service in old version.
            postcard = LogisticsCenter.buildProvider(service.getSimpleName());
        }

        if (null == postcard) {
            return null;
        }

        // Set application to postcard.
        postcard.setContext(mContext);

        LogisticsCenter.completion(postcard);
        return (T) postcard.getProvider();
    } catch (NoRouteFoundException ex) {
        logger.warning(Consts.TAG, ex.getMessage());
        return null; }}Copy the code

Get the RouteMeta based on the fully qualified name from the HashMap of Warehouse. ProvidersIndex

public static Postcard buildProvider(String serviceName) {
    RouteMeta meta = Warehouse.providersIndex.get(serviceName);

    if (null == meta) {
        return null;
    } else {
        return newPostcard(meta.getPath(), meta.getGroup()); }}Copy the code

The new Postcard contains only path and Group. Other information needs to be supplemented. Get the RouteMeta from the Routes cache based on path, or add it dynamically if it is not in the cache.

If it is in the cache, populate the Postcard with data

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

    // Start with the cache class
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    
    if (null == routeMeta) {
        // Maybe its does't exist, or didn't load.
        // There is no cache
        // It can be added dynamically, but the group must be added first
        if(! Warehouse.groupsIndex.containsKey(postcard.getGroup())) {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()));
                }

                // Dynamic add
                addRouteGroupDynamic(postcard.getGroup(), null);

                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);   // Reload}}else {
        // Cache access to data
        postcard.setDestination(routeMeta.getDestination()); // Route target class
        postcard.setType(routeMeta.getType());  // the RouteType is RouteType, which can be ACTIVITY, SERVICE, PROVIDER, CONTENT_PROVIDER, or FRAGMENT
        postcard.setPriority(routeMeta.getPriority()); / / permission values
        postcard.setExtra(routeMeta.getExtra()); // Additional 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); // fetch from the cache
                if (null == instance) { // There's no instance of this provider
                    // No cache
                    IProvider provider;
                    try {
                        provider = providerMeta.getConstructor().newInstance();
                        provider.init(mContext);
                        Warehouse.providers.put(providerMeta, provider); // Put it in the cache
                        instance = provider;
                    } catch (Exception e) {
                        logger.error(TAG, "Init provider failed!", e);
                        throw new HandlerException("Init provider failed!");
                    }
                }
                postcard.setProvider(instance); // Get the instance object
                postcard.greenChannel();    // Green channel, Provider is not affected by interceptors
                break;
            case FRAGMENT:
                postcard.greenChannel();    // The Fragment type also runs green and is not affected by interceptors
            default:
                break; }}}Copy the code

build

public Postcard build(String path) {
    return _ARouter.getInstance().build(path);
}
Copy the code

We use build for source analysis, first

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); }PathReplaceService = PathReplaceService = PathReplaceService = PathReplaceService
        return build(path, extractGroup(path), true); }}Copy the code

If PathReplaceService is implemented, path can be intercepted and modified.

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);
}
Copy the code
protected Postcard build(String path, String group, Boolean afterReplace) {
    if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        if(! afterReplace) { PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);if (null != pService) {
                path = pService.forString(path);
            }
        }
        return newPostcard(path, group); }}Copy the code
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    
    // We have another chance to modify the Postcard before navigation, whether it needs to be routed
    PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
    if (null! = pretreatmentService && ! pretreatmentService.onPretreatment(context, postcard)) {// If the condition is not met, no navigation is required
        // Pretreatment failed, navigation canceled.
        return null;
    }

    // Set context to postcard.
    postcard.setContext(null == context ? mContext : context);

    try {
        LogisticsCenter.completion(postcard); // Fill the data into the Postcard
    } catch (NoRouteFoundException ex) {
       ...........

        if (null! = callback) { callback.onLost(postcard);// give a callback
        } 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;
    }

    if (null! = callback) { callback.onFound(postcard);// The callback has been found
    }

    // Arouter init will get the InterceptorService
    if(! postcard.isGreenChannel()) {// It must be run in async thread, maybe interceptor cost too mush time made ANR.
        //fragment and provide are green channels
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            /**
             * Continue process
             *
             * @param postcard route meta
             */
            @Override
            public void onContinue(Postcard postcard) {
                _navigation(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(postcard, requestCode, callback);
    }

    return null;
}
Copy the code
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    final Context currentContext = postcard.getContext();

    switch (postcard.getType()) {
        case ACTIVITY:
            // Just as we normally start an Activity with Intetn
            // Build intent
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            intent.putExtras(postcard.getExtras()); // Additional parameters

            // Set flags.
            int flags = postcard.getFlags();
            if (0! = flags) { intent.setFlags(flags); }// Non activity, need FLAG_ACTIVITY_NEW_TASK
            if(! (currentContextinstanceof Activity)) {
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }

            // Set Actions
            String action = postcard.getAction();
            if(! TextUtils.isEmpty(action)) { intent.setAction(action); }// Navigation in main looper.
            runInMainThread(new Runnable() {
                @Override
                public void run(a) { startActivity(requestCode, currentContext, intent, postcard, callback); }});break;
        case PROVIDER:
            return postcard.getProvider();// If provide, return between
        case BOARDCAST:
        case CONTENT_PROVIDER:
        caseFRAGMENT: 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;
}
Copy the code

InterceptorService

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

We initialize the interceptors in the child thread first, and execute them in sequence. Because there may be time-consuming operations in the interception method, which may cause ANR, we put them in the child thread to execute interception operations.

@Override
public void init(final Context context) {
    LogisticsCenter.executor.execute(new Runnable() {
        @Override
        public void run(a) {
            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);
                        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) { interceptorInitLock.notifyAll(); }}}}); }Copy the code

@Override
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
    if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {

        checkInterceptorsInitStatus();

        if(! interceptorHasInit) { callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
            return;
        }

        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);
                    if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn't return anythings.
                        callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                    } else if (null! = postcard.getTag()) {// Maybe some exception in the tag.
                        callback.onInterrupt((Throwable) postcard.getTag());
                    } else{ callback.onContinue(postcard); }}catch(Exception e) { callback.onInterrupt(e); }}}); }else{ callback.onContinue(postcard); }}Copy the code
 private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
        if (index < Warehouse.interceptors.size()) {
            // Our custom interceptor
            IInterceptor iInterceptor = Warehouse.interceptors.get(index);
            iInterceptor.process(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    // Last interceptor excute over with no exception.
                    counter.countDown();
                    // The process is complete and passes to the next interceptor
                    _execute(index + 1, counter, postcard);  // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
                }

                @Override
                public void onInterrupt(Throwable exception) {
                    // Last interceptor execute over with fatal exception.

                    postcard.setTag(null == exception ? new HandlerException("No message.") : exception);    // save the exception message for backup.
                    counter.cancel();
                    // Be attention, maybe the thread in callback has been changed,
                    // then the catch block(L207) will be invalid.
                    // The worst is the thread changed to main thread, then the app will be crash, if you throw this exception!
// if (! Looper.getMainLooper().equals(Looper.myLooper())) { // You shouldn't throw the exception if the thread is main thread.
// throw new HandlerException(exception.getMessage());
/ /}}}); }}Copy the code

inject

Dependency injection, the Autowired annotated class, will automatically generate a class like the following. Get the Test3Activity class name and append ‘ARouter$$Autowired’ to the class name

Test3Activity ARouter$$Autowired Instantiates this object and calls the Inject method in this object. The parameter is the Test3Activity object.

Will call to inject

public void inject(Object thiz) {
    _ARouter.inject(thiz);
}
Copy the code
static void inject(Object thiz) {
    AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());
    if (null != autowiredService) {
        autowiredService.autowire(thiz);
    }
}
Copy the code
@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<>(50);
        blackList = new ArrayList<>();
    }

    @Override
    public void autowire(Object instance) {
        doInject(instance, null);
    }

    /**
     * Recursive injection
     *
     * @param instance who call me.
     * @param parent   parent of me.
     */
    private void doInject(Object instance, Class
        parent) { Class<? > clazz =null == parent ? instance.getClass() : parent;

        ISyringe syringe = getSyringe(clazz);
        if (null! = syringe) { syringe.inject(instance); } Class<? > superClazz = clazz.getSuperclass();// has parent and its not the class of framework.
        if (null! = superClazz && ! superClazz.getName().startsWith("android")) { doInject(instance, superClazz); }}private ISyringe getSyringe(Class
        clazz) {
        String className = clazz.getName();

        try {
            if(! blackList.contains(className)) { ISyringe syringeHelper = classCache.get(className);if (null == syringeHelper) {  // No cache.
                    syringeHelper = (ISyringe) Class.forName(clazz.getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();
                }
                classCache.put(className, syringeHelper);
                returnsyringeHelper; }}catch (Exception e) {
            blackList.add(className);    // This instance need not autowired.
        }

        return null; }}Copy the code