可能是最好理解的ARouter源码分析

ARouter是什么

ARouter是阿里巴巴开源的Android平台中对页面、服务提供路由功能的中间件,提倡的是简单且够用。
Github: https://github.com/alibaba/ARouter
介绍: https://yq.aliyun.com/articles/71687

简单使用示例

Activtiy跳转

ARouter.getInstance().build("/test/activity2").withString("name", "老王").navigation();

获取服务功能

ARouter.getInstance().navigation(HelloService.class).sayHello("mike");

((HelloService) ARouter.getInstance().build("/yourservicegroupname/hello").navigation()).sayHello("mike");

主要代码结构

  • arouter-api: 上层主要代码,包括入口类ARouter,主要逻辑代码类LogisticsCenter,相关辅助类ClassUtils等

  • arouter-annotation: ARouter中主要支持的annotation(包括Autowired, Route, Interceptor)的定义,以及RouteMeta等基础model bean的定义

  • arouter-compiler: ARouter中annotation对应的annotation processor代码(注解处理器:让这些注解代码起作用,Arouter中主要是生成相关代码,关于annotation processor,详细了解可参考 Java注解处理器, 这篇译文介绍的非常完整。

  • arouter-gradle-plugin: 一个gradle插件,目的是在arouter中插入相关注册代码(代替在Init时扫描dex文件获取到所有route相关类)

  • app: Demo代码,包括Activity跳转,面向接口服务使用,拦截器的使用等详细使用示例

核心源码分析

ARouter.getInstance().build("/test/activity2").withString("name", "老王").navigation();

从最常用的跳转开始分析,基本可了解到ARouter的运转原理。这行完成跳转的代码最终效果是携带参数跳转到对应的Activity,在Android层面来说最后一定是通过调用startActivity或是startActivityForResult来完成跳转。

分为几步来看:

  1. ARouter调用Build生成Postcard,过程是怎样的
  2. Postcard是什么
  3. Postcard调用navigation是怎样执行到startActivity的

生成Postcard过程

打开ARouter类,发现基本都是调用_ARouter类的方法。

ARouter类非常简单,只是通过Facade pattern包装了_ARouter类的相关方法,方便调用和阅读。

_ARouter类真正实现了相关入口功能,包括初始化和销毁等方法,另外主要包括生成Postcard以及通过Postcard完成navigation的代码 (这里先介绍生成Postcard生成部分,_ARouter详细介绍见下方)

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

这过程主要完成了2个步骤:

  1. 查找是否存在PathReplaceService,如果有调用PathReplaceService的forString方法获取新的path (后面介绍完Service功能会更好理解这部分)
  2. 从path中解析出group值,调用带有group参数的build方法
    protected Postcard build(String path, String group) {
        if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            PathReplaceService pService = 
                ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            return new Postcard(path, group);
        }
    }

在这个方法中就可以看到Postcard的构造。

Postcard是什么

首先Postcard继承RouteMeta,RouteMeta中存储的是关于route的一些基础信息,只定位于存储route基础信息。

public class RouteMeta {
    private RouteType type;         // Type of route
    private Element rawType;        // Raw type of route
    private Class destination;   // Destination
    private String path;            // Path of route
    private String group;           // Group of route
    private int priority = -1;      // The smaller the number, the higher the priority
    private int extra;              // Extra data
    private Map paramsType;  // Param type
    private String name;

    private Map injectConfig;  // Cache inject config.
  
    ...
    
}

Postcard类则还包括了"明信片"的"寄出"(navigation方法),"是否支持绿色通道"(isGreenChannel方法),以及支持"寄出效果"(withTransition方法)等具体的功能。

public final class Postcard extends RouteMeta {
    // Base
    private Uri uri;
    private Object tag;             // A tag prepare for some thing wrong.
    private Bundle mBundle;         // Data to transform
    private int flags = -1;         // Flags of route
    private int timeout = 300;      // Navigation timeout, TimeUnit.Second
    private IProvider provider;     // It will be set value, if this postcard was provider.
    private boolean greenChannel;
    private SerializationService serializationService;

    // Animation
    private Bundle optionsCompat;    // The transition animation of activity
    private int enterAnim = -1;
    private int exitAnim = -1;
  
    ...
      
    public void navigation(Activity mContext, int requestCode, NavigationCallback callback){
        ARouter.getInstance().navigation(mContext, this, requestCode, callback);
    }
  
    ...
      
    public Postcard withString(@Nullable String key, @Nullable String value) {
        mBundle.putString(key, value);
        return this;
    }
  
    ...
      
    public Postcard withTransition(int enterAnim, int exitAnim) {
        this.enterAnim = enterAnim;
        this.exitAnim = exitAnim;
        return this;
    }
  
    ...
}

回到前面的介绍,在_ARouter的的build方法中通过path和group参数构造出了一张可以"寄出"的"明信片"(new Postcard)。

Postcard.navigation到startActivity

接下来就是调用Postcard的navigation方法

public void navigation(Activity mContext, int requestCode, NavigationCallback callback) {
    ARouter.getInstance().navigation(mContext, this, requestCode, callback);
}

可以看到其实是调用了ARouter的navigation, 内部调用了_ARouter的navigation。接下来看看navigation的主要过程是怎样的。

protected Object navigation(final Context context, final Postcard postcard, final int
    requestCode, final NavigationCallback callback) {
    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;
}

这个方法有点长,但主要包括3步流程。

  1. 调用LogisticsCenter.completion

    主要是为Postcard找到对应router,并且用router中信息填充Postcard对象。

    如果该方法抛出NoRouteFoundException,则调用对应Callback的onLost,如果没有配置Callback则尝试获取是否存在DegradeService,如果存在调用DegradeService的onLost方法。(DegradeService的实现可以理解为兜底方案,比如native页面没有找到,用相应H5页面替代展示)

    LogisticsCenter类的详细介绍见下方。

  2. 调用Callback的onFound方法,然后判断是否可以走“绿色通道”(即不支持拦截)。

    不能走“绿色通道”的都需要经过拦截器拦截(后面会介绍拦截器原理),拦截器会返回继续和中断2种结果,继续则会继续执行调用navigation方法(相当于在跳转前做了点额外动作);

    如果走了“绿色通道”的则直接调用_navigation方法。

  3. _navigation方法中则可以看到,如果 postcard.getType() 是activity则调用startActivity或startActivityForResult,且支持动画和启动flags的设置,接着调用callback.onArrival。
    如果种类是provider则提供提供provider对象,如果是Fragment则生成对象且赋值参数。

ARouter.getInstance().build("/test/activity2").withString("name", "老王").navigation();

至此,已梳理完上方代码完成Activity跳转的主要流程。

获取接口对象

((HelloService) ARouter.getInstance().build("/yourservicegroupname/hello").navigation()).sayHello("mike");

大体流程和上面完成Activity跳转流程代码类似,主要区别在于,

  1. LogisticsCenter.completion中给Postcard填充type时,对应的是RouteType.PROVIDER,并且在Warehouse中找到对应的Provider赋值给Postcard。
  2. 在ARouter的_navigation方法中,发现Postcard类型是RouteType.PROVIDER则直接返回对应Provider。

ARouter中Annotation的使用

Annotation在ARouter中有着非常重要作用,Annotation基本原理这里不做详谈。
ARouter中主要的Annotation有3个:

Autowired
Route
Interceptor

对应的Annotation定义代码位置如下图所示,位于arouter-annotation module中。

可能是最好理解的ARouter源码分析_第1张图片
annotation代码位置.jpg

Autowired

Autowired主要完成界面跳转过程中,Intent参数的自动填充。

先看看Autowired是如何使用的,再看看Autowired是如何做到的。

如何使用

@Route(path = "/test/activity1", name = "测试用 Activity")
public class Test1Activity extends AppCompatActivity {

    @Autowired(desc = "姓名")
    String name = "jack";

    @Autowired
    int age = 10;

    ...
      
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test1);

        ARouter.getInstance().inject(this);
      
        ...
          
        //inject方法被调用后,被Autowired注解的变量则已从Intent中取出对应参数赋值
    }
}

如何实现

在arouter-compiler module中,查看对应的AutowiredProcessor代码,这是一个用来处理Autowired注解的注解处理器,主要目的是生成Java代码,生成怎样的Java代码(如何生成的,可基于注解处理器原理详细查看AutowiredProcessor),如下:

public class Test1Activity$$ARouter$$Autowired implements ISyringe {
  private SerializationService serializationService;

  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);
    Test1Activity substitute = (Test1Activity)target;
    substitute.name = substitute.getIntent().getExtras() == null ? substitute.name : 
            substitute.getIntent().getExtras().getString("name", substitute.name);
    substitute.age = substitute.getIntent().getIntExtra("age", substitute.age);
    substitute.height = substitute.getIntent().getIntExtra("height", substitute.height);
    substitute.girl = substitute.getIntent().getBooleanExtra("boy", substitute.girl);
    
    ...
  }

代码路径:


可能是最好理解的ARouter源码分析_第2张图片
autowired路径.jpg

梳理下Autowired注解整个流程:

在Activity的变量上标注,注解处理器会在编译阶段扫描有被Autowired注解标注的变量,根据这个类和变量的情况生成一份Java代码,其中最主要会有一个inject方法,完成对相关变量的解析赋值,然后在Activity的onCreate靠前位置调用对应inject方法即可。这个注解主要目的在于省去了手动编写解析Intent参数的代码。
(但为什么代码是调用 ARouter.getInstance().inject(this); 后面会有说明)

Route

Route注解标注的类(页面或服务类),可通过path跳转到对应界面或是获取到具体服务。

如何使用

@Route(path = "/test/activity1", name = "测试用 Activity")
public class Test1Activity extends AppCompatActivity {
  ...
}

@Route(path = "/yourservicegroupname/hello")
public class HelloServiceImpl implements HelloService {
  ...
}

如何实现

在arouter-compiler module中,查看对应的RouteProcessor代码,用来处理Route注解的注解处理器,主要完成以下几部分的生成代码工作(主要为RouteProcessor中的parseRoutes方法):

  1. 获取到所有的被注解Route的类,生成对应的RouteMeta,分组放到groupMap中,key为group name, value为支持排序放入的的Set中。

  2. 遍历groupMap中所有的Set的所有RouteMeta (所以是个双层for循环)生成对应代码。

  for (Map.Entry> entry : groupMap.entrySet()) {
      String groupName = entry.getKey();
       ...
      Set groupData = entry.getValue();
      for (RouteMeta routeMeta : groupData) {
        ...
      }
    ...
  }

生成的代码如下:

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map atlas) {
    ...
    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", null, -1, -2147483648));
    atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, 
                                "/test/webview", "test", null, -1, -2147483648));
    ...
  }
}
  1. 生成rootMap,key为group name,value为刚才每个group对应生成的java类的类名,根据rootMap生成对应Java文件。
   public class ARouter$$Root$$app implements IRouteRoot {
     @Override
     public void loadInto(Map> routes) {
       routes.put("test", ARouter$$Group$$test.class);
       routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);
     }
   }

详细关于这些生成类是如何被使用的,在后面LogisticsCenter中会有详细介绍。

Interceptor

Interceptor 主要用于在跳转过程中插入一些功能。

如何使用

@Interceptor(priority = 7)
public class Test1Interceptor implements IInterceptor {
    Context mContext;

    @Override
    public void process(final Postcard postcard, final InterceptorCallback callback) {
        if ("/test/activity4".equals(postcard.getPath())) {

            // 这里的弹窗仅做举例,代码写法不具有可参考价值
            final AlertDialog.Builder ab = new AlertDialog.Builder(MainActivity.getThis());
            ab.setCancelable(false);
            ab.setTitle("温馨提醒");
            ab.setMessage("想要跳转到Test4Activity么?(触发了\"/inter/test1\"拦截器,拦截了本次跳
                          转)");
            ab.setNegativeButton("继续", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    callback.onContinue(postcard);
                }
            });
            ab.setNeutralButton("算了", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    callback.onInterrupt(null);
                }
            });
            ab.setPositiveButton("加点料", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    postcard.withString("extra", "我是在拦截器中附加的参数");
                    callback.onContinue(postcard);
                }
            });

            MainLooper.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    ab.create().show();
                }
            });
        } else {
            callback.onContinue(postcard);
        }
    }

这个例子是想在跳转到 "/test/activity4" 对应的Activity过程中弹出对话框,在用户做出了相关动作后再继续跳转,点击"继续"则继续执行,点击"算了"则取消跳转,点击"加点料"则在跳转过程中添加参数。

如何实现

上面在分析ARouter的navigation的跳转过程中,分析_ARouter的navigation方法中有提到会去判断是否走"绿色通道",如果没有的话则要调用 interceptorService.doInterceptions 方法来经过拦截器的处理。

来看看拦截器是如何处理的,主要代码在InterceptorServiceImpl类中。主要方法有:

doInterceptions:供外部调用接口方法,会在异步线程中开启拦截器的逐个调用,会去调用_excute方法,完成对拦截器IInterceptor的process方法的调用,通过CountDownLatch来控制是否所有拦截器都调用完成,且在超时时间内。

_excute: 调用IInterceptor的process方法,在callback的onContinue方法中递归调用自己,但调整index值,使用下一个拦截器。

_ARouter介绍

ARouter类只是使用Facade patten对_ARouter类进行了封装,并没有太多实际功能代码,所以看看_ARouter中的具体实现代码。

首先_ARouter是个单例类,但会在getInstance方法中判断是否有进行初始化(必须要先初始化)。

其次几个主要的方法:

init: 这里面最重要是调用了LogisticsCenter.init,完成相关路由信息填充。

Inject: 获取AutowiredService的实体,会调用到AutowiredServiceImpl的autowire方法,在这个方法中会根据传入参数Activity的Name再加上注解处理器中约定的后缀字段获得新的类名(也就是注解处理器生成的对应的Java文件对应的类名),利用反射方式生成新对象,调用其inject方法。

(如上面分析的类 Test1Activity$$ARouter$$Autowired的inject方法)。

@Route(path = "/arouter/service/autowired")
public class AutowiredServiceImpl implements AutowiredService {
    
    ...

    @Override
    public void autowire(Object instance) {
        String className = instance.getClass().getName();
        try {
            if (!blackList.contains(className)) {
                ISyringe autowiredHelper = classCache.get(className);
                if (null == autowiredHelper) {  // No cache.
                    autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() 
                                            + SUFFIX_AUTOWIRED).getConstructor().newInstance();
                }
                autowiredHelper.inject(instance);
                classCache.put(className, autowiredHelper);
            }
        } catch (Exception ex) {
            blackList.add(className);    // This instance need not autowired.
        }
    }
}

build: 生成对应Postcard

navigation: 跳转到对应页面或获取对应Service等

arouter-gradle-plugin module

这个module 是一个gradle plugin, 目的是帮助在com.alibaba.android.arouter.core.LogisticsCenter类的loadRouterMap方法中插入各个module注册router代码。(关于gradle plugin的机制,可通过深入理解Android之Gradle详细了解)

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

    /**
     * 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 Contact me.
     * @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 Contact me.
     * @since 2017-12-06
     */
    private static void registerInterceptor(IInterceptorGroup interceptorGroup) {
        markRegisteredByPlugin();
        if (interceptorGroup != null) {
            interceptorGroup.loadInto(Warehouse.interceptorsIndex);
        }
    }

在ARouter 1.4版本前,是通过扫描ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes"这个packagename下获取所有route相关类(包括分布在不同的module中的),然后遍历分别调用对应的loadInto方法加载到 Warehouse 的各自Index中(包括Warehouse.providersIndex,Warehouse.interceptorsIndex等),构成不同类别的全局路由总表。
这段代码目前仍存在,主要对应LogisticsCenter.init方法中registerByPlugin变量为false时对应的分支代码。
这种方式的缺点在于是在运行时去扫描dex文件中所有类找到需要的类反射来完成映射表的注册。

从ARouter 1.4开始,引入AutoRegister机制(关于AutoRegister,可以在AutoRegister:一种更高效的组件自动注册方案了解详情)。基本原理是:在编译时,扫描所有类,将符合条件的类收集起来,并通过修改字节码生成注册代码到指定的管理类中,从而实现编译时自动注册的功能,不用再关心项目中有哪些组件类了。不会增加新的class,不需要反射,运行时直接调用组件的构造方法。
在ARouter中具体的实现如下:

  1. arouter-gradle-plugin实现为gradle plugin, plugin name 是 PLUGIN_NAME = "com.alibaba.arouter",定义在ScanSetting中。在app module中的build.gradle中加入 apply plugin: 'com.alibaba.arouter',表示使用该plugin。

  2. RegisterTransform中完成扫描以ROUTER_CLASS_PACKAGE_NAME = 'com/alibaba/android/arouter/routes/' 开头的类,然后调用RegisterCodeGenerator类的方法完成向LogisticsCenter::loadRouterMap插入代码。

通过反编译apk出来的实际出来的代码如下:

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

这样在编译阶段即可完成相关类的扫描工作,在ARouter初始化时只是完成路由总表的加载,省去在Dex文件中扫描类的步骤。

这里也贴下代码README.md中给予的说明

使用 Gradle 插件实现路由表的自动加载 (可选)

    apply plugin: 'com.alibaba.arouter'

    buildscript {
        repositories {
            jcenter()
        }

        dependencies {
            // Replace with the latest version
            classpath "com.alibaba:arouter-register:?"
        }
    }

可选使用,通过 ARouter 提供的注册插件进行路由表的自动加载(power by AutoRegister), 默认通过扫描 dex 的方式。
进行加载通过 gradle 插件进行自动注册可以缩短初始化时间解决应用加固导致无法直接访问dex 文件,初始化失败的问题,需要注意的是,该插件必须搭配 api 1.3.0 以上版本使用!

LogisticsCenter介绍

本想再详细介绍下LogisticsCenter类,但发现经过上面的一些介绍,LogisticsCenter的主要几个方法已经都解释过。

loadRouteMap: 由arouter-gradle-plugin 插件module在编译阶段插入注册Route相关代码。

init: 调用loadRouteMap完成Warehouse中各个路由Index的加载,如果有plugin帮忙插入代码则直接使用,如果没有则通过运行时扫描dex文件方式加载。

completion:前面分析navigation时有详细介绍,在Route路由表中根据postcard中的path找到对应的路由信息RouteMeta,利用RouteMeta中信息为Postcard赋值。

你可能感兴趣的:(可能是最好理解的ARouter源码分析)