前语

ARouter是阿里技术团队开源的组件化结构,用于组件化项目中完成跨模块调用。我会从两个方面对ARouter结构源码进行剖析:运行期源码和编译期源码。编译期间,ARouter内部的注解处理器会根据相重视解进行辅佐代码生成,这些生成的代码是ARrouter之所以能够供给跨模块调用的关键;运行期间,再凭借于这些编译器产品来完成跨模块调用。本篇先进行运行时源码的剖析,引出问题,读者先能够思考一下,编译器产品的完成逻辑,我会在下一篇中和咱们一同进行剖析。

demo项目结构

咱们以ARouter供给的demo为例来进行ARouter源码的剖析,在剖析源码之前,咱们先了解一下demo的架构组成,有助于咱们对ARouter进行更全面的了解。

graph TD
app --> module-java
app --> module-java-export
app --> module-kotlin
module-java --> arouter-api
module-java-export --> arouter-api
module-kotlin --> arouter-api
module-java --> arouter-compiler
module-java-export --> arouter-compiler
module-kotlin --> arouter-compiler
module-java --> arouter-annotation
module-java-export --> arouter-annotation
module-kotlin --> arouter-annotation

编译期产品

今日的重点是无参数传递时的Activity页面跳转,咱们先了解一下编译期间产生的辅佐代码。

module-java模块

module-java模块中被注解的类以及其参数装备许多,咱们以Test2Activity作为剖析的进口。

@Route(path = "/test/activity2")
public class Test2Activity extends AppCompatActivity {

group

首要咱们要知道一点,ARouter会按照module名为每个mudule生成一个名为ARouter$$Root$$modulejava格式的类,其间modulejava表明当时模块名。如module-java模块生成的类为:

public class ARouter$$Root$$modulejava implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("m2", ARouter$$Group$$m2.class);
    routes.put("module", ARouter$$Group$$module.class);
    routes.put("test", ARouter$$Group$$test.class);
    routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);
  }
}

也便是说,有几个模块(装备了ARouter)就会生成几个ARouter$$Root$$xxx类,从上边的示例能够看出,这个类是用来管理本模块中定义的一切group的,module-java模块中定义了四个group,别离是m2、module、test、yourservicegroupname。

凭借这个辅佐类,就能够经过group名找到该group的管理类ARouter$$Group$$xxxARouter$$Group$$xxx内部管理了该分组下的一切path信息,接下来咱们看看这些path信息。

path

持续剖析编译期的第二个产品,也便是ARouter$$Root$$modulejavamap映射中存储的value-Class<? extends IRouteGroup>,如上边test分组对应的ARouter$$Group$$test.class,它内部存储的是本分组下一切path的映射联系。

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("ch", 5); put("fl", 6); put("dou", 7); put("boy", 0); put("url", 8); put("pac", 10); put("obj", 11); put("name", 8); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
    atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
    atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
    atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("pac", 10); put("ch", 5); put("obj", 11); put("fl", 6); put("name", 8); put("dou", 7); put("boy", 0); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
    atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
  }
}

能够看到,办法体中履行时,是以path为key,以被注解类的相关信息封装出来的RouteMeta为value存入map中,有了这个类,就能够根据定义的path来找到被注解的类的相关信息。

group + path

了解了group和path的辅佐类生成的办法之后,你是不是也能猜到ARouter跨模块调用的大概完成了?如A、B两个没有依靠联系的模块需要跨模块调用,A想要翻开B中的某Activity页面,只需要在该Activity上添加@Route注解,指定该Activity的group和path,如@Route(path = "/login/loginApi") 然后编译过程中,注解处理器会根据B模块名生成该模块下的group管理类ARouter$$Root$$B,有了ARouter$$Root$$B再传入group名(login),就能够拿到本分组下的一切path信息管理类,ARouter$$Group$$loginARouter$$Group$$login内存储的又是一切path的合集,再经过指定path就能够找到该path对应的被注解的类的信息。所以,即使没有依靠联系的两个模块,在ARouter的协助下,也能完成调用到B模块中类的作用。

源码剖析

了解了编译器产品后,接下来咱们开端从下面两个办法下手,进行Activity页面跳转(无参数)源码剖析。

  • ARouter.init(getApplication())
  • ARouter.getInstance().build(“/test/activity2”).navigation()

init

init是ARouter的初始化办法,跟进这个办法,会进入到LogisticsCenter的init办法中。

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    //经过插件加载
    loadRouterMap();
    if (registerByPlugin) {
        //假如是经过插件注册,则不需要做任何处理,registerByPlugin默以为false,那么它是
        //在哪被赋值的?loadRouterMap
    } else {
        //非插件加载
    }
}

ARouter的初始化有两种办法,一种是经过插件在编译期生成代码,另一种是运行期遍历一切dex文件解分出注解类。咱们先看看loadRouterMap办法做了什么。

插件注册

/**
 * arouter-auto-register plugin will generate code inside this method
 * call this method to register all Routers, Interceptors and Providers
 */
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());
}

能够看到这儿除了将registerByPlugin赋值为false之外,再无任何代码,但是从办法注释上能够看出,ARouter插件会经过主动生成代码刺进到这个办法中来注册一切的Routers, Interceptors and Providers,生成代码的办法如办法体中注释那样,经过刺进代码调用registerRouteRoot、registerProvider、registerInterceptor办法进行注册(registerProvider、registerInterceptor注释上没表现出来,但实际会有),而传入的参数便是上边说到过的编译期产品如ARouter$$Root$$modulejava

private static void registerRouteRoot(IRouteRoot routeRoot) {
    markRegisteredByPlugin();
    if (routeRoot != null) {
        routeRoot.loadInto(Warehouse.groupsIndex);
    }
}

registerRouteRoot被调用后会先调用markRegisteredByPlugin将registerByPlugin变量赋值为true,总算找到registerByPlugin赋值的当地了。紧接着将Warehouse.groupsIndex这个静态map作为参数,调用IRouteRoot完成类的loadInto办法,咱们以ARouter$$Root$$modulejava辅佐类的完成为例,看下这个loadInto办法的完成,它会将modulejava模块下一切分组信息存入Warehouse.groupsIndex这个map调集中。

public class ARouter$$Root$$modulejava implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("m2", ARouter$$Group$$m2.class);
    routes.put("module", ARouter$$Group$$module.class);
    routes.put("test", ARouter$$Group$$test.class);
    routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);
  }
}

也便是说,一切的Routers, Interceptors and Providers分组信息都会被一次性在loadRouterMap办法中被存入到Warehouse.groupsIndex、Warehouse.interceptorsIndex、Warehouse.providersIndex等map调集中,这便是初始化时做的准备工作。

代码注册

接下来咱们再看下非插件类型的注册。前边在说插件类型初始化时,是经过调用registerRouteRoot办法直接创立各模块下的形如ARouter$$Root$$modulejava的方针,将其new出来存入map。插件当然是能够直接在编译期获取到各模块下生成的这个方针,但是若不使用插件生成的办法,又该怎样拿到这些类并创立方针呢?ARouter的做法便是遍历一切dex文件。

Set<String> routerMap;
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
    //debug状态下或者新版别时,需要从头扫描dex并将一切包名以"com.alibaba.android.arouter.routes"
    //开头的类获取到,getFileNameByPackageName办法完成就不再深入了
    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
    if (!routerMap.isEmpty()) {
        //拿到一次后,以json字符串的办法保存到SharedPreferences,下次同一版别不再进行扫描,由于扫描本身是很耗时的
        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
    }
    PackageUtils.updateVersion(context);  
} else {
    //不需要从头扫描的状况,直接从sp中读取
    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}
//逻辑走到这儿,routerMap中存储的全部是IRouteRoot、IInterceptorGroup、IProviderGroup类型的类,
//别离经过反射的办法将方针创立出来,并调用loadInto填充三个map调集
for (String className : routerMap) {
    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
    }
}

办法的最后,拿到各个IRouteRoot、IInterceptorGroup、IProviderGroup完成类后,经过反射创立方针,并调用各自的loadInto办法,这样一来,初始化完成后,三个静态map:Warehouse.groupsIndex、Warehouse.interceptorsIndex、Warehouse.providersIndex中就有信息了。

至此,咱们就把ARouter初始化的逻辑剖析完了。接下来开端看看,它是怎样凭借这些准备好的信息完成跨模块调用的,以下面一行代码调用为例。

ARouter.getInstance().build("/test/activity2").navigation();

getInstance

创立出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

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

ARouter的build办法会进入_ARouter的build办法。

protected Postcard build(String path) {
    //这儿有一个获取PathReplaceService的逻辑,与本次剖析无关,咱们先不重视,默以为null
    PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
    if (null != pService) {
        path = pService.forString(path);
    }
    //extractGroup从path上解分出group,如"/test/activity2"的group便是test
    return build(path, extractGroup(path), true);
}

相同,这儿的PathReplaceService越过,默以为null。能够看到,终究build办法便是构建了一个Postcard,并将group和path两个值保存为其成员变量。

protected Postcard build(String path, String group, Boolean afterReplace) {
    ......
    return new Postcard(path, group);
}

navigation

由于build办法的返回值是Postcard方针,那么navigation天然也是Postcard的办法了。不断跟进此办法,你会发现终究又来到了_ARouter中,_ARouter的navigation办法中,将postcard方针传了进去,所以_ARouter中也就包括了postcard的信息。

public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
    return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
}

_ARouter的navigation办法,这儿的内容较多,咱们只重视和本次剖析相关的,去掉一些非主线代码。

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    try {
        //填充postcard信息
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        return null;
    }
    //这儿是一个拦截器机制,跟本次剖析无关,看完LogisticsCenter.completion直接进入它的onContinue办法中
    interceptorService.doInterceptions(postcard, new InterceptorCallback() {
        @Override
        public void onContinue(Postcard postcard) {
            _navigation(postcard, requestCode, callback);
        }
    });
    return null;
}

LogisticsCenter.completion,看看这个办法做了什么。

public synchronized static void completion(Postcard postcard) {
    if (null == postcard) {
        throw new NoRouteFoundException(TAG + "No postcard!");
    }
    //首要根据传入的途径获取到RouteMeta方针。RouteMeta方针是什么?
    //还记得咱们前面剖析path生成的辅佐类时说到的下面这一行代码,
    //RouteMeta便是封装了被@Route注解的类的信息。
    //如RouteMeta.build(RouteType.ACTIVITY, TestModule2Activity.class, "/module/2", "m2", null, -1, -2147483648)
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    //拿到这个信息后,又分两种状况处理,为空和不为空,为空的状况不在咱们今日剖析的范围内,所以就不考虑了,咱们来看非空的逻辑。
    if (null == routeMeta) {
        ......
    } else {
        //给postcard填充信息,将routeMeta保存的方针信息设置进去
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());
        //本次剖析不涉及uri,所以直接越过
        Uri rawUri = postcard.getUri();
        if (null != rawUri) {   // Try to set params into bundle.
            ......
        }
        //这儿也都不满足,由于咱们的routeMeta.getType()是RouteType.ACTIVITY,越过
        switch (routeMeta.getType()) {
            case PROVIDER:  
                break;
            case FRAGMENT:
            default:
                break;
        }
    }
}

所以这个办法对于本次剖析来说,也仅仅填充Postcard信息,前边Postcard先存储了group和path信息,现在又将方针类的信息存入进去,可见此类的命名为“明信片”仍是很有意思的,明信片天然要包括目的地的信息,咱们看一下代码履行到这儿,Postcard现已包括了哪些信息,先有一个直观的了解。

Android开源结构系列-组件化之ARouter Activity页面无参数跳转源码剖析

持续回到navigation办法中,咱们持续来看onContinue办法的履行,来到这儿,才算是真正要开端进行页面跳转了,办法体中咱们只保留了ACTIVITY类型,其他类型不在本次剖析之内。

private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    final Context currentContext = postcard.getContext();
    switch (postcard.getType()) {
        case ACTIVITY:
            // 首要构建intent方针,从上边的截图中咱们能够看到,getDestination得到的
            //是方针途径Test2Activity的class方针,正好用来构建intent
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            //传递的intent参数,本次咱们剖析的是不带参数的跳转,所以这个暂不重视
            intent.putExtras(postcard.getExtras());
            // 设置flag类型
            int flags = postcard.getFlags();
            if (0 != flags) {
                intent.setFlags(flags);
            }
            // 假如currentContext不是Activity的上下文context方针,就设置FLAG_ACTIVITY_NEW_TASK
            if (!(currentContext instanceof Activity)) {
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }
            // 暂不重视
            String action = postcard.getAction();
            if (!TextUtils.isEmpty(action)) {
                intent.setAction(action);
            }
            // 切换到主线程履行startActivity,看到这儿是不是觉得很奇妙,原来ARouter
            //终究仍是使用了最常规的activity翻开办法进行跳转的
            runInMainThread(new Runnable() {
                @Override
                public void run() {
                    startActivity(requestCode, currentContext, intent, postcard, callback);
                }
            });
            break;
    }
    return null;
}

总结

至此咱们就现已将ARouter跨模块完成页面跳转的逻辑剖析完了,咱们回忆一下ARouter到底是怎样做到的?咱们在非组件化开发中,页面跳转只需拿到方针页面Activity的class方针,就能够直接调用startActivity进行跳转,咱们也发现ARouter终究的完成也是如此,所以这个结构所做的逻辑就很清晰了,一句话形容,整个结构所做的核心便是在协助咱们去拿到方针Activity页面的class方针。知道这一点之后,咱们还要知道它是怎样拿到的?ARouter经过编译器生成辅佐代码的办法来完成,辅佐代码中保存好开发者定义的path和方针页面的映射联系,这样一来,开发者就能够经过path拿到方针页面的信息,完成跳转。