Arouter从使用到原理

·  阅读 1330

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战

前言

凡是A,B无依赖关系,且想要互相通信的,其最基本的原理就是通过一个彼此都依赖的第三方C,不管是binder,socket,file,provider还是EventBus,Arouter等,都是这个原理,如果有人说不,那么要么是杠精,要么是想引人注意,要么就是放弃了治疗。

我们假设现有: app,login,以及common三个module,其中app跟login无关联,并且都依赖于common,其中app module中有个MainActivity,login module想要调用MainActivity,根据上述,只能通过共同依赖的common 或 其他公有依赖来实现。

1 通过common来实现

  • 1 在common中定义一个Map<String,Class<? extends Activity>>
  • 2 在app module中,将MainActivity.class添加到这个map中去:
map.put("main",MainActivity.class)
复制代码
  • 3 在login module中,通过map.get("main")得到MainActivity.class,然后创建Intent来启动MainActivity
Class<? extends Activity> mainActivityClass= map.get("main")
startActivity(new Intent(this,mainActivityClass))
复制代码

核心就两点: 注入获取。说白了就是: 你把自己想让别人用的放进公共仓库中(注入),并提供一个凭证(这里的凭证就是字符串"main"),也就是key,我想要的时候就拿着凭证从公共仓库中取出来(获取),公共仓库就是彼此都能访问的,那肯定是彼此都依赖的。

接着,我们来看Arouter的实现。

2 通过Arouter来实现。

通过Arouter的实现思路跟上述是一样的,只不过Arouter更简单,更省事,更"愚人",它把我们的"注入"和"获取"都实现了(开发把自己当白痴调API就行了),并且还加了很多优化,但是道理都是一样的,我们来看下。

简单使用

1 在gradle文件添加依赖:

如果是java则添加:

api 'com.alibaba:arouter-api:1.5.0'
annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
  
android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}
复制代码

如果是kotlin,则添加:

api 'com.alibaba:arouter-api:1.5.0'
kapt 'com.alibaba:arouter-compiler:1.2.2'

android {
    defaultConfig {
         kapt {
            arguments {
                arg("AROUTER_MODULE_NAME", project.getName())
            }
        }
    }
}
复制代码

java和kotlin的区别,就是kotlin使用kapt关键字,别的都一样。

2 在Application的onCreate()里面初始化

// 初始化(注入)
ARouter.init(this);
复制代码

3 定义path,也就是凭证

@Route(path = "/app/activity_main") // 这个就是凭证,也就是key
public class MainActivity {}
复制代码

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

ARouter.getInstance().build(path).navigation(); // 根据凭证path获取并启动Activity
复制代码

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

上面只是简单示例Arouter的使用,不过多介绍,本篇重点是讲解原理。想看详细使用可以访问: Arouter官网

那么,Arouter的注入是怎么做的呢,获取又是怎么获取的呢,且看下文。

原理剖析

1 编译时干的事 - 生成中间代码

apt技术: apt技术就是 先设定一套代码模版,然后在类加载期间,动态根据指定的代码模版生成一个.java文件,这个文件在运行时可以直接访问,可以看这里加深了解。

如下图:

类加载过程

所以,当我们在gralde中添加了Arouter的依赖后,那么在编译时就会 在对应module的 /build/generated/source/kapt/debug/ 下生成 "com.alibaba.android.arouter.routes" 目录,Arouter生成的代码都放在这里,比如:

// 这一个IRouteRoot,看名字"ARouter$$Root$$app",其中"ARouter$$Root"是前缀,"app"是group名字,也就是path里面以"/"分隔得到的第一个字符串,然后通过"$$"连接,
// 那么这玩意儿的完整类名就是"com.alibaba.android.arouter.routes.ARouter$$Root$$app"
public class ARouter$$Root$$app implements IRouteRoot {

    // 参数是一个map,value类型是 IRouteGroup
    @Override
    public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
        // "app"就是@Route(path = "/app/activity_main") 中的"app",在Arouter中叫做group,是以path中的"/"分隔得到的
        // 这个的value是:ARouter$$Group$$app.class,也就是下面的类
        routes.put("app", ARouter$$Group$$app.class);
    }
}

// 这是一个IRouteGroup,同理,前缀是"ARouter$$Group"
public class ARouter$$Group$$app implements IRouteGroup {
    @Override
    public void loadInto(Map<String, RouteMeta> atlas) {
        // "app/activity_main"就是我们通过@Route指定的path,后面RouteMeta保存了要启动的组件类型,以及对应的.class文件
        // 这个 RouteMeta.build()的参数很重要,后面要用到
        atlas.put("/app/activity_main", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/app/activity_main", "app", null, -1, -2147483648));
    }
}

// RouteMeta.build()方法,参数后面有用
// type就是: RouteType.ACTIVITY,
// destination就是MainActivity.class,
// path就是"/app/activity_main",
// group就是"app"
// paramsType是null
// priority是-1
// extra是-2147483648
public static RouteMeta build(RouteType type, Class<?> destination, String path, String group, Map<String, Integer> paramsType, int priority, int extra) {
    return new RouteMeta(type, null, destination, null, path, group, paramsType, priority, extra);
}
复制代码

注意,上述代码全部是在app module中的build()中生成的。也就是说,这些代码对于login module来说,是完全透明的,不可达的。

而且,我们发现生成的.java文件,都有个共同的前缀"ARouter$$",比如"ARouter$$Root"。又因为它们是在Arouter生成的目录下面,所以它们的完整类名都有个前缀:"com.alibaba.android.arouter.routes.ARouter$$"。好,现在假设编译完了,我们启动app。

2 运行时干的事 - 注入

现在我们已经编译完了,直接点击run启动了app,现在来到了运行时,此时我们已经在/build/generated/source/kapt/debug/ 下生成了 "com.alibaba.android.arouter.routes"目录,并且里面还有一堆Arouter生成的代码。

接下来代码顺序执行,跑到了Application的onCreate()里面,于是就执行了初始化:

// 初始化,此时会调用编译时生成的那一堆代码,来"注入"需要的相关信息
Arouter.init(this)

// 调到了这里
protected static synchronized boolean init(Application application) {
    // 保存了mContext,后面有用
    mContext = application;

    // 初始化
    LogisticsCenter.init(mContext, executor);
    hasInit = true;
    mHandler = new Handler(Looper.getMainLooper());

    return true;
}
复制代码

我们跟着代码,发现最终调了:

// executor是内置的一个线程池
LogisticsCenter.init(mContext, executor);
复制代码

接下来我们就来看这个代码,这里删除了日志以及部分次要逻辑:

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;
        try {
            Set<String> routerMap;
            
            // 如果是debugable()或者更新了app的版本
            if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                // 那么就会重新获取所有的class,所以,当你的Arouter出现了route not found时候,更新版本号 或者 开启Arouter的debug就ok了。

                // 这里会获取所有"com.alibaba.android.arouter.routes"目录下的class文件的类名。
                routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

                // 这里缓存到SharedPreferences里面,方便下次获取。
                if (!routerMap.isEmpty()) {
                    context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                }

                // 将app的版本号缓存到SharedPreferences,方便下次使用。
                PackageUtils.updateVersion(context);
            } else {
                // 如果版本号没有更新,并且没开启debug,则从缓存中取出之前缓存的所有class
                routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
            }

            // 遍历刚刚拿到的所有类名,并且反射调用它们的loadInto()方法,那么app module中的那些生成的类,它们的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)) {
                    // 于是,调用了它的loadInto(map),也就等价于调用了:map.put("app", ARouter$$Group$$app.class),这个键值对 就放在了Warehouse.groupsIndex里面。
                    ((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)) {
                    ((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)) {
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }
        } 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)的结果就是将多有的(key,group.class)类存入Warehouse.groupsIndex里面

我们看下Warehouse的代码:

class Warehouse {
    // Cache route and metas

    // 这个就是我们刚刚注入的那个map,果然接收一个IRouteGroup,对上了。
    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();
    }
}
复制代码

接下来我们看下获取所有"com.alibaba.android.arouter.routes"目录下的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)) {
                        // 如果是.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();

    // 返回
    return classNames;
}
复制代码

上面代码的逻辑很简单:

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

根据上面章节,我们又知道,ARouter在编译时生成的文件都是以"com.alibaba.android.arouter.routes"为前缀的,所以这个函数的结果就是获取所有Arouter编译时生成的文件名。

然后我们来看,怎么获取所有DexFile文件路径:

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

    // EXTRACTED_NAME_EXT 就是 ".classes",所以这个结果类似于 "test.classes"这样
    String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;


    // 如果开启了MultiDex,那么就遍历获取每一个dex文件的路径
    if (!isVMMultidexCapable()) {
        // 获取所有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++) {
            //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());
            } else {
                throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
            }
        }
    }

    // 返回
    return sourcePaths;
}
复制代码

上述代码的逻辑很简单,就是获取app对应的所有的Dex文件的路径,其实Android的代码打包出来就是一堆Dex文件,可以看成是.class文件的合集。也就是说,我们写代码到打包的时候,是:.java -> .class -> .dex这样的包装,而现在,我们要反过来,从 .dex -> .class这样搞回去,当然我们只需要得到.class就足矣。

好,我们再来回顾一下流程:

  • 1 在Application的onCreate()里面我们调用了Arouter.init(this)。
  • 2 接着调用了ClassUtils.getFileNameByPackageNam()来获取所有"com.alibaba.android.arouter.routes"目录下的dex文件的路径。
  • 3 然后遍历这些dex文件获取所有的calss文件的完整类名。
  • 4 然后遍历所有类名,获取指定前缀的类,然后通过反射调用它们的loadInto(map)方法,这是个注入的过程,都注入到参数Warehouse的成员变量里面了。
  • 5 其中就有Arouter在编译时生成的"com.alibaba.android.arouter.routes.ARouter$$Root.ARouter$$Root$$app"类,它对应的代码:<"app", ARouter$$Group$$app.class>就被添加到Warehouse.groupsIndex里面了。

好,现在我们的注入过程就完事了,说白了就是: app包名 -> 获取.dex -> 获取.class -> 找对应的.class -> 反射调用方法 -> 存入Warehouse中,这个过程就是注入,Warehouse就是仓库,里面保存了需要的key和.class,好,我们来看获取的过程

3 调用时干的事 - 获取

调用的代码很简单:

// 这里的path就是我们通过@Route指定的,也就是"/app/activity_main"
ARouter.getInstance().build(path).navigation();
复制代码

其中Arouter.getInstance()很简单,就是个单例,我看看build(path)函数:

public Postcard build(String path) {
    // 就一行代码
    return _ARouter.getInstance().build(path);
}

// 调到了这里
protected Postcard build(String path) {
    if (TextUtils.isEmpty(path)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        //...省略一些代码

        // 到这里,extractGroup(path)是获取group名字的,path是"/app/activity_main",那么group就是app
        return build(path, extractGroup(path));
    }
}


// 获取group名字,参数就是"/app/activity_main"
private String extractGroup(String path) {
    // 校验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名字,结果就是"app"
        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,我们已经知道参数是("/app/activity_main","app")了
protected Postcard build(String path, String group) {
    if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        
        // ... 省略一些代码

        // 直接创建了个玩意,我们就叫它明信片吧
        return new Postcard(path, group);
    }
}

// 来看下明信片的构造
 public Postcard(String path, String group) {
     // 调下面
    this(path, group, null, null);
}

// 最终走到这里
public Postcard(String path, String group, Uri uri, Bundle bundle) {
    // 保存了path,就是"/app/activity_main"
    setPath(path);
    // 保存了group,就是"app"
    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() {
    // 这里的参数传个null
    return navigation(null);
}

// 参数是null
public Object navigation(Context context) {
    // 重载调用,这个context是null
    return navigation(context, null);
}

// 经过一番调用,最终走到这里
public Object navigation(Context context, NavigationCallback callback) {
    // 这里将this作为参数调用下去,this就是Postcard,包含了刚刚的path和group。
    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) {
    //...

    try {

        // 核心函数1
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        if (null != callback) {
            callback.onLost(postcard);
        } else {
            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()) {
        // 需要调用拦截器
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            @Override
            public void onContinue(Postcard postcard) {
                _navigation(context, postcard, requestCode, callback);
            }

            @Override
            public void onInterrupt(Throwable exception) {
                if (null != callback) {
                    callback.onInterrupt(postcard);
                }

                logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
            }
        });
    } else {
        // 不需要调用拦截器

        // 核心函数2
        return _navigation(context, postcard, requestCode, callback);
    }

    return null;
}

复制代码

接着,我们来看那两个核心函数:

// 核心函数1
public synchronized static void completion(Postcard postcard) {
    if (null == postcard) {
        throw new NoRouteFoundException(TAG + "No postcard!");
    }

    // 先从Warehouse.routes里面获取RouteMeta,我们上述代码的经历只用到了Warehouse.groupsIndex,所以肯定是null

    // 第二次过来了,现在Warehouse.routes有值了,就是根据path拿到的。
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {
        
        // 接着跑这里,从Warehouse.groupsIndex获取IRouteGroup的class!终于用到我们前面注入的玩意儿了,postcard.getGroup()就是"app",
        // 而我们前面调过 Warehouse.groupsIndex.put("app", ARouter$$Group$$app.class),这里就直接取出来了。
        Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());

        if (null == groupMeta) {
            throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
        } else {
            try {
                // 开始反射了
                IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                
                // 调了loadInfo,我们回去看下ARouter$$Group$$app.class的loadInto方法:
                // atlas.put("/app/activity_main", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/app/activity_main", "app", null, -1, -2147483648));
                // 直接put了,key是"/app/activity_main",跟Postcard的path一样,这下就放在Warehouse.routes里面了,下次就能拿到了。
                iGroupInstance.loadInto(Warehouse.routes);

                // 把group扔掉,group的意义就是用来拿route的,现在拿到了已经没用了,删除省内存。
                Warehouse.groupsIndex.remove(postcard.getGroup());
            } catch (Exception e) {
                throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
            }

            // 又调了自己,回到这个函数头重新看
            completion(postcard);
        }
    } else {
        // 第二次进来,跑这里,设置一堆属性,还记得很重要的那一堆参数吗
        // RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/app/activity_main", "app", null, -1, -2147483648)
        postcard.setDestination(routeMeta.getDestination()); // MainActivity.class
        postcard.setType(routeMeta.getType()); // RouteType.ACTIVITY
        postcard.setPriority(routeMeta.getPriority()); // -1
        postcard.setExtra(routeMeta.getExtra()); // -2147483648


        // uri是null,不用看
        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)) {
                for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                    setValue(postcard,
                            params.getValue(),
                            params.getKey(),
                            resultMap.get(params.getKey()));
                }

                postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
            }

            postcard.withString(ARouter.RAW_URI, rawUri.toString());
        }

        // 根据类型执行逻辑,我们的类型是RouteType.ACTIVITY,下面好像都没有
        switch (routeMeta.getType()) {
            case PROVIDER: 
                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();
                break;
            case FRAGMENT:
                postcard.greenChannel();
            default:
                break;
        }
    }
}
复制代码

核心函数1搞完了,总共跑了两次,

  • 第一次,我们从Warehouse.groupsIndex取出注入时保存的数据,然后loadInto()相关数据到Warehouse.routes里面去了。
  • 第二次,我们仅仅是给参数postcard赋值,Destination和Type等。

好,现在我们的Warehouse.routes有数据了,并且参数postcard有destination和type了,于是接着执行核心函数2:

// 核心函数2
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {

    // 我们知道参数context是null,于是就取Arouter.init(context)的context,也就是application
    final Context currentContext = null == context ? mContext : context;

    // 直接根据类型执行逻辑了
    switch (postcard.getType()) {
        case ACTIVITY: // 这就是我们的类型
            // 创建intent,destination就是MainActivity.class,我们在核心函数1里面指定过了
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            intent.putExtras(postcard.getExtras());

            // Flags,我们没有设置,就是-1
            int flags = postcard.getFlags();
            if (-1 != flags) {
                intent.setFlags(flags);
            } else if (!(currentContext instanceof Activity)) { // 我们的context不是Activity
                // 于是就添加FLAG_ACTIVITY_NEW_TASK这个Flag(否则用application启动Activity,有的版本会崩溃),如果navigation()传递了Activity作为context,就不会添加这个flag
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }

            // 设置Action,我们是没有的
            String action = postcard.getAction();
            if (!TextUtils.isEmpty(action)) {
                intent.setAction(action);
            }

            // 切换到UI线程去启动Activity,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;
}
复制代码

核心函数2的逻辑很简答,直接用postcard里面的type去执行对应逻辑,然后执行到了Activity分支,于是就取出destination,也就是MainActivity.class,来启动。

好,我们来小结一下:

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

atlas.put("/app/activity_main", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/app/activity_main", "app", null, -1, -2147483648));
复制代码

此后,我们的Werehouse的routes里面就有了:

{"/app/activity_main", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/app/activity_main", "app"},
复制代码

接着,根据postCard的类型去进行switch-case,当case到RouteType.ACTIVITY时,就进行Activity的启动,此时我们有了对应Activity的class,如果navigation(context)传递了context,则就用这个context来启动Activity,否则就用Arouter.init(context)这个context来启动,如果这个context不是activity,则会添加Intent.FLAG_ACTIVITY_NEW_TASK这个flag来启动Activity。

总结

我们一路追踪了Arouter的源码流程:

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

总之,这跟我们一开始用的common公共依赖是一样的,Arouter本身也是被app和login两个module依赖的,所以Arouter本身就是第三方公共依赖。

所以,一个大思想就是: 顶层两个互不相通的模块想要通信,可以借助公共依赖的底层模块来进行,这里的模块是广义的泛指,比如两个应用程序互相通信可以借助系统程序,两个activity互相通信可以借助application,甚至两个类互相通信可以借助父类。两个小技术就是Apt和类加载,当然也可以不用这两个技术,技术是手段,思想是目标,只要能实现目标,什么都无所谓。

分类:
Android
标签: