找回密码
 会员注册
查看: 19|回复: 0

ARouter原理解析之仿ARouter自定义路由框架

[复制链接]

1

主题

0

回帖

4

积分

新手上路

积分
4
发表于 2024-10-11 19:55:27 | 显示全部楼层 |阅读模式
ARouter是什么?ARouter是阿里开源的一款android路由框架,帮助 Android App 进行组件化改造的路由框架 —— 支持模块间的路由、通信、解耦;结合路由可以实现组件化。ARouter接入指北完整Arouter接入指南,ARouter重度用户可以跳过,直接往后看第一步,根build.gradle设置使用arouter-registerapplyplugin:'com.alibaba.arouter'buildscript{repositories{mavenCentral()}dependencies{classpath"com.alibaba:arouter-register:"}}第二步,创建baselib,并加入dependenciesapi'com.alibaba:arouter-api:x.x.x'第三步,创建组件module,例如login 或者setting 组件android{defaultConfig{...javaCompileOptions{annotationProcessorOptions{arguments=[AROUTER_MODULE_NAME:project.getName()]}}}}dependencies{//替换成最新版本,需要注意的是api//要与compiler匹配使用,均使用最新版可以保证兼容//compile'com.alibaba:arouter-api:x.x.x'此移动到baselib中apiproject(path:':baselib')annotationProcessor'com.alibaba:arouter-compiler:x.x.x'...}第四步,通过注解@Route 注册页面//在支持路由的页面上添加注解(必选)//这里的路径需要注意的是至少需要有两级,/xx/xx@Route(path="/test/activity")publicclassYourActivityextendActivity{...}第五步,初始化if(isDebug()){//这两行必须写在init之前,否则这些配置在init过程中将无效ARouter.openLog();//打印日志ARouter.openDebug();//开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)}ARouter.init(mApplication);//尽可能早,推荐在Application中初始化第六步,使用ARouterARouter.getInstance().build("/test/activity").navigation();ARouter比传统Intent有哪些优点传统intent的优点轻量简单传统intent的缺点跳转过程无法控制,一旦调用了startActivity(Intent)便交由系统执行,中间过程无法插手跳转失败无法捕获、降级,出现问题直接抛出异常显示Intent中因为存在直接的类依赖关系,导致耦合严重startActivity(newIntent(MainActivity.this,LoginActivity.class));//强依赖LoginActivity隐式Intent中会出现规则集中式的管理,导致协作困难,都需要在Manifest中进行配置,导致扩展性比较差//隐式比显式更强一点,可以在两个无关子module之间跳转,由于显式无法引入包,所以无法完成跳转Intentintent=newIntent();intent.setClassName(MainActivity.this,"com.cnn.loginplugin.ui.login.LoginActivity");//设置包路径startActivity(intent);ARouter优点模块间通信(后面讲原理)支持url 跳转 build("/test/activity").navigation()支持拦截器//比较经典的应用就是在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查//拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行@Interceptor(priority=8,name="测试用拦截器")publicclassTestInterceptorimplementsIInterceptor{@Overridepublicvoidprocess(Postcardpostcard,InterceptorCallbackcallback){...callback.onContinue(postcard);//处理完成,交还控制权//callback.onInterrupt(newRuntimeException("我觉得有点异常"));//觉得有问题,中断路由流程//以上两种至少需要调用其中一种,否则不会继续路由}@Overridepublicvoidinit(Contextcontext){//拦截器的初始化,会在sdk初始化的时候调用该方法,仅会调用一次}}参数注入,@Autowired注解实现,更方便,需要配合ARouter.getInstance().inject(this);一起使用@AutowiredpublicStringname;@Autowiredintage;//通过name来映射URL中的不同参数@Autowired(name="girl")booleanboy;//支持解析自定义对象,URL中使用json传递@AutowiredTestObjobj;//使用withObject传递List和Map的实现了//Serializable接口的实现类(ArrayList/HashMap)//的时候,接收该对象的地方不能标注具体的实现类类型//应仅标注为List或Map,否则会影响序列化中类型//的判断,其他类似情况需要同样处理@AutowiredListlist;@AutowiredMap>map;支持外部url 跳转简单demo,github做简单静态界面服务器,并部署到https://oslanka.github.io/statichtml.github.io/,手机浏览器打开,并点击href实现html打通原生,按道理来说,所有未拦截的ARouter路径,均可被web浏览器跳转,html代码如下: 测试跳转 跳转登录android-ARouter 跳转登录android-ARouter带参数 跳转android-ARouter设置界面 跳转android-ARouter设置界面 跳转android-ARouter错误路径关于拦截器拦截器(拦截跳转过程,面向切面编程)什么是面向切面编程AOP?AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率//拦截器会在跳转之前执行,多个拦截器会按优先级顺序依次执行@Interceptor(priority=8,name="测试用拦截器")publicclassTestInterceptorimplementsIInterceptor{@Overridepublicvoidprocess(Postcardpostcard,InterceptorCallbackcallback){...callback.onContinue(postcard);//处理完成,交还控制权//callback.onInterrupt(newRuntimeException("我觉得有点异常"));//觉得有问题,中断路由流程//以上两种至少需要调用其中一种,否则不会继续路由}@Overridepublicvoidinit(Contextcontext){//拦截器的初始化,会在sdk初始化的时候调用该方法,仅会调用一次}}动态路由动态注册路由信息 适用于部分插件化架构的App以及需要动态注册路由信息的场景,可以通过 ARouter 提供的接口实现动态注册 路由信息,目标页面和服务可以不标注 @Route 注解,「注意:同一批次仅允许相同 group 的路由信息注册」ARouter.getInstance().addRouteGroup(newIRouteGroup(){@OverridepublicvoidloadInto(Mapatlas){atlas.put("/dynamic/activity",//pathRouteMeta.build(RouteType.ACTIVITY,//路由信息TestDynamicActivity.class,//目标的Class"/dynamic/activity",//Path"dynamic",//Group,尽量保持和path的第一段相同0,//优先级,暂未使用0//Extra,用于给页面打标));}});ARouter详细API//构建标准的路由请求,并指定分组ARouter.getInstance().build("/home/main","ap").navigation();//构建标准的路由请求,通过Uri直接解析Uriuri;ARouter.getInstance().build(uri).navigation();//构建标准的路由请求,startActivityForResult//navigation的第一个参数必须是Activity,第二个参数则是RequestCodeARouter.getInstance().build("/home/main","ap").navigation(this,5);//指定FlagARouter.getInstance().build("/home/main").withFlags();.navigation();//获取FragmentFragmentfragment=(Fragment)ARouter.getInstance().build("/test/fragment").navigation();//对象传递ARouter.getInstance().withObject("key",newTestObj("Jack","Rose")).navigation();//使用绿色通道(跳过所有的拦截器)ARouter.getInstance().build("/home/main").greenChannel().navigation();原理探索ARouter.init 时,通过获取/data/app/包名/base.apk来筛选出ARouter生成的类,如下图。对于Activity类型,跳转ARouter.getInstance().build("/login/login").navigation();,最终执行的是,如下:***Startactivity**@seeActivityCompat*/privatevoidstartActivity(intrequestCode,ContextcurrentContext,Intentintent,Postcardpostcard,NavigationCallbackcallback){if(requestCode>=0){//Needstartforresultif(currentContextinstanceofActivity){//启动context为ActivityActivityCompat.startActivityForResult((Activity)currentContext,intent,requestCode,postcard.getOptionsBundle());}else{//启动context为Application时,不支持requestCodelogger.warning(Consts.TAG,"Mustuse[navigation(activity,...)]tosupport[startActivityForResult]");}}else{//启动context为ApplicationActivityCompat.startActivity(currentContext,intent,postcard.getOptionsBundle());}if((-1!=postcard.getEnterAnim()&-1!=postcard.getExitAnim())¤tContextinstanceofActivity){//Oldversion.((Activity)currentContext).overridePendingTransition(postcard.getEnterAnim(),postcard.getExitAnim());}if(null!=callback){//Navigationover.callback.onArrival(postcard);}}两个无关的module 如何跳转的呢?我们发现最终执行startActivity时,所用的context为Application,思路是这样的,子module启动另外无关子module时,将执行权,交还给主进程/主程序去处理打开生成路由文档,AROUTER_GENERATE_DOC="enable",会生成arouter-map-of-xx.json和3个java文件//更新build.gradle,添加参数AROUTER_GENERATE_DOC=enable//生成的文档路径:build/generated/ap_generated_sources/(debugorrelease)/com/alibaba/android/arouter/docs/arouter-map-of-${moduleName}.jsonandroid{defaultConfig{...javaCompileOptions{annotationProcessorOptions{arguments=[AROUTER_MODULE_NAME:project.getName(),AROUTER_GENERATE_DOC:"enable"]}}}}//ARouter映射关系如何生成?Generated出三个文件//ARouter$Group$login//ARouter$Providers$loginplugin//ARouter$Root$loginpluginatlas.put("/login/login",RouteMeta.build(RouteType.ACTIVITY,LoginActivity.class,"/login/login","login",newjava.util.HashMap(){{put("password",8);put("username",8);}},-1,-2147483648));//map存映射关系//staticMaproutes=newHashMap();以上三个文件是如何生成的呢?APT是Annotation Processing Tool的简称,即注解处理工具,apt是在编译期对代码中指定的注解进行解析,然后做一些其他处理(如通过javapoet生成新的Java文件)ARouter使用了两个库auto-service javapoet,来实现从注解到代码的注入,其中auto-service为注解处理器的库,javapoet为代码生成器通过例子了解APT首先我们了解一下元注解,meta-annotation(元注解)@TargetTYPE,//类、接口、枚举类FIELD,//成员变量(包括:枚举常量)METHOD,//成员方法PARAMETER,//方法参CONSTRUCTOR,//构造方法LOCAL_VARIABLE,//局部变量ANNOTATION_TYPE,//注解类PACKAGE,//可用于修饰:包TYPE_PARAMETER,//类型参数,JDK1.8新增TYPE_USE//使用类型的任何地方,JDK1.8新增```-@Retention```java SOURCE, 只在本编译单元的编译过程中保留,并不写入Class文件中。 CLASS, 在编译的过程中保留并且会写入Class文件中,但是JVM在加载类的时候不需要将其加载为运行时可见的(反射可见)的注解==是JVM在加载类时反射不可见。 RUNTIME 在编译过程中保留,会写入Class文件,并且JVM加载类的时候也会将其加载为反射可见的注解。```-@Documented注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息.-@Inherited注解的作用是:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)-通过元注解我们定义自己的注解-[AutoService注解处理器](https://github.com/google/auto/tree/master/service)注解处理器是一个在javac中的,用来编译时扫描和处理的注解的工具。你可以为特定的注解,注册你自己的注解处理器。到这里,我假设你已经知道什么是注解,并且知道怎么申明的一个注解类型。一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。-虚处理器`AbstractProcessor`-`init(ProcessingEnvironmentenv)`:【核心】每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入`ProcessingEnviroment`参数。`ProcessingEnviroment`提供很多有用的工具类`Elements`,`Types`和`Filer`-`process(Setannotations,RoundEnvironmentenv)`:【核心】这相当于每个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件-`getSupportedAnnotationTypes()`这里你必须指定,这个注解处理器是注册给哪个注解的-`getSupportedSourceVersion()`用来指定你使用的Java版本。通常这里返回`SourceVersion.latestSupported()`- APT 所用的代码生成器:**[JavaPoet](https://github.com/square/javapoet)**isaJavaAPIforgenerating`.java`sourcefiles.(JavaPoet是一个javaapi,为了生成.java源文件的)-官方helloworld```javaMethodSpecmain=MethodSpec.methodBuilder("main").addModifiers(Modifier.PUBLIC,Modifier.STATIC).returns(void.class).addParameter(String[].class,"args").addStatement("$T.out.println($S)",System.class,"Hello,JavaPoet!").build();TypeSpechelloWorld=TypeSpec.classBuilder("HelloWorld").addModifiers(Modifier.PUBLIC,Modifier.FINAL).addMethod(main).build();JavaFilejavaFile=JavaFile.builder("com.example.helloworld",helloWorld).build();javaFile.writeTo(System.out);通过以上可生成以下java 文件packagecom.example.helloworld;publicfinalclassHelloWorld{publicstaticvoidmain(String[]args){System.out.println("Hello,JavaPoet!");}}JavaPoet 主要api-JavaFile用于构造输出包含一个顶级类的Java文件-TypeSpec生成类,接口,或者枚举-MethodSpec生成构造函数或方法-FieldSpec生成成员变量或字段-ParameterSpec用来创建参数-AnnotationSpec用来创建注解JavaPoet 主要占位符-$L(forLiterals)执行结构的字符或常见类型,或TypeSpec,$S(forStrings)字符,$T(forTypes)类,$N(forNames)方法等标识符$L>$S//1.Passanargumentvalueforeachplaceholderintheformatstringto`CodeBlock.add()`.Ineachexample,wegeneratecodetosay"Iate3tacos"CodeBlock.builder().add("Iate$L$L",3,"tacos")//2.Whengeneratingthecodeabove,wepassthehexDigit()methodasanargumenttothebyteToHex()methodusing$N:MethodSpecbyteToHex=MethodSpec.methodBuilder("byteToHex").addParameter(int.class,"b").returns(String.class).addStatement("char[]result=newchar[2]").addStatement("result[0]=$N((b>>>4)&0xf)",hexDigit).addStatement("result[1]=$N(b&0xf)",hexDigit).addStatement("returnnewString(result)").build();//=======================publicStringbyteToHex(intb){char[]result=newchar[2];result[0]=hexDigit((b>>>4)&0xf);result[1]=hexDigit(b&0xf);returnnewString(result);}//$TforTypes//WeJavaprogrammersloveourtypes:theymakeourcodeeasiertounderstand.AndJavaPoetisonboard.Ithasrichbuilt-insupportfortypes,includingautomaticgenerationofimportstatements.Justuse$Ttoreferencetypes:.addStatement("returnnew$T()",Date.class)==returnnewDate();实战-自定义简易版路由-CRouter新建name-annotation javaLib,定义CRoute注解@Target({ElementType.TYPE})@Retention(RetentionPolicy.CLASS)public@interfaceCRoute{Stringpath();}新建name-compiler javaLib1.dependencies{implementationproject(path:':TestRouter-annotation')annotationProcessor'com.google.auto.service:auto-service:1.0-rc7'compileOnly'com.google.auto.service:auto-service-annotations:1.0-rc7'implementation'com.squareup:javapoet:1.8.0'}2.@AutoService(Processor.class)publicclassTestRouteProcessorextendsAbstractProcessor{@Overridepublicsynchronizedvoidinit(ProcessingEnvironmentprocessingEnvironment){super.init(processingEnvironment);//dosomething}@Overridepublicbooleanprocess(Setset,RoundEnvironmentroundEnvironment){//dosomething}}业务module执行顺序如下1.annotationProcessorproject(':TestRouter-compiler')implementationproject(':TestRouter-annotation')2.添加注解@CRoute(path="/csetting/csetting")3.编译运行4.业务module apt 生成的java 文件,如下:publicfinalclassC$csettingC$csettingHelloWorld{publicstaticStringholder="/csetting/csetting:com.cnn.settingplugin.SettingsActivity";publicstaticvoidmain(String[]args){System.out.println("Hello,JavaPoet!");}}参考ARouter-init 方法,写出我们CRouter-init/***Init,itmustbecallbeforeusedrouter.*/publicstaticvoidinit(Applicationapplication){if(!hasInit){CRouter.application=application;hasInit=true;try{getFileNameByPackageName(application,ROUTE_ROOT_PAKCAGE);}catch(PackageManager.NameNotFoundExceptione){e.printStackTrace();}catch(IOExceptione){e.printStackTrace();}catch(InterruptedExceptione){e.printStackTrace();}}}利用反射获取到注解对应映射关系,并参考ARouter存入HashMap通过隐式启动Activity模拟跳转到此我们模拟出简易版本的ARouter,完整自定义CRouter/***Createdbycainingon7/29/2116:09* E-Mail Address:cainingning@360.cn*/publicclassCRouter{privatevolatilestaticCRouterinstance=null;privatevolatilestaticbooleanhasInit=false;privatestaticApplicationapplication;publicstaticfinalStringROUTE_ROOT_PAKCAGE="com.cnn.crouter";privatestaticMapmapHolder=newHashMap();/***Init,itmustbecallbeforeusedrouter.*/publicstaticvoidinit(Applicationapplication){if(!hasInit){CRouter.application=application;hasInit=true;try{getFileNameByPackageName(application,ROUTE_ROOT_PAKCAGE);}catch(PackageManager.NameNotFoundExceptione){e.printStackTrace();}catch(IOExceptione){e.printStackTrace();}catch(InterruptedExceptione){e.printStackTrace();}}}/***Getinstanceofrouter.A*AllfeatureUuse,willbestartshere.*/publicstaticCRoutergetInstance(){if(!hasInit){thrownewInitException("ARouter::Init::Invokeinit(context)first!");}else{if(instance==null){synchronized(CRouter.class){if(instance==null){instance=newCRouter();}}}returninstance;}}publicvoidnavigation(Stringpath){startActivity(path);}privatevoidstartActivity(Stringpath){StringclassPath=mapHolder.get(path);if(!TextUtils.isEmpty(classPath)){Intentintent=newIntent();intent.setClassName(application,classPath);//设置包路径ActivityCompat.startActivity(application,intent,null);}else{Toast.makeText(application,"路径空啦",Toast.LENGTH_SHORT).show();}}/***通过指定包名,扫描包下面包含的所有的ClassName**@paramcontextUknow*@parampackageName包名*@return所有class的集合*/privatestaticSetgetFileNameByPackageName(Contextcontext,finalStringpackageName)throwsPackageManager.NameNotFoundException,IOException,InterruptedException{finalSetclassNames=newHashSet();Listpaths=getSourcePaths(context);finalCountDownLatchparserCtl=newCountDownLatch(paths.size());for(finalStringpath:paths){DefaultPoolExecutor.getInstance().execute(newRunnable(){@Overridepublicvoidrun(){DexFiledexfile=null;try{if(path.endsWith("EXTRACTED_SUFFIX")){//NOTusenewDexFile(path),becauseitwillthrow"permissionerrorin/data/dalvik-cache"dexfile=DexFile.loadDex(path,path+".tmp",0);}else{dexfile=newDexFile(path);}EnumerationdexEntries=dexfile.entries();while(dexEntries.hasMoreElements()){StringclassName=dexEntries.nextElement();if(className.startsWith(packageName)){classNames.add(className);try{Classclazz=Class.forName(className);Objectobj=clazz.newInstance();Fieldfield03=clazz.getDeclaredField("holder");//获取属性为id的字段Stringvalue=(String)field03.get(obj);String[]split=value.split(":");if(split!=null&split.length==2){mapHolder.put(split[0],split[1]);}Log.i("test-->",mapHolder.toString());}catch(ClassNotFoundExceptione){e.printStackTrace();}catch(IllegalAccessExceptione){e.printStackTrace();}catch(InstantiationExceptione){e.printStackTrace();}catch(SecurityExceptione){e.printStackTrace();}catch(NoSuchFieldExceptione){e.printStackTrace();}catch(IllegalArgumentExceptione){e.printStackTrace();}}}}catch(Throwableignore){Log.e("ARouter","Scanmapfileindexfilesmadeerror.",ignore);}finally{if(null!=dexfile){try{dexfile.close();}catch(Throwableignore){}}parserCtl.countDown();}}});}parserCtl.await();returnclassNames;}privatestaticListgetSourcePaths(Contextcontext)throwsPackageManager.NameNotFoundException,IOException{ApplicationInfoapplicationInfo=context.getPackageManager().getApplicationInfo(context.getPackageName(),0);ListsourcePaths=newArrayList();sourcePaths.add(applicationInfo.sourceDir);//addthedefaultapkpathreturnsourcePaths;}}总结ARouter使用指南ARouter拦截器SchemeFilte 实现外部html 跳转Native,打通WEB&Native了解 「JavaPoet」 &AutoService 注解处理器 apt原理写出简易版CRouter,通过实战我们了解ARouter实现原理项目demo地址问题除了ARouter,你知道利用apt 实现的框架都有哪些?ARouter有没有什么缺点?引用https://github.com/alibaba/ARouterhttps://github.com/square/javapoethttps://github.com/google/autohttps://github.com/Oslanka/statichtml.github.iohttps://github.com/Oslanka/ArouterDemo
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-26 12:08 , Processed in 0.716285 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表