美文网首页
ARouter源码分析上

ARouter源码分析上

作者: 就叫汉堡吧 | 来源:发表于2021-08-04 19:06 被阅读0次
    • What is ARouter

      A framework for assisting in the renovation of Android app componentization
      

      官方介绍只有简短的一句话总结,一套协助Android app组件化革新的框架。

    • Why to use it

      即便是组件化,我们其实也可以用startActivity的方式来进行跳转,那ARouter又有何特殊之处呢?

      1. 传统的方式,不管是url传参还是bundle传参都较为繁琐,需要把参数先设置到url或者bundle,然后在跳转页通过getIntent方法来获取传参,ARouter把这部分工作封装了起来。通过链式调用设置参数,通过Autowired注解获取参数。
      2. 支持组件化,ARouter的组件化在于不需要module间相互依赖就可以互相跳转。
      3. 支持interceptor,可以通过interceptor做一些跳转前的逻辑,比如需要登录状态才能访问的页面。
      4. 支持依赖注入,通过AOP思想把注解映射成路由类。
      5. 支持Instant Run方式编译的apk。
      6. 支持MultiDex。
      7. 支持业务类Service,通过Route和Autowired注解和Service类联系起来。
    • How to use it & Why it work

      • 依赖

        每一个需要使用ARouter的module下的build.gradle中添加下面配置(可以把这块抽出来,通过apply from: '../xx.gradle'去引用):

        //如果含有kotlin代码,需要添加一下kotlin plugin
        apply plugin: 'kotlin-kapt'
        
        //使用ARouter auto-register自动扫描路由信息,非必选,通过gradle注册,性能更优
        apply plugin: 'com.alibaba.arouter'
        
        android {
            defaultConfig {
                ...
                javaCompileOptions {
                    annotationProcessorOptions {
                        arguments = [AROUTER_MODULE_NAME: project.getName()]
                    }
                }
            }
        }
        
        //使用ARouter auto-register自动扫描路由信息,非必选,通过gradle注册,性能更优
        buildscript {
            repositories {
                jcenter()
            }
        
            dependencies {
                //ARouter auto-register,Replace with the latest versios
                classpath "com.alibaba:arouter-register:?"
            }
        }
        
        //如果是包含kotlin类的路由,需要添加下面代码
        kapt {
            arguments {
                arg("AROUTER_MODULE_NAME", project.getName())
            }
        }
        
        dependencies {
            // Replace with the latest version
            //使用api继承依赖,不需要每个module都添加
            api 'com.alibaba:arouter-api:?'
            //这个不能使用api依赖继承,所以每个module都要添加
            annotationProcessor 'com.alibaba:arouter-compiler:?'
            //如果是kotlin,需要添加kotlin编译jar包
            kapt 'com.alibaba:arouter-compiler:?'
            ...
        }
        
      • 两种加载方式

        通过Route、Autowired、Interceptor等ARouter注解修饰的类需要被加载到一些Map中,这些Map被放在Warehouse类中:

        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() {
                routes.clear();
                groupsIndex.clear();
                providers.clear();
                providersIndex.clear();
                interceptors.clear();
                interceptorsIndex.clear();
            }
        }
        

        加载的方式有两种,一种是通过plugin来利用gradle的构建自动生成加载代码的方式加载,另一种是通过java代码手动扫描出含有这些注解的类,这也是初始化的两种方式,官方提倡使用register-plugin自动生成性能更好。

      • 初始化

        class MyApp : MultiDexApplication() {
        
            override fun onCreate() {
                super.onCreate()
                if(BuildConfig.DEBUG){
                    ARouter.openLog()
                    ARouter.openDebug()
                }
                ARouter.init(this)
            }
          
            override fun onTerminate() {
                super.onTerminate()
                ARouter.getInstance().destroy()
            }
        }
        

        为了app安全考虑,非debug版本下不要输出log和打开debug模式。

        看一下ARouter的init方法做了什么:

        /**
         * 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的类去做的,Arouter只是它的包装类。__ARouter的init方法返回hasInit标志是否已成功初始化:

        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;
        }
        

        此处又调用了LogisticsCenter.init方法:

        /**
         * 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();
                //billy.qi modified at 2017-12-06
                //load by plugin first
                loadRouterMap();
                if (registerByPlugin) {
                    logger.info(TAG, "Load router map by arouter-auto-register plugin.");
                } else {
                    Set<String> routerMap;
        
                    // It will rebuild router map every times when debuggable.
                    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.
                        routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                        if (!routerMap.isEmpty()) {
                            context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                        }
        
                        PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
                    } else {
                        logger.info(TAG, "Load router map from cache.");
                        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();
        
                    for (String className : routerMap) {
                        if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                            // This one of root elements, load root.
                            ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                        } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                            // Load interceptorMeta
                            ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                        } 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() + "]");
            }
        }
        

        首先调用了loadRouterMap()方法来试图通过arouter-auto-register plugin加载route信息:

        /**
         * arouter-auto-register plugin will generate code inside this method
         * call this method to register all Routers, Interceptors and Providers
         * @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
         * @since 2017-12-06
         */
        private static void loadRouterMap() {
            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());
        }
        

        这个方法里什么也没有,因为这里要交给plugin去注册,默认设置为不通过plugin注册,通过注释可以得知,plugin会把加载逻辑代码动态的生成在这个方法里,至于怎样生成的另起篇文章总结。

        下载arouter-register的source包查看:

        class RouteMethodVisitor extends MethodVisitor {
        
            RouteMethodVisitor(int api, MethodVisitor mv) {
                super(api, mv)
            }
        
            @Override
            void visitInsn(int opcode) {
                //generate code before return
                if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
                    extension.classList.each { name ->
                        name = name.replaceAll("/", ".")
                        mv.visitLdcInsn(name)//类名
                        // generate invoke register method into LogisticsCenter.loadRouterMap()
                        mv.visitMethodInsn(Opcodes.INVOKESTATIC
                                , ScanSetting.GENERATE_TO_CLASS_NAME
                                , ScanSetting.REGISTER_METHOD_NAME
                                , "(Ljava/lang/String;)V"
                                , false)
                    }
                }
                super.visitInsn(opcode)
            }
            @Override
            void visitMaxs(int maxStack, int maxLocals) {
                super.visitMaxs(maxStack + 4, maxLocals)
            }
        }
        

        mv.visitMethodInsn方法就是注入加载代码的地方,采用循环的方式来加载每一个类。ScanSetting.GENERATE_TO_CLASS_NAME是:

        /**
         * The register code is generated into this class
         */
        static final String GENERATE_TO_CLASS_NAME = 'com/alibaba/android/arouter/core/LogisticsCenter'
        

        ScanSetting.REGISTER_METHOD_NAME是:

        /**
         * register method name in class: {@link #GENERATE_TO_CLASS_NAME}
         */
        static final String REGISTER_METHOD_NAME = 'register'
        

        在arouter-api的jar包中找到LogisticsCenter的register方法:

        /**
         * register by class name
         * Sacrificing a bit of efficiency to solve
         * the problem that the main dex file size is too large
         * @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
         * @param className class name
         */
        private static void register(String className) {
            if (!TextUtils.isEmpty(className)) {
                try {
                    Class<?> clazz = Class.forName(className);
                    Object obj = clazz.getConstructor().newInstance();
                    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);
                }
            }
        }
        

        三个方法分别是注册Root(页面)、Interceptor、IProvider(Service)的逻辑:

        /**
         * method for arouter-auto-register plugin to register Routers
         * @param routeRoot IRouteRoot implementation class in the package: com.alibaba.android.arouter.core.routers
         * @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
         * @since 2017-12-06
         */
        private static void registerRouteRoot(IRouteRoot routeRoot) {
            markRegisteredByPlugin();
            if (routeRoot != null) {
                routeRoot.loadInto(Warehouse.groupsIndex);
            }
        }
        
        /**
         * method for arouter-auto-register plugin to register Interceptors
         * @param interceptorGroup IInterceptorGroup implementation class in the package: com.alibaba.android.arouter.core.routers
         * @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
         * @since 2017-12-06
         */
        private static void registerInterceptor(IInterceptorGroup interceptorGroup) {
            markRegisteredByPlugin();
            if (interceptorGroup != null) {
                interceptorGroup.loadInto(Warehouse.interceptorsIndex);
            }
        }
        
        /**
         * method for arouter-auto-register plugin to register Providers
         * @param providerGroup IProviderGroup implementation class in the package: com.alibaba.android.arouter.core.routers
         * @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
         * @since 2017-12-06
         */
        private static void registerProvider(IProviderGroup providerGroup) {
            markRegisteredByPlugin();
            if (providerGroup != null) {
                providerGroup.loadInto(Warehouse.providersIndex);
            }
        }
        

        markRegisteredByPlugin是修改registerByPlugin标志位:

        /**
         * mark already registered by arouter-auto-register plugin
         * @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
         * @since 2017-12-06
         */
        private static void markRegisteredByPlugin() {
            if (!registerByPlugin) {
                registerByPlugin = true;
            }
        }
        

        可以看到,对于不同类型的route类分别保存到Warehouse中对应的Map中,注意这里添加的只是类,不是跳转的所有信息。

        回到init方法,判断registerByPlugin是否为true来决定是否手动加载,如果没有设置register-plugin,则会走到else分支,这里就是手动代码检索路由信息的地方:

        if (ARouter.debuggable() || PackageUtils.isNewVersion(context))判断如果是debug模式或者是新版本更新的话就重新检索路由信息,核心代码就是ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE),ROUTE_ROOT_PACKAGE是:

        public static final String ROUTE_ROOT_PAKCAGE = "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<>();
        
            List<String> paths = getSourcePaths(context);
            final CountDownLatch parserCtl = new CountDownLatch(paths.size());
        
            for (final String path : paths) {
                DefaultPoolExecutor.getInstance().execute(new Runnable() {
                    @Override
                    public void run() {
                        DexFile dexfile = null;
        
                        try {
                            if (path.endsWith(EXTRACTED_SUFFIX)) {
                                //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
                                dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                            } else {
                                dexfile = new DexFile(path);
                            }
        
                            Enumeration<String> dexEntries = dexfile.entries();
                            while (dexEntries.hasMoreElements()) {
                                String className = dexEntries.nextElement();
                                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;
        }
        

        这里的工作就是多线程读取所有的dex文件,逐个判断里面的className,如果className是以com.alibaba.android.arouter.routes开头的则存入classNames集合,最终会返回所有com.alibaba.android.arouter.routes开头的类名。

        注意在最前面有一个getSourcePaths方法来获取apk的所有dex文件的位置,然后从这些位置读取dex文件:

         /**
             * 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<>();
                sourcePaths.add(applicationInfo.sourceDir); //add the default apk path
        
                //the prefix of extracted file, ie: 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
                    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...
                        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;
            }
        

        isVMMultidexCapable判断是否JVM支持MultiDex了,就是判断JVM的版本(2.1之后的支持):

        /**
         * Identifies if the current VM has a native support for multidex, meaning there is no need for
         * additional installation by this library.
         *
         * @return true if the VM handles multidex
         */
        private static boolean isVMMultidexCapable() {
            boolean isMultidexCapable = false;
            String vmName = null;
        
            try {
                if (isYunOS()) {    // YunOS需要特殊判断
                    vmName = "'YunOS'";
                    isMultidexCapable = Integer.valueOf(System.getProperty("ro.build.version.sdk")) >= 21;
                } else {    // 非YunOS原生Android
                    vmName = "'Android'";
                    String versionString = System.getProperty("java.vm.version");
                    if (versionString != null) {
                        Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString);
                        if (matcher.matches()) {
                            try {
                                int major = Integer.parseInt(matcher.group(1));
                                int minor = Integer.parseInt(matcher.group(2));
                                isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR)
                                        || ((major == VM_WITH_MULTIDEX_VERSION_MAJOR)
                                        && (minor >= VM_WITH_MULTIDEX_VERSION_MINOR));
                            } catch (NumberFormatException ignore) {
                                // let isMultidexCapable be false
                            }
                        }
                    }
                }
            } catch (Exception ignore) {
        
            }
        
            Log.i(Consts.TAG, "VM with name " + vmName + (isMultidexCapable ? " has multidex support" : " does not have multidex support"));
            return isMultidexCapable;
        }
        

        取到routeMap之后会把它存到SP中,最后遍历routeMap,根据前缀Root、Interceptors、Providers的不同分别存入Warehouse中的对应xxxIndex的map中,这一步和自动加载是一样的。

        至此,初始化工作就完成了。

      • 路由名称注意

        Route注解指定的路由path中格式为/groupName/elseName...,至少含有两个‘/’且groupName不能为空。因为ARouter会自动生成以groupName为后缀的类,可以在build的generated包下找到。

        arouter-api:xxx的这个jar包下的routes包中有三个已经存在的类:

        ARouter$$Group$$arouter:

        public class ARouter$$Group$$arouter implements IRouteGroup {
            public ARouter$$Group$$arouter() {
            }
        
            public void loadInto(Map<String, RouteMeta> atlas) {
                atlas.put("/arouter/service/autowired", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", (Map)null, -1, -2147483648));
                atlas.put("/arouter/service/interceptor", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", (Map)null, -1, -2147483648));
            }
        }
        

        ARouter$$Providers$$arouterapi:

        public class ARouter$$Providers$$arouterapi implements IProviderGroup {
            public ARouter$$Providers$$arouterapi() {
            }
        
            public void loadInto(Map<String, RouteMeta> providers) {
                providers.put("com.alibaba.android.arouter.facade.service.AutowiredService", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", (Map)null, -1, -2147483648));
                providers.put("com.alibaba.android.arouter.facade.service.InterceptorService", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", (Map)null, -1, -2147483648));
            }
        }
        

        ARouter$$Root$$arouterapi:

        public class ARouter$$Root$$arouterapi implements IRouteRoot {
            public ARouter$$Root$$arouterapi() {
            }
        
            public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
                routes.put("arouter", arouter.class);
            }
        }
        

        在build中还会自动生成ARouter$$Providers$$moduleName、ARouter$$Root$$moduleName,moduleName是模块的名字。以module是app为例,ARouter$$Root$$app为:

        public class ARouter$$Root$$app implements IRouteRoot {
          @Override
          public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
            routes.put("john", ARouter$$Group$$john.class);
            routes.put("rxseriers", ARouter$$Group$$rxseriers.class);
          }
        }
        

        前面分析的初始化中会调用到loadInto方法,这里就会把ARouter$$Group$$john.class和ARouter$$Group$$rxseriers.class保存到Warehouse.groupsIndex中。

        而Route注解生成的类的类名格式为ARouter$$Group$$groupName,知道了这些之后,我们可以得出结论,路由中的groupName不能是“arouter”,倘若相同,自动生成的class名就会和routes包下的默认存在的class同名,那么routes.put("arouter", ARouter$$Group$$john.class)传入的class就会被routes中的同名class替代,从而导致找不到自定义的路由类的问题。

      • 跳转

        Example:

        ARouter.getInstance().build(Const.LOGIN_ACTIVITY_PATH).withString("name","John").navigation()
        

        通过单例模式获取ARouter实例:

        public static ARouter getInstance() {
            if (!hasInit) {
                throw new InitException("ARouter::Init::Invoke init(context) first!");
            } else {
                if (instance == null) {
                    synchronized (ARouter.class) {
                        if (instance == null) {
                            instance = new ARouter();
                        }
                    }
                }
                return instance;
            }
        }
        

        可以看到实例化之前必须先初始化。

        build有多个重载方法:

        /**
         * 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), true);
            }
        }
        
        /**
         * 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);
            }
        }
        
        /**
         * Build postcard by path and group
         */
        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 new Postcard(path, group);
            }
        }
        
        }
        

        其实就是两种方式,一个参数是String类型的path,这就是Route注解的path属性的值,另一个参数是Uri类型,这种形式最终使用的是Uri的path值,无论哪种形式,都必须是/groupName/???的格式,extractGroup方法决定了这样:

        /**
         * 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 {
                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(明信片)对象,接下来是调用它的navigation方法。

        navigation方法同样有多个重载方法,所有可传参数有:Activity mContext, int requestCode, NavigationCallback callback。mContext若不传就是构建时传入的application上下文,requestCode是用以forResult方式打开Activity的需求,可以传一个callback以自定义处理一些特殊情况。

        最终会调用到_ARouter的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)) {
                // Pretreatment failed, navigation canceled.
                return null;
            }
        
            try {
                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(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;
        }
        

        这里只看核心代码,LogisticsCenter.completion(postcard)用于完善和检查Postcard的必要信息,暂时先不看,先看方法最终的逻辑:

        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:
                    // 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);
                    }
        
                    // Set Actions
                    String action = postcard.getAction();
                    if (!TextUtils.isEmpty(action)) {
                        intent.setAction(action);
                    }
        
                    // Navigation in main looper.
                    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;
        }
        
        /**
         * Start activity
         *
         * @see ActivityCompat
         */
        private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
            if (requestCode >= 0) {  // Need start for result
                if (currentContext instanceof Activity) {
                    ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
                } else {
                    logger.warning(Consts.TAG, "Must use [navigation(activity, ...)] to support [startActivityForResult]");
                }
            } else {
                ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
            }
        
            if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
                ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
            }
        
            if (null != callback) { // Navigation over.
                callback.onArrival(postcard);
            }
        }
        

        可以看到,最底层也是调用了startActvity或startActivityForResult(requestCode大于0且context是Activity)来处理回调,动画和NavigationCallback的处理也是在这里。

        回过头来我们看一下LogisticsCenter.completion(postcard)方法,它只在navigation方法里面最开始调用:

        public synchronized static void completion(Postcard postcard) {
            if (null == postcard) {
                throw new NoRouteFoundException(TAG + "No postcard!");
            }
        
            RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
            if (null == routeMeta) {    // Maybe its does't exist, or didn't load.
                Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
                if (null == groupMeta) {
                    throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
                } else {
                    // 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()));
                        }
        
                        IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                        iGroupInstance.loadInto(Warehouse.routes);
                        Warehouse.groupsIndex.remove(postcard.getGroup());
        
                        if (ARouter.debuggable()) {
                            logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                        }
                    } catch (Exception e) {
                        throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                    }
        
                    completion(postcard);   // Reload
                }
            } else {
                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
                        for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                            setValue(postcard,
                                    params.getValue(),
                                    params.getKey(),
                                    resultMap.get(params.getKey()));
                        }
        
                        // Save params name which need auto inject.
                        postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
                    }
        
                    // Save raw uri
                    postcard.withString(ARouter.RAW_URI, rawUri.toString());
                }
        
                switch (routeMeta.getType()) {
                    case PROVIDER:  // if the route is provider, should find its instance
                        // Its provider, so it must implement IProvider
                        Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                        IProvider instance = Warehouse.providers.get(providerMeta);
                        if (null == instance) { // There's no instance of this provider
                            IProvider provider;
                            try {
                                provider = providerMeta.getConstructor().newInstance();
                                provider.init(mContext);
                                Warehouse.providers.put(providerMeta, provider);
                                instance = provider;
                            } catch (Exception e) {
                                throw new HandlerException("Init provider failed! " + e.getMessage());
                            }
                        }
                        postcard.setProvider(instance);
                        postcard.greenChannel();    // Provider should skip all of interceptors
                        break;
                    case FRAGMENT:
                        postcard.greenChannel();    // Fragment needn't interceptors
                    default:
                        break;
                }
            }
        }
        

        还记得之前init的时候已经把自动生成的Root类保存进了Warehouse.groupsIndex了吧,这里首先会从Warehouse.groupsIndex中取RouteMeta,如果没取到说明之前没有跳转过,则从Warehouse.groupsIndex中取得之前存入的class,取到之后调用groupMeta.getConstructor().newInstance()反射生成实例,调用它的loadInto(Warehouse.routes)方法,这里我定义的是@Route(path="/john/"),所以生成的是ARouter$$Group$$john:

        /**
         * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
        public class ARouter$$Group$$john implements IRouteGroup {
          @Override
          public void loadInto(Map<String, RouteMeta> atlas) {
            atlas.put("/john/", RouteMeta.build(RouteType.ACTIVITY, LoginActivity.class, "/john/", "john", new java.util.HashMap<String, Integer>(){{put("name", 8); put("age", 3); }}, -1, -2147483648));
          }
        }
        

        那么loadInto方法就是这里的,做的工作就是把生成的RouteMeta对象保存进Warehouse.routes,后面再次递归调用completion方法,再次进来,Warehouse.routes内就含有了RouteMeta对象。

        我们看一下自动生成的RouteMeta都含有什么关键信息。

        首先第一个参数RouteType,标志跳转目标的类型:

        public enum RouteType {
            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");
        
            int id;
            String className;
        
            public int getId() {
                return id;
            }
        
            public RouteType setId(int id) {
                this.id = id;
                return this;
            }
        
            public String getClassName() {
                return className;
            }
        
            public RouteType setClassName(String className) {
                this.className = className;
                return this;
            }
        
            RouteType(int id, String className) {
                this.id = id;
                this.className = className;
            }
        
            public static RouteType parse(String name) {
                for (RouteType routeType : RouteType.values()) {
                    if (routeType.getClassName().equals(name)) {
                        return routeType;
                    }
                }
        
                return UNKNOWN;
            }
        }
        

        第二个参数是Class类型,是跳转目标类。第三个参数是Route定义的path。第四个参数是group。第五个参数是Map,用于保存Autowired参数类型的,value是整型,表示enum.ordinal:

        public enum TypeKind {
            // Base type
            BOOLEAN,
            BYTE,
            SHORT,
            INT,
            LONG,
            CHAR,
            FLOAT,
            DOUBLE,
        
            // Other type
            STRING,
            SERIALIZABLE,
            PARCELABLE,
            OBJECT;
        }
        

        通过setValue方法赋值到postcard的mBundle:

        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.parseByte(value));
                    } else if (typeDef == TypeKind.SHORT.ordinal()) {
                        postcard.withShort(key, Short.parseShort(value));
                    } else if (typeDef == TypeKind.INT.ordinal()) {
                        postcard.withInt(key, Integer.parseInt(value));
                    } else if (typeDef == TypeKind.LONG.ordinal()) {
                        postcard.withLong(key, Long.parseLong(value));
                    } else if (typeDef == TypeKind.FLOAT.ordinal()) {
                        postcard.withFloat(key, Float.parseFloat(value));
                    } else if (typeDef == TypeKind.DOUBLE.ordinal()) {
                        postcard.withDouble(key, Double.parseDouble(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()) {
                        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());
            }
        }
        

        注意这里setValue使用resultMap.get(params.getKey())来尝试获取Uri中的值,就是Uri中‘?’到‘#’(可以没有)之间的‘xx=xx&xx=xx&...’键值对。

        第六个参数是priority,设置优先级,通过Route注解的priority属性指定,最后一个参数通过Route注解的extras属性指定。

      • 参数传递

        对于基本的类型传递,Postcard中都有对应的方法withXxx()来设置,对于实例化类型,如果实现了Serializable或Parcelable的,可以调用withSerializable和withParcelable方法设置。

        而对于自定义的未实现上述接口的则需要调用withObject方法:

        public Postcard withObject(@Nullable String key, @Nullable Object value) {
            serializationService = ARouter.getInstance().navigation(SerializationService.class);
            mBundle.putString(key, serializationService.object2Json(value));
            return this;
        }
        

        可见需要定义实现自SerializationService的类,譬如:

        @Route(path = AppConstant.ARouteService.SERVICE_SERIALIZATION)
        public class ModuleSerializationRouteServiceImpl implements SerializationService {
        
            private Gson gson;
            private GsonBuilder gsonBuilder;
        
            @Override
            public <T> T json2Object(String input, Class<T> clazz) {
                return null;
            }
        
            @Override
            public String object2Json(Object instance) {
                return gson.toJson(instance);
            }
        
            @Override
            public <T> T parseObject(String input, Type clazz) {
                return gson.fromJson(input, clazz);
            }
        
            @Override
            public void init(Context context) {
                gsonBuilder = new GsonBuilder();
                gsonBuilder.registerTypeAdapter(Class.class, new ClassTypeAdapter());
                gson = gsonBuilder.create();
            }
        }
        

        这里的navigation(Class<? extends T> service)是:

        protected <T> T navigation(Class<? extends T> service) {
            try {
                Postcard postcard = LogisticsCenter.buildProvider(service.getName());
        
                // Compatible 1.0.5 compiler sdk.
                // 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;
                }
        
                LogisticsCenter.completion(postcard);
                return (T) postcard.getProvider();
            } catch (NoRouteFoundException ex) {
                logger.warning(Consts.TAG, ex.getMessage());
                return null;
            }
        }
        

        LogisticsCenter.buildProvider从Warehouse.providersIndex中取得provider:

        public static Postcard buildProvider(String serviceName) {
            RouteMeta meta = Warehouse.providersIndex.get(serviceName);
        
            if (null == meta) {
                return null;
            } else {
                return new Postcard(meta.getPath(), meta.getGroup());
            }
        }
        

        实例化SerializationService和调用它的init的代码就在completion中的:

        switch (routeMeta.getType()) {
            case PROVIDER:  // if the route is provider, should find its instance
                // Its provider, so it must implement IProvider
                Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                IProvider instance = Warehouse.providers.get(providerMeta);
                if (null == instance) { // There's no instance of this provider
                    IProvider provider;
                    try {
                        provider = providerMeta.getConstructor().newInstance();
                        provider.init(mContext);
                        Warehouse.providers.put(providerMeta, provider);
                        instance = provider;
                    } catch (Exception e) {
                        throw new HandlerException("Init provider failed! " + e.getMessage());
                    }
                }
                postcard.setProvider(instance);
                postcard.greenChannel();    // Provider should skip all of interceptors
                break;
                    ...
                  
        }
        

        所以实际上withObject存储的是对象的json字符串。

      • 传参自动注入

        传参自然需要获取参数,上面我们知道最终也是通过intent和bundle传递的参数,那么获取的时候当然也可是通过getIntent().getExtras()方法获取参数值。

        对于ARouter,这些工作我们还可以通过Autowiredz注解自动完成。

        要完成自动注入需要两点,一点是必须在每个需要自动注入的属性上面加Autowired注解,它有三个注解属性,必须要有的是name属性,这个属性的值必须和跳转时传递到bundle的key相同,如果指定了name属性,则字段的名字不必和bundle的key相同,反之则必须相同;第二个是required属性,如果设置为true则表示该Field的值不能为null,否则会crash;最后一个参数是desc,用于描述Field的作用。注意如果是kotlin类,则需要同时加上@JvmField属性,否则不能通过kapt编译。

        另一点是需要在使用自动注入的字段的值之前调用ARouter.getInstance().inject(this)方法,否则使用值为null的实例会抛出NullPointerException异常。

        下面我们看看,对于这些要求ARouter是怎么通过代码设置的。

        首先,含有Autowired注解的类会被自动生成一个名为“类名$$ARouter$$Autowired”的类,那么ARouter.getInstance().inject(this)方法调用了_ARouter的inject方法:

        static void inject(Object thiz) {
            AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());
            if (null != autowiredService) {
                autowiredService.autowire(thiz);
            }
        }
        

        可见,这里会找到path为/arouter/service/autowired的AutowiredService,那这个接口的实现类在哪里,还记得我们初始化的时候for循环loadInto操作吗,就在那个时候前缀为com.alibaba.android.arouter.routes.ARouter$$Providers的类被存到了Warehouse.providesIndex中,所以jar包arouter-api中的ARouter$$Providers$$arouterapi类自然也被加进去了,所以completion中找到这个类调用它的loadInto方法:

        public void loadInto(Map<String, RouteMeta> providers) {
            providers.put("com.alibaba.android.arouter.facade.service.AutowiredService", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", (Map)null, -1, -2147483648));
            providers.put("com.alibaba.android.arouter.facade.service.InterceptorService", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", (Map)null, -1, -2147483648));
        }
        

        所以path为/arouter/service/autowired的就是AutowiredServiceImpl类,接下来调用它的autowire方法,传入的是被跳转页Activity的实例:

        @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);
            }
        
            //这里可见会自动把其父类中的Autowired字段注入
            Class<?> superClazz = clazz.getSuperclass();
            // has parent and its not the class of framework.
            if (null != superClazz && !superClazz.getName().startsWith("android")) {
                doInject(instance, superClazz);
            }
        }
        

        来看看getSyringe方法:

        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);
                    return syringeHelper;
                }
            } catch (Exception e) {
                blackList.add(className);    // This instance need not autowired.
            }
        
            return null;
        }
        

        (ISyringe) Class.forName(clazz.getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance()这行代码就是获取自动生成的名为“类名$$ARouter$$Autowired”的类,这里比如说是LoginActivity类,那么这行代码就会找到LoginActivity$$ARouter$$Autowired:

        public class LoginActivity$$ARouter$$Autowired implements ISyringe {
          private SerializationService serializationService;
        
          @Override
          public void inject(Object target) {
            serializationService = ARouter.getInstance().navigation(SerializationService.class);
            LoginActivity substitute = (LoginActivity)target;
            substitute.name = substitute.getIntent().getExtras() == null ? substitute.name : substitute.getIntent().getExtras().getString("name", substitute.name);
            substitute.howOld = substitute.getIntent().getIntExtra("age", substitute.howOld);
          }
        }
        

        可见这个类实现自ISyringe接口,所以调用ISyringe的inject方法就是调用LoginActivity$$ARouter$$Autowired的inject方法,可以看到,这个方法里给这个Activity实例的相关字段赋值,取值来源也是通过getIntent()。

      • Service Management

        // Declaration interface, other components get the service instance through the interface
        public interface HelloService extends IProvider {
            String sayHello(String name);
        }
        
        @Route(path = "/yourservicegroupname/hello", name = "test service")
        public class HelloServiceImpl implements HelloService {
        
            @Override
            public String sayHello(String name) {
                return "hello, " + name;
            }
        
            @Override
            public void init(Context context) {
        
            }
        }
        

        结合Autowired就可以实现自动注入,原理同上。

      • NavigationCallback

        如果你需要在路由正常跳转或者没找到时做一些回调处理,那么可以在调用navigation方法时传入一个NavigationCallback对象用于处理:

        public interface NavigationCallback {
        
            /**
             * Callback when find the destination.
             *
             * @param postcard meta
             */
            void onFound(Postcard postcard);
        
            /**
             * Callback after lose your way.
             *
             * @param postcard meta
             */
            void onLost(Postcard postcard);
        
            /**
             * Callback after navigation.
             *
             * @param postcard meta
             */
            void onArrival(Postcard postcard);
        
            /**
             * Callback on interrupt.
             *
             * @param postcard meta
             */
            void onInterrupt(Postcard postcard);
        }
        
      • DegradeService

        对于NoRouteFoundException发生时,你可以通过NavigationCallback来处理,但需要每次navigation都需要指定,如果你需要指定一个全局通用的onLost处理,你可以定义一个类实现DegradeService接口,重写onLost方法,这样所有的navigation发生NoRouteFoundException时都会进入到此方法,因为navigation方法中:

        try {
            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;
        }
        

        可见,优先走NavigationCallback的onLost。

      • Interceptor

        navigation方法中,判断postcard.isGreenChannel()是否为false来决定是否需要拦截处理,如果是false,则进行拦截处理:

        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            /**
             * Continue process
             *
             * @param postcard route meta
             */
            @Override
            public void onContinue(Postcard postcard) {
                _navigation(context, postcard, requestCode, callback);
            }
        
            /**
             * Interrupt process, pipeline will be destory when this method called.
             *
             * @param exception Reson of interrupt.
             */
            @Override
            public void onInterrupt(Throwable exception) {
                if (null != callback) {
                    callback.onInterrupt(postcard);
                }
        
                logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
            }
        });
        

        interceptorService是什么时候实例化的呢?答案还是在初始化里面,init方法里,若成功执行完 _ARouter.init(application)方法则会返回true赋值给hasInit,hasInit为true会执行 _ARouter.afterInit()方法:

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

        同AutowiredService一样,初始化时在ARouter$$Providers$$arouterapi的loadInto中path为/arouter/service/interceptor的InterceptorServiceImpl.class被存进了Warehouse.interceptorsIndex,所以这里afterInit会取到InterceptorServiceImpl.class,它的doInterceptions方法如下:

        @Override
        public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
            if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {
        
                checkInterceptorsInitStatus();
        
                if (!interceptorHasInit) {
                    callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
                    return;
                }
        
                LogisticsCenter.executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        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(new HandlerException(postcard.getTag().toString()));
                            } else {
                                callback.onContinue(postcard);
                            }
                        } catch (Exception e) {
                            callback.onInterrupt(e);
                        }
                    }
                });
            } else {
                callback.onContinue(postcard);
            }
        }
        

        checkInterceptorsInitStatus():

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

        只要interceptorHasInit不为true,则会一直阻塞当前线程等待,每10秒重新检查一次,那interceptorHasInit在哪里改变的?还记得在completion里:

        switch (routeMeta.getType()) {
            case PROVIDER:  // if the route is provider, should find its instance
                // Its provider, so it must implement IProvider
                Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                IProvider instance = Warehouse.providers.get(providerMeta);
                if (null == instance) { // There's no instance of this provider
                    IProvider provider;
                    try {
                        provider = providerMeta.getConstructor().newInstance();
                        provider.init(mContext);
                        Warehouse.providers.put(providerMeta, provider);
                        instance = provider;
                    } catch (Exception e) {
                        throw new HandlerException("Init provider failed! " + e.getMessage());
                    }
                }
                postcard.setProvider(instance);
                postcard.greenChannel();    // Provider should skip all of interceptors
                break;
            ...
        }
        

        此时是InterceptorServiceImpl,为什么InterceptorServiceImpl的Type是PROVIDER而不是INTERCEPTOR呢?因为ARouter$$Providers$$arouterapi的loadInto存入时设置的就是PROVIDER:

        providers.put("com.alibaba.android.arouter.facade.service.InterceptorService", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", (Map)null, -1, -2147483648));
        

        所以provider.init()方法就是InterceptorServiceImpl的init方法:

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

        可以看到,这里会把Warehouse.interceptorsIndex里的所以interceptor都执行init方法,然后设置interceptorHasInit为true,因为这里的init方法是新建线程执行的,所以在执行的同时,completion后面的doInterceptions方法也在异步执行,所以现在可以理解checkInterceptorsInitStatus的意义了,它就是在等init完成,调用interceptorInitLock.notifyAll()结束wait,从而继续往下执行doInterceptions方法,如果因为等待时间太长被系统终止则会抛出InterruptedException异常。

        doInterceptions下面的代码就是开启子线程执行_execute方法,interceptorCounter.await同步等待 _executes执行完:

          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) {
                            // Last interceptor excute over with no exception.
                            counter.countDown();
                            _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 excute over with fatal exception.
        
                            postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage());    // 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());
        //                    }
                        }
                    });
                }
            }
        

        这里就是对所有的Interceptor依次执行process方法,假如定义了一个Interceptor:

        @Interceptor(priority = 8, name = "test interceptor")
        public class TestInterceptor implements IInterceptor {
            @Override
            public void process(Postcard postcard, InterceptorCallback callback) {
                ...
                // No problem! hand over control to the framework
                if(LoginUtils.isLogin()){
                        callback.onContinue(postcard);
                }else{
                  callback.onInterrupt(new Throwable("The Route needs login first!"));
                }
                
                // Interrupt routing process
                // callback.onInterrupt(new RuntimeException("Something exception"));      
        
                // The above two types need to call at least one of them, otherwise it will not continue routing
            }
        
            @Override
            public void init(Context context) {
                // Interceptor initialization, this method will be called when sdk is initialized, it will only be called once
            }
        }
        

        这里模拟了跳转需要登录的逻辑,如果已登录则执行跳转,如果未登录则intertupt。在process中调用callback.onContinue或者callback.onInterrupt方法就会回到_execute中处理。

        执行完后判断如果interceptorCounter的getCount大于0说明因Postcard超时时间到期中止,这个超时时间可以设置,默认300秒,如果postcard.getTag()不为null说明有异常产生,若顺利的话会走到callback.onContinue(postcard),就会回调到_ARouter.navigation方法里doIntercaptions传入的匿名InterceptorCallback对象的onContinue方法,从而执行 _navigation方法执行跳转。

        至此,拦截工作就完成了。

      • Priority

        Interceptor的priority在哪设置的?@Interceptor注解修饰的类会被自动创建的ARouter$$Interceptors$$app存进Warehouse.interceptorsIndex中,原理同上:

        public class ARouter$$Interceptors$$app implements IInterceptorGroup {
          @Override
          public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
            interceptors.put(5, LoginInterceptor.class);
          }
        }
        

        因为我创建的Interceptor是:

        @Interceptor(name = "LoginInInterceptor" , priority = 5)
        class LoginInterceptor : IInterceptor {
        

        所以interceptors.put的参数分别是5和LoginInterceptor.class,5是设置的priority,为什么把priority作为key呢?因为Warehouse的interceptorsIndex是一个UniqueKeyTreeMap,它的put方法:

        @Override
        public V put(K key, V value) {
            if (containsKey(key)) {
                throw new RuntimeException(String.format(tipText, key));
            } else {
                return super.put(key, value);
            }
        }
        

        比其父类多的一个操作是首先判断不能添加重复key值的value。然后调用了父类TreeMap的put方法:

        public V put(K key, V value) {
            TreeMapEntry<K,V> t = root;
            if (t == null) {
                compare(key, key); // type (and possibly null) check
        
                root = new TreeMapEntry<>(key, value, null);
                size = 1;
                modCount++;
                return null;
            }
            int cmp;
            TreeMapEntry<K,V> parent;
            // split comparator and comparable paths
            Comparator<? super K> cpr = comparator;
            if (cpr != null) {
                do {
                    parent = t;
                    cmp = cpr.compare(key, t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else
                        return t.setValue(value);
                } while (t != null);
            }
            else {
                if (key == null)
                    throw new NullPointerException();
                @SuppressWarnings("unchecked")
                    Comparable<? super K> k = (Comparable<? super K>) key;
                do {
                    parent = t;
                    cmp = k.compareTo(t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else
                        return t.setValue(value);
                } while (t != null);
            }
            TreeMapEntry<K,V> e = new TreeMapEntry<>(key, value, parent);
            if (cmp < 0)
                parent.left = e;
            else
                parent.right = e;
            fixAfterInsertion(e);
            size++;
            modCount++;
            return null;
        }
        

        可见会按照key值将新元素插入到合适的位置,顺序就是key值越小越靠前,而我们前面看到执行process的时候都是从Warehouse.interceptorsIndex的第0个元素开始,这也就是为什么它文档上写的priority越小优先级越高。

        Route注解里面也可以设置priority,但是我发现它好像没有用到,我唯一能想到的就是可以在NavigationCallback或者Interceptor中通过Postcard对象取到它然后根据它的值来做点什么逻辑,同样extra属性也是这样。

      • Pretreatment Service

        在navigation方法的最前面有这样几行代码:

        PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
        if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
            // Pretreatment failed, navigation canceled.
            return null;
        }
        

        如果说Interceptor是在找到路由后、跳转之前的处理拦截,那PretreatmentService就是在查找路由之前的处理拦截:

        @Route(path = "/xxx/xxx")
        public class PretreatmentServiceImpl implements PretreatmentService {
            @Override
            public boolean onPretreatment(Context context, Postcard postcard) {
                // Do something before the navigation, if you need to handle the navigation yourself, the method returns false
            }
        
            @Override
            public void init(Context context) {
        
            }
        }
        

        官方注释告诉我们,如果你不希望通过ARouter自动跳转的话这里可以返回false,就不会执行查找路由等操作了。

        注意,PretreatmentService只能定义一个,因为:

        public class ARouter$$Providers$$app implements IProviderGroup {
          @Override
          public void loadInto(Map<String, RouteMeta> providers) {
            providers.put("com.alibaba.android.arouter.facade.service.PretreatmentService", RouteMeta.build(RouteType.PROVIDER, MyPreTreatmentService.class, "/lllSS/myPretreatmentService", "lllSS", null, 1, -2147483648));
            providers.put("com.alibaba.android.arouter.facade.service.PretreatmentService", RouteMeta.build(RouteType.PROVIDER, OtherPretreatmentService.class, "/lllSS/otherPretreatmentService", "lllSS", null, 2, -2147483648));
          }
        }
        

        定义多个,key值是一样的,因此只有最后一个会生效。

      • Rewrite URL

        在 _ARouter.build方法里有这样的处理:

        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        if (null != pService) {
            //path是String
            path = pService.forString(path);
            //path是Uri
            //path = pService.forUri(path);
        }
        

        也就是说出build传入的路由地址还可以通过PathReplaceService的api统一处理。

        定义如下:

        // Implement the PathReplaceService interface
        @Route(path = "/xxx/xxx")
        public class PathReplaceServiceImpl implements PathReplaceService {
            /**
            * For normal path.
            *
            * @param path raw path
            */
            String forString(String path) {
                // Custom logic
                return path;
            }
        
        /**
            * For uri type.
            *
            * @param uri raw uri
            */
            Uri forUri(Uri uri) {
                // Custom logic
                return url;
            }
        }
        

        同样,定义多个只会以ARouter$$Providers$$moduleName的loadInto里面put的最后一个key为com.alibaba.android.arouter.facade.service.PathReplaceService的为准。

    • 其他API description

      // Build a standard route request
      ARouter.getInstance().build("/home/main").navigation();
      
      // Build a standard route request, via URI
      Uri uri;
      ARouter.getInstance().build(uri).navigation();
      
      // Build a standard route request, startActivityForResult
      // The first parameter must be Activity and the second parameter is RequestCode
      ARouter.getInstance().build("/home/main", "ap").navigation(this, 5);
      
      // Pass Bundle directly
      Bundle params = new Bundle();
      ARouter.getInstance()
          .build("/home/main")
          .with(params)
          .navigation();
      
      // Set Flag
      ARouter.getInstance()
          .build("/home/main")
          .withFlags();
          .navigation();
      
      // For fragment
      Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
                          
      // transfer the object 
      ARouter.getInstance()
          .withObject("key", new TestObj("Jack", "Rose"))
          .navigation();
      
      // Think the interface is not enough, you can directly set parameter into Bundle
      ARouter.getInstance()
              .build("/home/main")
              .getExtra();
      
      // Transition animation (regular mode)
      ARouter.getInstance()
          .build("/test/activity2")
          .withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom)
          .navigation(this);
      
      // Transition animation (API16+)
      ActivityOptionsCompat compat = ActivityOptionsCompat.
          makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2, 0, 0);
      
      // ps. makeSceneTransitionAnimation, When using shared elements, you need to pass in the current Activity in the navigation method
      
      ARouter.getInstance()
          .build("/test/activity2")
          .withOptionsCompat(compat)
          .navigation();
              
      // Use green channel (skip all interceptors)
      ARouter.getInstance().build("/home/main").greenChannel().navigation();
      
      // Use your own log tool to print logs
      ARouter.setLogger();
      
      // Use your custom thread pool
      ARouter.setExecutor();
      

    相关文章

      网友评论

          本文标题:ARouter源码分析上

          本文链接:https://www.haomeiwen.com/subject/fuauvltx.html