Arouter从使用到原理

303 阅读11分钟

Arouter是一款Alibaba出品的优秀的路由框架,它通过Apt技术生成保存路径(路由path)和被注解(@Router)的组件类的映射关系的类,利用这些保存了映射关系的类,Arouter根据用户的请求(Postcard)寻找到要跳转的目标地址(class),使用Intent跳转。

使用: 1 在gradle文件添加依赖,如果是java则添加

image.png

在公共的modlue加上

api 'com.alibaba:arouter-api:1.5.2'

在各个需跳转modlue里面加上:

image.png

如果是kotlin: kapt 'com.alibaba:arouter-compiler:1.5.2' android { defaultConfig { kapt { arguments { arg("AROUTER_MODULE_NAME", project.getName()) } } } }

然后在Application的onCreate里面初始化:

image.png

定义path,也就是凭证。

image.png

根据path启动对应的Activity,也就是获取。

image.png

其中Arouter.init(this)就是注入的过程,注入的key就是通过@Route(path)定义的path,然后就拿着path去调用navigation()来获取并启动对应的Activity了。

原理解析: 编译时干的事-生成中间代码。

APT: Annotation Processing Tool注解处理器,是一种处理注解的工具,它是用来在编译时扫描和处理注解的,注解处理器以Java代码(或者编译过的字节码)作为输入,生成JAVA文件作为输出。Arouter中通过注解生成相关路由信息类。

所有当我们在gradle中添加了Arouter的依赖后,那么在编译时就会在对应的module的

image.png

下生成com.alibaba.android.arouter.routes目录,Arouter生成的代码都在这里,比如:

image.png

image.png

image.png

这些是在login模块生成的build中生成的,也就是说这些代码对于app模块来说,是不可达的。

上述就是编译所做的事。下面来看运行时所做的一些工作。

运行时做的工作 现在我们已经在

image.png

可能as版本不一样生成的目录不一样。

下面开始来分析代码流程:

image.png

image.png

最终调用了这个方法,我们分析下:

/**
 * LogisticsCenter init, load all metas in memory. Demand initialization
 */
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    mContext = context;
    executor = tpe;

    try {
        long startInit = System.currentTimeMillis();
        //load by plugin first
        loadRouterMap();
        if (registerByPlugin) {
            logger.info(TAG, "Load router map by arouter-auto-register plugin.");
        } else {
            Set<String> routerMap;

            // 如果是debugable或者更新了APP的版本
            if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                // 这里会获取所有"com.alibaba.android.arouter.rotues"目录下的class文件
                routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                if (!routerMap.isEmpty()) {//这里缓存到sp里面去,方便下次获取
                    context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                }
                //将app的版本号缓存到sp,方便下次使用
                PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
            } else {
                logger.info(TAG, "Load router map from cache.");
                //如果app版本号没更新,并且没有开启debug模式,则从缓存中取出之前缓存的所有class。
                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();
//遍历刚刚拿到的所有类名,并且反射调用它们的loadInto()方法,那么login模块中的那些生成的类,它们的loadinto()就被调用了,并且注入到参数里面了。
            for (String className : routerMap) {
            //拼接的字符串其实就是"com.alibaba.android.arouter.routes.ARouter&&Root",这个就是编译时生成的那个"ARouter$$Root$$app"的前缀
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    // This one of root elements, load root.
                    //这就调用了它的loadInto(map),也就是map.put("login", ARouter$$Group$$login.class);这个键值对注入了Warehouse.groupIndex里面。
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);          //这个是"com.alibaba.android.arouter.routes.ARouter$$Interceptors"
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    // Load interceptorMeta
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);    //这个是"com.alibaba.android.arouter.routes.ARouter$$Providers"
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    // Load providerIndex
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }
        }

        logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");

        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() + "]");
    }
}

上述一共做了几步操作: 1 获取所有"com.alibaba.android.arouter.routes"目录下的类名,这一步有个缓存操作。 2 遍历所有获取到的类名,然后调用它们的loadInto(map)方法。 3 调用loadInto(map)的结果就是以键值对的方式将它们存入Warehouse.groupsIndex里面。

下面来看下Warehouse的代码:

class Warehouse {
    // Cache route and metas
    
    //注释1
    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() {
        routes.clear();
        groupsIndex.clear();
        providers.clear();
        providersIndex.clear();
        interceptors.clear();
        interceptorsIndex.clear();
    }
}

上面注释1处:

image.png

image.png

正好相对应。

接下来我们来看下获取所有"com.alibaba.android.arouter.routes"目录下的class文件路径的逻辑。

/**
 * 通过指定包名,扫描包下面包含的所有的ClassName
 *
 * @param context     U know
 * @param packageName 包名
 * @return 所有class的集合
 */
 //我们传进来的packageName是"com.alibaba.android.arouter.routes"
public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
    final Set<String> classNames = new HashSet<>();
    //获取所有dex文件的路径。下面会详细分析
    List<String> paths = getSourcePaths(context);
    final CountDownLatch parserCtl = new CountDownLatch(paths.size());
    //遍历所有dex文件的路径
    for (final String path : paths) {
        DefaultPoolExecutor.getInstance().execute(new Runnable() {
            @Override
            public void run() {
                DexFile dexfile = null;

                try { //是否是".zip"文件
                    if (path.endsWith(EXTRACTED_SUFFIX)) {
                        //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache" //如果是.zip文件,就调用loadDex来加载
                        dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                    } else {
                    //否则,就直接根据路径创建即可
                        dexfile = new DexFile(path);
                    }
                    //遍历dexfile下面的元素
                    Enumeration<String> dexEntries = dexfile.entries();
                    while (dexEntries.hasMoreElements()) {
                        String className = dexEntries.nextElement();
                        //如果是以"com.alibaba.android.arouter.routes"开头,就添加
                        if (className.startsWith(packageName)) {
                            classNames.add(className);
                        }
                    }
                } catch (Throwable ignore) {
                    Log.e("ARouter", "Scan map file in dex files made error.", ignore);
                } finally {
                    if (null != dexfile) {
                        try {
                            dexfile.close();
                        } catch (Throwable ignore) {
                        }
                    }

                    parserCtl.countDown();
                }
            }
        });
    }

    parserCtl.await();

    Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
    return classNames;
}

上述代码:

1 获取所有Dex文件路径,并且遍历创建DexFile 2 遍历DexFile,并且将所有以"com.alibaba.android.arouter.routes"文件开头的添加,然后返回。

我们又知道,Arouter在编译时生成的文件都是以"com.alibaba.android.arouter.routes"为前缀的,所以这个函数的结果就是获取所有Arouter编译时生成的文件名。 然后我们来看,怎么获取所有DexFile文件路径:

/**
     * get all the dex path
     *
     * @param context the application context
     * @return all the dex path
     * @throws PackageManager.NameNotFoundException
     * @throws IOException
     */
    public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
        ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
        File sourceApk = new File(applicationInfo.sourceDir);

        List<String> sourcePaths = new ArrayList<>();
        //添加apk的默认路径,可以理解为apk文件的路径
        sourcePaths.add(applicationInfo.sourceDir); //add the default apk path

        //the prefix of extracted file, ie: test.classes
        //EXTRACTED_NAME_EXT就是.classed,所以这个结果类似于"test.classes"
        String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;

//        如果VM已经支持了MultiDex,就不要去Secondary Folder加载 Classesx.zip了,那里已经么有了
//        通过是否存在sp中的multidex.version是不准确的,因为从低版本升级上来的用户,是包含这个sp配置的
        if (!isVMMultidexCapable()) {
            //the total dex numbers
            //获取所有Dex文件的总数
            int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);
            File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
            //遍历获取路径
            for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
                //for each dex file, ie: test.classes2.zip, test.classes3.zip...
                //EXTRACTED_SUFFIX就是".zip",所以fileName就类似于test.classes2.zip,test.classes3.zip这样。
                String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
                File extractedFile = new File(dexDir, fileName);
                if (extractedFile.isFile()) {
                //添加路径
                    sourcePaths.add(extractedFile.getAbsolutePath());
                    //we ignore the verify zip part
                } else {
                    throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
                }
            }
        }

        if (ARouter.debuggable()) { // Search instant run support only debuggable
            sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));
        }
        return sourcePaths;
    }

上述代码的逻辑就是获取APP对应的所有的Dex文件的路径,android代码打包出来就是一堆Dex文件,可以看成是.class文件的集合,我们写代码到打包的过程就是.java->.class->.dex这样的过程,现在我们需要.dex->.class这样获取我们需要的.class文件。

现在来总结一下运行时的做的一些事。

1 首先调用了Arouter.init(this)方法

2 接着调用了ClassUtils.getFileNameByPackageName()来获取所有"com.alibaba.android.arouter.routes"目录下的dex文件的路径

3 然后遍历这些dex文件获取所有的class文件的完整类名

4 然后遍历所有类名,获取指定前缀的类,通过反射调用它们的loadInto(map)方法,把参数都注入到Warehouse的成员变量里面了。

通俗来说就是app包名->获取.dex->获取.class->找指定前缀的.class->反射调用方法->存入Warehouse中,这个过程就是"注入"过程,Warehouse里面保存了需要的key和.class。接着来看获取的过程。

调用时候做的事

ARouter.getInstance().build(RoutePath.PATH_ACTIVITY_LOGIN).navigation();

下面来一步一步分析:

image.png

image.png

/**
 * Extract the default group from path.
 */
private String extractGroup(String path) {
    if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
        throw new HandlerException(Consts.TAG + "Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!");
    }

    try {
    //获取group名字,结果就是"login"
        String defaultGroup = path.substring(1, path.indexOf("/", 1));
        if (TextUtils.isEmpty(defaultGroup)) {
            throw new HandlerException(Consts.TAG + "Extract the default group failed! There's nothing between 2 '/'!");
        } else {
            return defaultGroup;
        }
    } catch (Exception e) {
        logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage());
        return null;
    }
}
/**
 * Build postcard by path and group
 */
 //这里我们已经知道参数是("/login/LoginActivity","login")了
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);
            }
        }
        //最后创建一个Postcard类。
        return new Postcard(path, group);
    }
}
public Postcard(String path, String group, Uri uri, Bundle bundle) {
//保存了path,就是"/login/LoginActivity"
    setPath(path);
    //保存了group,就是"login"
    setGroup(group);
    //uri是Null
    setUri(uri);
    //创建个Bundle()
    this.mBundle = (null == bundle ? new Bundle() : bundle);
}

现在可以知道,Arouter.getInstance().build(path);最终是创建个Postcard,保存了path和group,然后我们看下Postcard和navigation()函数:

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 Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
    return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
}
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)) {
        // Pretreatment failed, navigation canceled.
        return null;
    }

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

    try {
    //重要函数1
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        logger.warning(Consts.TAG, ex.getMessage());

        if (debuggable()) {
            // Show friendly tips for user.
            runInMainThread(new Runnable() {
                @Override
                public void run() {
                    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);
            }
        }

        return null;
    }

    if (null != callback) {
        callback.onFound(postcard);
    }
    //判断是否需要调用拦截器
    if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
        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 { 
    //重要函数2 
        return _navigation(postcard, requestCode, callback);
    }

    return null;
}

重要函数1:

/**
 * Completion the postcard by route metas
 *
 * @param postcard Incomplete postcard, should complete by this method.
 */
public synchronized static void completion(Postcard postcard) {
    if (null == postcard) {
        throw new NoRouteFoundException(TAG + "No postcard!");
    }
    //先从Warehouse.routes里面获取RouteMeta,此时我们知道是null的

    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {
        // Maybe its does't exist, or didn't load.
        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()));
                }
                //重要函数:3

                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 {
    //这里就是设置一些参数
    //atlas.put("/login/LoginActivity", RouteMeta.build(RouteType.ACTIVITY, LoginActivity.class, "/login/loginactivity", "login", null, -1, -2147483648));
}这里面的一些参数
        //LoginActivity.class
        postcard.setDestination(routeMeta.getDestination());
        //RouteType.ACTIVITY
        postcard.setType(routeMeta.getType());
        //-1
        postcard.setPriority(routeMeta.getPriority());
        //-2147483648
        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
                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());
        }
        //这里就是根据类型执行对应的逻辑,我们类型是RouteType.ACTIVITY,下面没有。
        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) {
                        logger.error(TAG, "Init provider failed!", e);
                        throw new HandlerException("Init provider failed!");
                    }
                }
                postcard.setProvider(instance);
                postcard.greenChannel();    // Provider should skip all of interceptors
                break;
            case FRAGMENT:
                postcard.greenChannel();    // Fragment needn't interceptors
            default:
                break;
        }
    }
}

重要函数3:

public synchronized static void addRouteGroupDynamic(String groupName, IRouteGroup group) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    if (Warehouse.groupsIndex.containsKey(groupName)){
        // If this group is included, but it has not been loaded
        // load this group first, because dynamic route has high priority.
        //反射调用loadInto方法
        atlas.put("/login/LoginActivity", RouteMeta.build(RouteType.ACTIVITY, LoginActivity.class, "/login/loginactivity", "login", null, -1, -2147483648));
} // 直接put了,key是"/login/LoginActivity",跟Postcard的path一样,这下就放在Warehouse.routes里面了,下次就能拿到了。
        Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);
        Warehouse.groupsIndex.remove(groupName);
    }

    // cover old group.
    if (null != group) {
        group.loadInto(Warehouse.routes);
    }
}

重要函数1执行了两次一共:

第一次:我们从Warehouse.groupIndex取出注入时保存的数据,然后loadInto()相关数据到Warehouse.routes里面去

第二次:就是给一些参数赋值,Destination和Type等等。

现在我们的Warehouse.routes有数据了,并且参数postcard有destination和type了,接着执行重要函数2:

private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    final Context currentContext = postcard.getContext();

    switch (postcard.getType()) {
        case ACTIVITY: //这里就是我们的类型
            // Build intent
            //创建Intent,postcard.getDestination()就是LoginActivity.class
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            intent.putExtras(postcard.getExtras());

            // Set flags.
            int flags = postcard.getFlags();
            if (0 != flags) {
                intent.setFlags(flags);
            }

            // Non activity, need FLAG_ACTIVITY_NEW_TASK
            //当我们的context不是Activity类型时,添加一个Flag。
            if (!(currentContext instanceof 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.
            //切换到UI线程去启动Activity。
            runInMainThread(new Runnable() {
                @Override
                public void run() {
                    startActivity(requestCode, currentContext, intent, postcard, callback);
                }
            });

            break;
        case PROVIDER:
            return postcard.getProvider();
        case BOARDCAST:
        case CONTENT_PROVIDER:
        case FRAGMENT:
            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;
}

这个函数大概逻辑就是直接用postcard里面的type去执行对应逻辑,然后执行到了Activity分之,于是就取出destination,也就是LoginActivity.class来启动。

当我们通过path去navigation(),此path="/login/LoginActivity",此时会根据这个path构造一个postCard,其中group="login"(以"/"分割得到的),然后以group(也就是"login")从Warehouse的groupIndex里面获取值,就得到了"ArouterGroupGrouplogin.class"这个class对象,接着使用反射创建了一个实例并调用loadInto()函数,于是执行到了下面的loadInto方法。

public class ARouter$$Group$$login implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    // "/login/LoginActivity"就是我们通过@Route指定的path,后面RouteMeta保存了要启动的组件类型,以及对应的.class文件
    atlas.put("/login/LoginActivity", RouteMeta.build(RouteType.ACTIVITY, LoginActivity.class, "/login/loginactivity", "login", null, -1, -2147483648));
  }
}

此后,我们的Warehouse的routes里面就有值了。 接着根据postCard的类型去进行switch-case,当case到RouteType.ACTIVITY(还有其他类型PROVIDER)时,就进行Activity的启动,此时我们有了对应Activity的class。

image.png

image.png

image.png

这个接口是我们如果需要暴露给其他模块调用接口时候需要继承的。

总结

1 在编译时通过Apt技术来给代码含有@Route(path)注解的类生成中间代码 2 Arouter.init(context)初始化时,就是进行注入操作,key就是path。 3 ARouter.getInstance().build(RoutePath.PATH_ACTIVITY_LOGIN).navigation();时候用path来进行获取操作,最终获取到启动的Activity的class对象。