在app的开发中,页面之间的相互跳转是最基本常用的功能。在Android中的跳转一般通过显式intent和隐式intent两种方式实现的,而Android的原生跳转方式会存在一些缺点:
- 显式intent的实现方式,因为会存在直接的类依赖的问题,导致耦合严重;
- 隐式intent的实现方式,则会出现规则集中式管理,导致协作变得困难;
- 可配置性较差,一般而言配置规则都是在Manifest中的,这就导致了扩展性较差;
- 跳转过程无法控制,一旦使用了StartActivity()就无法插手其中任何环节了,只能交给系统管理;
- 当多组件化开发,使用原生的路由方式很难实现完全解耦;
而阿里的ARouter路由框架具有解耦、简单易用、支持多模块项目、定制性较强、支持拦截逻辑等诸多优点,很好的解决了上述的问题。关于ARouter具体实现功能,典型应用以及相应技术方案实现的介绍不在这详细介绍,具体可参见开源最佳实践:Android平台页面路由框架ARouter。
阿里ARouter的分析计划
- 阿里ARouter使用及源码解析(一)
- 阿里ARouter拦截器使用及源码解析(二)
- 阿里ARouter参数自动装载使用及源码解析(三)
基本功能使用
1.添加依赖和配置
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [ moduleName : project.getName() ]
}
}
}
}
dependencies {
compile 'com.alibaba:arouter-api:1.2.1.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.1.2.1'
...
}
2.添加注解
// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/test1")
public class Test1Activity extends AppCompatActivity{ @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test1); } }
3.初始化SDK
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button btn1,btn2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn1 = (Button) findViewById(R.id.btn1); btn2 = (Button) findViewById(R.id.btn2); btn1.setOnClickListener(this); btn2.setOnClickListener(this); } @Override public void onClick(View v) { if (v.getId() == R.id.btn1) { // 如果使用了InstantRun,必须在初始化之前开启调试模式,但是上线前需要关闭,InstantRun仅用于开发阶段, // 线上开启调试模式有安全风险,可以使用BuildConfig.DEBUG来区分环境 ARouter.openDebug(); ARouter.init(getApplication()); // 尽可能早,推荐在Application中初始化 } } }
4.发起跳转操作
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button btn1,btn2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn1 = (Button) findViewById(R.id.btn1); btn2 = (Button) findViewById(R.id.btn2); btn1.setOnClickListener(this); btn2.setOnClickListener(this); } @Override public void onClick(View v) { if (v.getId() == R.id.btn1) { .... } else if (v.getId() == R.id.btn2){ ARouter.getInstance().build("/test/test1").navigation(); } } }
以上相关代码就是ARouter的最基本功能使用的步骤,下面来分析跳转功能是如何实现的。
原理分析
1.ARouter编译的过程
ARouter在编译期的时候,利用自定义注解完成了页面的自动注册。相关注解源码参见arouter-annotation,编译处理器源码参见arouter-compiler
下面是注解@Route
的源码介绍:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
/** *路由的路径,标识一个路由节点 */ String path(); /** * 将路由节点进行分组,可以实现按组动态加载 */ String group() default ""; /** * 路由节点名称,可用于生成javadoc文档 */ String name() default "undefined"; /** * 用32位int类型标示,可用于页面的一些配置 */ int extras() default Integer.MIN_VALUE; /** * 路由的优先级 */ int priority() default -1; }
Route中的extra
值是个int值,由32位表示,即转换成二进制后,一个int中可以配置31个1或者0,而每一个0或者1都可以表示一项配置(排除符号位),如果从这31个位置中随便挑选出一个表示是否需要登录就可以了,只要将标志位置为1,就可以在声明的拦截器中获取到这个标志位,通过位运算的方式判断目标页面是否需要登录。所以可以通过extra
给页面配置30多个属性,然后在拦截器中去进行处理。
ARouter在拦截器中会把目标页面的信息封装一个类Postcard
,这个类就包含了目标页面注解上@Route
标识的各种信息。关于拦截器的使用以及源码分析,后续会有介绍。
将代码编译一遍,可以看到ARouter生成下面几个源文件:
![](https://upload-images.jianshu.io/upload_images/5994347-6c409a3386b0abd9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/327)
上面三个文件均是通过注解处理器RouteProcessor
生成的,关于如何自定义注解处理器,可以阅读Android编译时注解APT实战(AbstractProcessor),同时也需要学习JavaPoet的基本使用。下面我们看RouteProcessor
是如何生成相关文件的。
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { //判断被注解了的元素集合是否为空 if (CollectionUtils.isNotEmpty(annotations)) { //获取所有被@Route注解的元素集合,Element可以是类、方法、变量等 Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class); try { logger.info(">>> Found routes, start... <<<"); //具体处理注解,生成java文件的方法 this.parseRoutes(routeElements); } catch (Exception e) { logger.error(e); } return true; } return false; }
process()
方法相当于处理器的主函数main()
,可以在这个方法中扫描、评估和处理注解的代码,以及生成Java文件。RouteProcessor
中调用了parseRoutes()
,用来处理所有被@Route
注解的元素。在分析上述三个java文件如何生成之前,先看看生成文件的具体代码。
- ARouter$$Root$$app类
public class ARouter$$Root$$app implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("test", ARouter$$Group$$test.class); } }
- ARouter$$Group$$test类
public class ARouter$$Group$$test implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/test/test1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/test1", "test", null, -1, -2147483648)); } }
- ARouter$$Providers$$app类
public class ARouter$$Providers$$app implements IProviderGroup { @Override public void loadInto(Map<String, RouteMeta> providers) { } }
我们接着分析上述三个文件是如何生成的
1.首先获取生成方法的参数的类型和参数名称
private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
if (CollectionUtils.isNotEmpty(routeElements)) {
logger.info(">>> Found routes, size is " + routeElements.size() + " <<<"); rootMap.clear(); // TypeElement 表示一个类或接口元素 // public static final String ACTIVITY = "android.app.Activity"; //得到类activity元素 TypeElement type_Activity = elementUtil.getTypeElement(ACTIVITY); // public static final String SERVICE = "android.app.Service"; //得到类service的元素 TypeElement type_Service = elementUtil.getTypeElement(SERVICE); // public static final String SERVICE = "android.app.Fragment"; TypeMirror fragmentTm = elements.getTypeElement(FRAGMENT).asType(); // public static final String SERVICE = "android.support.v4.app.Fragment"; TypeMirror fragmentTmV4 = elements.getTypeElement(Consts.FRAGMENT_V4).asType(); // public static final String IROUTE_GROUP = "com.alibaba.android.arouter.facade.template.IRouteGroup"; //得到接口IRouteGroup元素 TypeElement type_IRouteGroup = elementUtil.getTypeElement(IROUTE_GROUP); // public static final String IROUTE_GROUP = "com.alibaba.android.arouter.facade.template.IProviderGroup"; //得到接口IProviderGroup元素 TypeElement type_IProviderGroup = elementUtil.getTypeElement(IPROVIDER_GROUP); //获取RouteMeta,RouteType类名 ClassName routeMetaCn = ClassName.get(RouteMeta.class); ClassName routeTypeCn = ClassName.get(RouteType.class); //下面代码是获取生成java文件中方法的参数类型名称和参数名称。 /* 获取获取ARouter$$Root$$app 类中方法参数Map<String, Class<? extends IRouteGroup>>类型的名称 */ ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get( ClassName.get(Map.class), ClassName.get(String.class), ParameterizedTypeName.get( ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup)) ) ); /* 获取ARouter$$Group$$test,ARouter$$Providers$$app类中方法参数 Map<String, RouteMeta>类型的名称 */ ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get( ClassName.get(Map.class), ClassName.get(String.class), ClassName.get(RouteMeta.class) ); /* 获取相关的参数 */ //获取ARouter$$Root$$app 类中方法的参数Map<String, Class<? extends IRouteGroup>> routes ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build(); //获取ARouter$$Group$$test类中方法的参数Map<String, RouteMeta> atlas ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build(); //获取ARouter$$Providers$$app类中方法的参数Map<String, RouteMeta> providers ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build(); ..... } }
2.获取了方法的参数的类型和参数名称后,下面便是生成相应的方法
private void parseRoutes(Set<? extends Element> routeElements) throws IOException { if (CollectionUtils.isNotEmpty(routeElements)) { ........ /* 首先创建ARouter$$Root$$xxx 类中的loadInto()方法 @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {} */ MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO) .addAnnotation(Override.class) .addModifiers(PUBLIC) .addParameter(rootParamSpec); // 遍历所有被@Route注解的元素 for (Element element : routeElements) { TypeMirror tm = element.asType(); Route route = element.getAnnotation(Route.class); RouteMeta routeMete = null; //判断该元素否为 Activity 、IProvider 、 Service 的子类,然后创建相应的RouteMeta 对象 if (typeUtil.isSubtype(tm, type_Activity.asType())) { // Activity logger.info(">>> Found activity route: " + tm.toString() + " <<<"); // 如果是acitiviy类型,获取所有被@Autowired的属性 //关于@Autowired的注解,我们之后再进行分析 Map<String, Integer> paramsType = new HashMap<>(); for (Element field : element.getEnclosedElements()) { if (field.getKind().isField() && field.getAnnotation(Autowired.class) != null && !typeUtil.isSubtype(field.asType(), iProvider)) { // It must be field, then it has annotation, but it not be provider. Autowired paramConfig = field.getAnnotation(Autowired.class); paramsType.put(StringUtils.isEmpty(paramConfig.name()) ? field.getSimpleName().toString() : paramConfig.name(), TypeUtils.typeExchange(field.asType())); } } // ACTIVITY类型节点 routeMete = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType); } else if (typeUtil.isSubtype(tm, iProvider)) { // IProvider logger.info(">>> Found provider route: " + tm.toString() + " <<<"); //从该判断可看出,如果要想成功注册一个 PROVIDER 类型的路由节点, //一定要实现 com.alibaba.android.arouter.facade.template.IProvider 这个接口 routeMete = new RouteMeta(route, element, RouteType.PROVIDER, null); } else if (typeUtil.isSubtype(tm, type_Service.asType())) { // Service logger.info(">>> Found service route: " + tm.toString() + " <<<"); //SERVICE类型节点 routeMete = new RouteMeta(route, element, RouteType.parse(SERVICE), null); } else if (types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) { logger.info(">>> Found fragment route: " + tm.toString() + " <<<"); //FRAGMENT类型节点 routeMete = new RouteMeta(route, element, RouteType.parse(FRAGMENT), null); } //routeMete包含了每个路由节点的各种信息,下面的方法的主要功能就是根据@Route注解信息对节点进行分组,保存在groupMap集合中。 //关于方法的具体实现,后面会有解析 categories(routeMete); } ......... } }
以上代码主要功能就是遍历所有被@Route注解的元素,然后将每个路由节点的信息按照类型(ACTIVITY类型,实现了IProvider 接口类型以及SERVICE类型)封装到RouteMeta
中,最后调用categories(routeMete)
方法将节点分组,保存在groupMap
集合。
继续往下分析
private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
if (CollectionUtils.isNotEmpty(routeElements)) {
........
/*
然后创建ARouter$$Providers$$xxx 类中的loadInto()方法
@Override
public void loadInto(Map<String, RouteMeta> providers) {}
*/
MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(providerParamSpec);
//遍历分组的集合,生成相应的java文件 //因为本文使用的例子没有对页面进行分组,所以只生成了一个组文件ARouter$$Group$$xxx for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) { String groupName = entry.getKey(); /* 创建ARouter$$Group$$xxx 类中的loadInto()方法 @Override public void loadInto(Map<String, RouteMeta> atlas) {} */ MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO) .addAnnotation(Override.class) .addModifiers(PUBLIC) .addParameter(groupParamSpec); // 生成loadInto()方法体 Set<RouteMeta> groupData = entry.getValue(); //遍历每个组里面的路由节点 for (RouteMeta routeMeta : groupData) { switch (routeMeta.getType()) { //如果节点类型是PROVIDER, case PROVIDER: //获取路由节点元素的接口集合 List<? extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces(); for (TypeMirror tm : interfaces) { if (types.isSameType(tm, iProvider)) { // Its implements iProvider interface himself. //路由节点元素其中一个接口是 com.alibaba.android.arouter.facade.template.IProvider //给ARouter$$Providers$$xxx 类中的loadInto()添加方法体 loadIntoMethodOfProviderBuilder.addStatement( "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))", (routeMeta.getRawType()).toString(),//路由节点元素的全名 routeMetaCn, routeTypeCn, ClassName.get((TypeElement) routeMeta.getRawType()), routeMeta.getPath(), routeMeta.getGroup()); } else if (types.isSubtype(tm, iProvider)) { //路由节点元素其中一个接口是com.alibaba.android.arouter.facade.template.IProvider 接口的子类型 loadIntoMethodOfProviderBuilder.addStatement( "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))", tm.toString(), //IProvider子类型的全名 routeMetaCn, routeTypeCn, ClassName.get((TypeElement) routeMeta.getRawType()), routeMeta.getPath(), routeMeta.getGroup()); } //上面方法体的代码为: //providers.put("实现接口的名称", RouteMeta.build(RouteType.PROVIDER, 类名.class, "@Route.path", "@Route.group", null, @Route.priority, @Route.extras)); } break; default: break; } // 将路由节点中被@Autowired注解的属性集合转换成字符串 StringBuilder mapBodyBuilder = new StringBuilder(); //获取路由节点中被@Autowired注解的属性集合 Map<String, Integer> paramsType = routeMeta.getParamsType(); if (MapUtils.isNotEmpty(paramsType)) { for (Map.Entry<String, Integer> types : paramsType.entrySet()) { mapBodyBuilder.append("put(\"").append(types.getKey()).append("\", ").append(types.getValue()).append("); "); } } String mapBody = mapBodyBuilder.toString(); //给ARouter$$Group$$xxx 类中的loadInto()添加方法体 //注意:有多个分组就会创建多个组文件 loadIntoMethodOfGroupBuilder.addStatement( "atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap<String, Integer>(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))", routeMeta.getPath(), routeMetaCn, routeTypeCn, ClassName.get((TypeElement) routeMeta.getRawType()), routeMeta.getPath().toLowerCase(), routeMeta.getGroup().toLowerCase()); } // 真正生成ARouter$$Group$$test JAVA文件 //NAME_OF_GROUP = ARouter$$Group$$ // groupName = test; 关于groupname的值在方法categories(routeMete)中会有讲解 String groupFileName = NAME_OF_GROUP + groupName; JavaFile.builder(PACKAGE_OF_GENERATE_FILE, TypeSpec.classBuilder(groupFileName) .addJavadoc(WARNING_TIPS) .addSuperinterface(ClassName.get(type_IRouteGroup)) .addModifiers(PUBLIC) .addMethod(loadIntoMethodOfGroupBuilder.build()) .build() ).build().writeTo(mFiler); logger.info(">>> Generated group: " + groupName + "<<<"); //将生成的组文件放在rootmap集合中去,为下面生成ARouter$$Root$$xxx文件做准备 rootMap.put(groupName, groupFileName); } ....... } }
以上代码主要功能由几点:
- 遍历
groupmap
集合给ARouter$$Group$$xxx类中的loadInto()
添加方法体,并且生成ARouter$$Group$$xxx JAVA文件,而文件命名为ARouter$$Group$$+groupname,其中有多个分组就会创建多个组文件。比如AROUTER
源码中的样例就生成了多个分组文件
![](https://upload-images.jianshu.io/upload_images/5994347-bd709da910e3a722.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/592)
关于生成的loadInto()
中的方法体的例子,来自 AROUTER
源码中的样例:
public class ARouter$$Group$$test implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { //存在被@Autowired注解参数生成的代码 atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("name", 18); put("boy", 0); put("age", 3); put("url", 18); }}, -1, -2147483648)); ..... //没有被@Autowired注解参数生成的代码 atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648)); .... } }
- 遍历每个组里面的路由节点,查找节点类型是否为PROVIDER类型,如果是就向给ARouter$$Providers$$xxx类中的
loadInto()
添加方法,其文件命名ARouter$$Providers$$+modulename。关于生成的loadInto()
中的方法体的例子,来自 AROUTER源码中的样例:
public class ARouter$$Providers$$app implements IProviderGroup { @Override public void loadInto(Map<String, RouteMeta> providers) { providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648)); //路由节点元素其中一个接口是IProvider的子类型 providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json", "service", null, -1, -2147483648)); //路由节点元素其中一个接口是IProvider接口 providers.put("com.alibaba.android.arouter.demo.testservice.SingleService", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/service/single", "service", null, -1, -2147483648)); } }
- 将生成的组文件放在rootmap集合中去,为下面生成ARouter$$Root$$xxx文件做准备,其文件命名ARouter$$Root$$+modulename。
我们接着分析parseRoutes()
方法最后一段代码,这段代码其实很简单,主要目的就是给ARouter$$Root$$xxx的loadInto()
添加方法体,最后生成Router$$Providers$$xxx,ARouter$$Root$$xxx文件
private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
if (CollectionUtils.isNotEmpty(routeElements)) {
........
//遍历rootMap集合,给ARouter$$Root$$xxx的`loadInto()`添加方法体
if (MapUtils.isNotEmpty(rootMap)) { // Generate root meta by group name, it must be generated before root, then I can findout the class of group. for (Map.Entry<String, String> entry : rootMap.entrySet()) { loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue())); } } // 生成Router$$Providers$$xxx文件 String providerMapFileName = NAME_OF_PROVIDER + SEPARATOR + moduleName; JavaFile.builder(PACKAGE_OF_GENERATE_FILE, TypeSpec.classBuilder(providerMapFileName) .addJavadoc(WARNING_TIPS) .addSuperinterface(ClassName.get(type_IProviderGroup)) .addModifiers(PUBLIC) .addMethod(loadIntoMethodOfProviderBuilder.build()) .build() ).build().writeTo(mFiler); logger.info(">>> Generated provider map, name is " + providerMapFileName + " <<<"); // 生成ARouter$$Root$$xxx文件 String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName; JavaFile.builder(PACKAGE_OF_GENERATE_FILE, TypeSpec.classBuilder(rootFileName) .addJavadoc(WARNING_TIPS) .addSuperinterface(ClassName.get(elementUtil.getTypeElement(ITROUTE_ROOT))) .addModifiers(PUBLIC) .addMethod(loadIntoMethodOfRootBuilder.build()) .build() ).build().writeTo(mFiler); logger.info(">>> Generated root, name is " + rootFileName + " <<<"); } }
关于生成的loadInto()
中的方法体的例子,来自 AROUTER源码中的样例:
public class ARouter$$Root$$app implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("service", ARouter$$Group$$service.class); routes.put("test", ARouter$$Group$$test.class); } }
上面分析的便是parseRoutes()
方法所有代码的解析
3.最后我们看下categories()
方法是如何分组的
private void categories(RouteMeta routeMete) { //如果路由路径合法,且有groupname进行执行 if (routeVerify(routeMete)) { logger.info(">>> Start categories, group = " + routeMete.getGroup() + ", path = " + routeMete.getPath() + " <<<"); //根据groupname获取该组的路由节点集合,如果集合为空,则创建一个新的组,将该节点添加进去,并将组集合保存在groupmap中; //不为空,则添加到所属的组集合中去 Set<RouteMeta> routeMetas = groupMap.get(routeMete.getGroup()); if (CollectionUtils.isEmpty(routeMetas)) { Set<RouteMeta> routeMetaSet = new TreeSet<>(new Comparator<RouteMeta>() { @Override public int compare(RouteMeta r1, RouteMeta r2) { try { return r1.getPath().compareTo(r2.getPath()); } catch (NullPointerException npe) { logger.error(npe.getMessage()); return 0; } } }); routeMetaSet.add(routeMete); groupMap.put(routeMete.getGroup(), routeMetaSet); } else { routeMetas.add(routeMete); } } else { logger.warning(">>> Route meta verify error, group is " + routeMete.getGroup() + " <<<"); } } //判断路由路径是否合法,并且设置groupname private boolean routeVerify(RouteMeta meta) { String path = meta.getPath(); //如果路径为空,或者不是由'/'开头,返回false if (StringUtils.isEmpty(path) || !path.startsWith("/")) { // The path must be start with '/' and not empty! return false; } //如果在@Route注解中没有设置group标识,那么就默认取path路径第一段路径名作为groupname if (StringUtils.isEmpty(meta.getGroup())) { // Use default group(the first word in path) try { String defaultGroup = path.substring(1, path.indexOf("/", 1)); if (StringUtils.isEmpty(defaultGroup)) { return false; } meta.setGroup(defaultGroup); return true; } catch (Exception e) { logger.error("Failed to extract default group! " + e.getMessage()); return false; } } return true; }
通过分析,如果@Route注解中有设置group标识,作为groupname,如果没有就取/xxx1/xxx2,xxx1作为groupname,并将同一组的路由节点放到同一个集合中去。
至此关于@Route
注解在编译期时生成ARouter$$Root$$xxx,Router$$Providers$$xxx,ARouter$$Group$$xxx三种映射文件的源码分析完毕。
2.ARouter初始化过程
ARouter经过代码编译后,生成了相应的映射文件,我们可以断定,ARouter 的初始化会将这些文件加载到内存中去,形成一个路由表,以供后面路由查找跳转之用。其相关源码可参见 arouter-api
ARouter
的init()
方法
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
的 init ()
方法,而且其他的跳转方法最终调用的也是_ARouter
种的方法。
_ARouter
的init()
方法
protected static synchronized boolean init(Application application) { mContext = application; LogisticsCenter.init(mContext, executor); logger.info(Consts.TAG, "ARouter init success!"); hasInit = true; return true; }
_ARouter
中又调用了LogisticsCenter.init()
,继续追踪下去,其中传入了一个线程池executor
,这个线程池在拦截器的时候会使用到。
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException { mContext = context; executor = tpe; try { //ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes" // 获取ROUTE_ROOT_PAKCAGE 包里面的所有文件 List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE); //遍历所有ROUTE_ROOT_PAKCAGE 包里的文件 for (String className : classFileNames) { //文件名以“com.alibaba.android.arouter.routes.ARouter$$Root”开头执行下面代码 if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) { // 通过反射实例化,并且调用loadInto(),目的即是将编译生成的ARouter$$Group$$xxx文件加载到内存中,保存在Warehouse.groupsIndex; ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) { //文件名以“com.alibaba.android.arouter.routes.ARouter$$Interceptors”开头执行下面代码 // 执行编译生成的ARouter$$Interceptors$$xxx的loadInto(),将自定义拦截器类存放在Warehouse.interceptorsIndex中 ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) { //文件名以“com.alibaba.android.arouter.routes.ARouter$$Providers”开头执行下面代码 // 执行编译生成的ARouter$$Interceptors$$xxx的loadInto() ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex); } } 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() + "]"); } }
_ARouter
的afterInit()
方法
static void afterInit() { // 通过路由机制,初始化路由拦截机制。关于路由拦截机制的使用和原理,后续文章会有分析 interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation(); }
以上就是ARouter初始化的所有代码,关于如何查找到com.alibaba.android.arouter.routes
包内所有文件这里便不做过多分析,大家可以去阅读 arouter-api中ClassUtils
这个类的源码。
总结下来,其实ARouter 的初始化只做了一件事,找到自己编译期产生的清单文件,把 Group 、Interceptor 、Provider 三种清单加载到 Warehouse 内存仓库中。即下面这些文件,来源自AROUTER源码中的样例
![](https://upload-images.jianshu.io/upload_images/5994347-c375d8bc320ad241.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/518)
值得注意的是,在初始化阶段,ARouter 仅载入了 Group 清单,并没有具体载入每个 Group 中包含的具体的路由节点清单,只有当使用到具体的 Group 时,才会加载对应的 Group 列表。这种分组管理,按需加载,大大的降低了初始化时的内存压力。并且Warehouse
类中保存了路由清单,并且将使用过的路由对象缓存起来,之后查找都是直接使用缓存的对象 。
3.ARouter调用过程分析
页面跳转最基本方法
ARouter.getInstance().build("/test/activity2").navigation();
获取Provider服务(实现了IProvider接口以及IProvider子类接口的服务类)的方法有两种:
1.byName方式
ARouter.getInstance().build("/service/hello").navigation()
2.byType方式
ARouter.getInstance().navigation(HelloService.class)
ARouter路由跳转采用链式调用,ARouter.getInstance()
其中采用的单例模式,获取ARouter的实例,这个就不作过多分析,主要分析build()
和navigation()
。
build()方法
ARouter的build(String path)
和init()
方法一样,调用的是_ARouter
的build(String path)
方法。
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)); } }
其中extractGroup(String path)
就是根据path获取分组名,即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 { // /xxx1/xxx2 ===> defaulGroup = xxx1 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(String path)
方法最终调用的是build(String path, String group)
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); } }
值得注意的是其中ARouter.getInstance().navigation(PathReplaceService.class)
就是得到实现PathReplaceService
接口的一个服务对象,对原始path进行处理后,生成新的path路径。而这个类需要我们自己自定义去实现,如果没有实现,pService=null,原始path不做任何处理。
下面是PathReplaceService
接口,我们可以通过实现forString()
和forUri()
方法,对某些url进行替换处理,跳转到其他的目标页面。
public interface PathReplaceService extends IProvider { /** * For normal path. * * @param path raw path */ String forString(String path); /** * For uri type. * * @param uri raw uri */ Uri forUri(Uri uri); }
最后返回一个Postcard
实例对象,里面封装了路由节点的路径,分组等节点信息。其实build()
方法的目的只有一个就是根据路由,封装成Postcard
对象,其对象贯穿之后整个路由过程。Postcard 包含了众多的属性值,提供了路由过程中所有的控制变量。
public final class Postcard extends RouteMeta { private Uri uri; private Object tag; // A tag prepare for some thing wrong. private Bundle mBundle; // 传递的参数 private int flags = -1; // intent 的flag标志 private int timeout = 300; // Navigation timeout, TimeUnit.Second ! private IProvider provider; // IProvider服务对象 private boolean greenChannal; private SerializationService serializationService;//序列化服务对象 // 跳转动画 private Bundle optionsCompat; // The transition animation of activity private int enterAnim; private int exitAnim; // copy from RouteMeta private RouteType type; // 路由节点类型 private Element rawType; private Class<?> destination; //需要跳转到的页面 private String path; // 路径 private String group; // 分组 private int priority = -1; // 优先级 private int extra; // 配置标识 private Map<String, Integer> paramsType; // 路由页面被@Autowired注解属性 // ...... }
navigation()方法
关于页面跳转的navigation()
方法有多个重载的方法,但最终都会调用_ARouter
下面这个方法
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) { try { //首先对postcard进行一些处理,设置postcard的destination,type,priority 等一些属性值,completion()后面会有分析 LogisticsCenter.completion(postcard); } catch (NoRouteFoundException ex) { logger.warning(Consts.TAG, ex.getMessage()); if (debuggable()) { // Show friendly tips for user. Toast.makeText(mContext, "There's no route matched!\n" + " Path = [" + postcard.getPath() + "]\n" + " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show(); } // 如果处理postcard失败,通过 callback 回调失败结果 // callback为空的情况下,如果有定义全局的降级处理(DegradeService),则使用全局处理 //降级处理也需要我们自己实现DegradeService接口 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; } //路由处理成功,回调callback.onFound() if (null != callback) { callback.onFound(postcard); } //目前来说,PROVIDER服务类型,以及FRAGMENT类型不需要通过拦截器外,其他类型均需要通过拦截器 //关于拦截器相关用法及原理分析在后续的文章中会讲解到,大家去可以关注下 if (!postcard.isGreenChannel()) { 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; }
值得注意的是,当跳转路由处理失败的时候,会获取一个降级服务,我们可以实现DegradeService
接口,实现onLost()
方法,对路由处理失败的情况进行处理,比如跳转到一个信息提示页面,让用户去更新版本等操作等。 下面是DegradeService
接口:
public interface DegradeService extends IProvider { /** * Router has lost. * * @param postcard meta */ void onLost(Context context, Postcard postcard); }
通过上面代码的分析,不管是否通过拦截器进行处理,最后都会调用_navigation()
达到路由的目的:
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: //下面就是最基本的使用intent进行activity进行跳转 // 创建intent final Intent intent = new Intent(currentContext, postcard.getDestination()); //设置传参 intent.putExtras(postcard.getExtras()); //activity启动标志 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); } // 在主线程中进行跳转 new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { //新版本带转场动画的启动方式 if (requestCode > 0) { // Need start for result ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle()); } else { ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle()); } if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version. //老版本的跳转动画 ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim()); } //跳转成功,回调callback.onArrival() if (null != callback) { // Navigation over. callback.onArrival(postcard); } } }); break; case PROVIDER: return postcard.getProvider(); case BOARDCAST: case CONTENT_PROVIDER: case FRAGMENT: Class fragmentMeta = postcard.getDestination(); try { //实例化fragment,并传递参数 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; }
目前仅ARouter实现了 ACTIVITY , PROVIDER ,FRAGMENT三种种类型。上面关于postcard的provider,destination的值都是在completion()
中设置的。我们接着看LogisticsCenter
的completion(Postcard postcard)
。
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!"); } // 查找Warehouse仓库的路由节点缓存,看是否已在缓存中 RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath()); if (null == routeMeta) { // 如果没有,查找仓库的组别清单中是否存在该组别,组别清单已经在初始化的时候加载到仓库中去了 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 { // 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())); } // 实例化个组别的类,调用loadInto(),将组别中所有的路由节点加载进仓库Warehouse.routes,缓存 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() + "]"); } //当路由节点加载到缓存中去后,重新查找执行else代码,对postcard进行处理 completion(postcard); // Reload } } else { //给postcard设置destination,type,priority等值,供上面讲解到的_navigation()进行使用 // 其中routeMeta是在ARouter$$Group$$xxx的loadInto中创建的 postcard.setDestination(routeMeta.getDestination()); postcard.setType(routeMeta.getType()); postcard.setPriority(routeMeta.getPriority()); postcard.setExtra(routeMeta.getExtra()); //如果通过build(Uri url) 进行跳转的话 通过解析url ,将传参保存进bundle中 Uri rawUri = postcard.getUri(); if (null != rawUri) { //splitQueryParameters()就是在uri中携带的参数进行解析 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 autoinject. postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{})); } // Save raw uri postcard.withString(ARouter.RAW_URI, rawUri.toString()); } //从这里也可以看出PROVIDER,FRAGMENT不需要通过拦截器 switch (routeMeta.getType()) { case PROVIDER: // 如果是PROVIDER节点类型,从服务节点列表中获取,如果没有,则实例化,并保存在服务节点列表Warehouse.providers中 //并将实例化的对象设置给postcard的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(); // Provider should skip all of interceptors break; case FRAGMENT: postcard.greenChannel(); // Fragment needn't interceptors default: break; } } }
分析到这里,关于页面基本跳转的原理分析就已经结束了。最后就是关于获取Provider服务两种方法的源码分析。其中byName方式,和页面跳转是一模一样的。我们只需要看看byType方式即可。byType方式最后调用的是_ARouter
的navigation(Class<? extends T> service)
protected <T> T navigation(Class<? extends T> service) { try { // 通过 className 获取 Postcard 对象 Postcard postcard = LogisticsCenter.buildProvider(service.getName()); // 兼容1.0.5 compiler sdk版本. if (null == postcard) { // No service, or this service in old version. postcard = LogisticsCenter.buildProvider(service.getSimpleName()); } // 对 Postcard 对象进行处理 LogisticsCenter.completion(postcard); //返回 Postcard 中的 provider 属性值 return (T) postcard.getProvider(); } catch (NoRouteFoundException ex) { logger.warning(Consts.TAG, ex.getMessage()); return null; } }
上面代码中的completion()
方法之前已经分析过了,只需要看下LogisticsCenter.buildProvider(service.getName())
即可。
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()); } }
这个方法非常的简单,就是根据服务类名去仓库Warehouse.providersIndex中获去路由节点元素,然后封装在Postcard对象中。服务类清单列表Warehouse.providersIndex中的值是在初始化时缓存的。值得注意的是,PROVIDER 类型的路由节点既存在于对应的分组中,也存在于服务类清单列表中。所以,ARouter 可通过byType,byName两种方式来获取。
补充
关于ARouter的基本用法上面只有最基本跳转的介绍,下面对其他一些基本使用进行下补充
- 带参数跳转
//1.传递参数
ARouter.getInstance().build("/test/activity1")
.withString("name", "老王")
.withInt("age", 18) .withBoolean("boy", true) .withLong("high", 180) .withString("url", "https://a.b.c") .withParcelable("pac", testParcelable) .withObject("obj", testObj) .navigation(); //2.直接传递Bundle Bundle params = new Bundle(); ARouter.getInstance() .build("/test/activity1") .with(params) .navigation();
这些传参都是保存在生成的postcard
对象中的mBundle
属性里,然后在跳转的时候通过intent.putExtras(postcard.getExtras())
达到传送参数的目的。
值得注意的是,关于对象的传递有两种,一种是withParcelable()
方法,不过此方法需要传递的对象实现Parcelable
接口,达到序列化的目的;另外一种是withObject()
方法,此方法的原理是将实体类转换成json字符串,通过String的方式进行传递,而且使用这种方式需要实现 SerializationService,并使用@Route注解标注,下面是ARouter样例:
@Route(path = "/service/json")
public class JsonServiceImpl implements SerializationService { @Override public void init(Context context) { } @Override public <T> T json2Object(String text, Class<T> clazz) { return JSON.parseObject(text, clazz); } @Override public String object2Json(Object instance) { return JSON.toJSONString(instance); } }
而且,需要在跳转到的页面获取JsonServiceImpl
服务,将json字符串转换成对象。
SerializationService serializationService = ARouter.getInstance().navigation(SerializationService.class);
TestObj obj = serializationService.json2Object(getIntent().getString("obj"), TestObj.class);
- 带返回结果跳转
ARouter.getInstance().build("/test/activity2").navigation(this, 666);
值得注意的是,这时候的 navigation
需要传递activit和requestCode。
- 获取Fragment的实例
定义一个fragment
@Route(path = "/test/fragment")
public class BlankFragment extends Fragment { public BlankFragment() { //必须要一个空的构造器 } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { TextView textView = new TextView(getActivity()); return textView; } }
获取frament
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
- 带转场动画跳转
// 转场动画(常规方式)
ARouter.getInstance() .build("/test/activity2")
.withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom)
.navigation(this);
// 转场动画(API16+)
ActivityOptionsCompat compat = ActivityOptionsCompat.makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2, 0, 0); ARouter.getInstance().build("/test/activity2").withOptionsCompat(compat) .navigation();
- 获取服务
服务是全局单例的,只有在第一次使用到的时候才会被初始化。
暴露服务,必须实现IProvider 接口 或者其子类型
// 声明接口,其他组件通过接口来调用服务
public interface HelloService extends IProvider { String sayHello(String name); } // 实现接口 @Route(path = "/service/hello", name = "测试服务") public class HelloServiceImpl implements HelloService { @Override public String sayHello(String name) { return "hello, " + name; } @Override public void init(Context context) { } }
获取服务
//bytype
HelloService helloService1 = ARouter.getInstance().navigation(HelloService.class);
//byname
HelloService helloService2 = (HelloService) ARouter.getInstance().build("/service/hello").navigation();
- 多模块结构
![](https://upload-images.jianshu.io/upload_images/5994347-767a114aa4746c87.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/788)
app中可能存在多个模块,每个模块下面都有一个root结点,每个root结点都会管理整个模块中的group节点,每个group结点则包含了该分组下的所有页面,而每个模块允许存在多个分组,每个模块中都会有一个拦截器节点就是Interceptor结点,除此之外每个模块还会有控制拦截反转的provider结点
最后
到此,关于ARouter的基本用法以及原理分析的就全部结束了,如果有不清楚或者错误的地方,希望各位同学指出。关于ARouter拦截器,各种服务,依赖注入等更多进阶用法及源码分析会更新在后续的文章。
如果各位同学认为本文对你有一些帮助,希望能点个喜欢,谢谢!