带你打造一个多Module路由框架
我过我要的生活
不是生活过我就好
只要是我想要的
期待多久都有情调
开篇推荐一首歌曲,送给一直默默奋斗、坚持,但有时又感到迷茫的你。猛戳《过我的生活》
项目GitHub地址:https://github.com/ms-liu/AndroidRemoteDemo
一、项目为什么要分成多Module
先感受下传统的两种划分模式:业务功能分包、不同组件分包。
这两种分包都是放在一个App这一个Module里面的。
每次编译往往都是需要将项目的所有业务都编译一遍,过程繁重。对于单元测试也只是想想就好,因为和普通测试根本没差。另外经常在开发人员之间发生的事情,就是为谁修改了谁的代码而发生互怼事件 [斜眼笑]。
总体体现:
- 编译困难
- 单元测试困难
- 开发、维护困难
因此将项目分成多个Module是很有必要的,这样可以:
- 提高灵活性——每次只需要编译我们自己想要的Module即可,同时提升编译速度。
- 方便单元测试——可以轻松的针对每个Module作出单元测试。
- 让职责分工明确——可以更好的让开发人员去维护各自的模块
二、解决Module之间页面跳转问题
1、问题分析
项目被分成多Module之后,由于项目业务往往是交叉的,所以Module当中包含的页面跳转往往也是相互的。
为了能够让页面Module之间的页面能够相互跳转,我们往往需要将每个Module之间相互compile,这样无异于又增加了模块之间耦合性,并且还有可能会报出一个循环编译的警告(warning recycle compile)
2、问题解决
通过参考Web页面的跳转方式,不难想到的是我们完全可以仿照Web跳转,也先去为每一个Activity注册一个地址,然后,然后…..(PS:然后就没有思路了,MDZZ)。这时看图一张:
首先去关心每一个需要被其它Module调用的页面,我们将它配置到Common Module中的Remote Configure文件。
下面我们就是在Remote模块当中编写一些具体Router Operator类。下面我们就需要思考对一个路由地址究竟需要进行怎样的处理了。
- 我们需要拿到这个地址(注册)——通过put/add方法
- 我们需要让路由地址生效(调用)——通过invoke方法
3、具体代码编写
(一)定义操作接口
核心方法就是put和invoke
|
|
(二)基础实现类
因为是Demo讲解,所以只是针对Intent这一种
(三)具体Activity这种处理
当中定义了PROTOCOL 常量,相当于Http这种协议,在Demo中是预留,因为没有处理Service等其它组件的情况,所以没有用到。
|
|
(四)Manager管理者编写
|
|
(五)代言者——Remote编写
为什么是代言者,是因为其实每一个具体方法都是由Manager去完成的
(六)测试
在Application中添加、注册路由
123456789101112public class RemoteApp extends Application {@Overridepublic void onCreate() {super.onCreate();initRemote();}private void initRemote() {Remote.putRemoteUri(ActivityIntentOperator.PROTOCOL+ IRemoteUrlConfig.LOGIN_REMOTE_URL, LoginActivity.class);}}在MainActivity中调用,并执行跳转
12Intent invoke = Remote.invoke(MainActivity.this, ActivityIntentOperator.PROTOCOL + IRemoteUrlConfig.LOGIN_REMOTE_URL);startActivity(invoke);
至此,我们已经完成了一个路由框架,已经可以解决多Module之间,页面跳转问题。
(三)拔高与提升
如果已经消化完上面内容,那么就可以再跟随我们做一些拔高与提升了。
下面我们将会使整个框架提升到通过注解Annotation的形式,来完成所有的工作,达到简化使用的目的。
1、创建RemoteAnnotation Module
- 创建注解@Module——指明当前页面所在Module12345@Target(ElementType.TYPE)@Retention(RetentionPolicy.CLASS)public @interface Module {String value()default "";}
- 创建注解@Modules——指明当前有多少个Module12345@Target(ElementType.TYPE)@Retention(RetentionPolicy.CLASS)public @interface Modules {String[]value();}
2、创建RemoteCompiler Module
- 创建配置文件
|
|
创建RemoteProcessor——注解处理类
对此处不太了解的可以移步到 http://hannesdorfmann.com/annotation-processing/annotationprocessing101123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178@AutoService(Processor.class)public class RemoteProcessor extends AbstractProcessor {private Filer mFiler;private Messager mMessager;private List<String> mStaticRemoteUriList = new ArrayList<>();@Overridepublic synchronized void init(ProcessingEnvironment processingEnvironment) {super.init(processingEnvironment);//文件操作mFiler = processingEnvironment.getFiler();//消息输出mMessager = processingEnvironment.getMessager();}/*** 当前java 源码版本* java compiler version* @return*/@Overridepublic SourceVersion getSupportedSourceVersion() {return SourceVersion.latest();}/*** 指明需要关心的注解* need handle Annotation type* @return*/@Overridepublic Set<String> getSupportedAnnotationTypes() {Set<String> types = new HashSet<>();types.add(Modules.class.getCanonicalName());types.add(Module.class.getCanonicalName());types.add(StaticRemote.class.getCanonicalName());return types;}/*** 具体处理* @param set* @param re* @return*/@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment re) {//清除URL维护集合mStaticRemoteUriList.clear();try {Set<? extends Element> modules = re.getElementsAnnotatedWith(Modules.class);if (modules != null && !modules.isEmpty()){patchModulesClass(modules);return true;}processModule(re);}catch (Exception e){mMessager.printMessage(Diagnostic.Kind.NOTE,e.getMessage());}return true;}private void processModule(RoundEnvironment re) {try {Set<? extends Element> staticElementSet = re.getElementsAnnotatedWith(StaticRemote.class);if (staticElementSet != null && !staticElementSet.isEmpty()) {for (Element e :staticElementSet) {if (!(e instanceof TypeElement)) {continue;}TypeElement te = (TypeElement) e;mStaticRemoteUriList.add(te.getAnnotation(StaticRemote.class).value());}}Set<? extends Element> module = re.getElementsAnnotatedWith(Module.class);patchModuleClass(module);}catch (Exception e) {mMessager.printMessage(Diagnostic.Kind.NOTE,e.getMessage());}}/*** 创建class文件* create class** package com.mmyz.router;** public class Page_Login(){* public static autoInvoke(){* Remote.putRemoteUriDefaultPattern("activity://com.mmyz.account.LoginActivity");* }* }**/private void patchModuleClass(Set<? extends Element> module) {try {if (module == null || module.isEmpty())return;mMessager.printMessage(Diagnostic.Kind.NOTE,module.toString());Element next = module.iterator().next();Module annotation = next.getAnnotation(Module.class);String pageName = annotation.value();String className = Config.PAGE_PREFIX+pageName;JavaFileObject file = mFiler.createSourceFile(className, next);PrintWriter printWriter = new PrintWriter(file.openWriter());printWriter.println("package "+ Config.PACKAGE_NAME +";");printWriter.println("import "+ Config.PACKAGE_NAME+".Remote;");printWriter.println("import "+ Config.PACKAGE_NAME+".exception.NotFoundClassException;");printWriter.println("public class "+className +" {");printWriter.println("public static void "+ Config.PAGE_METHOD_NAME+"(){");printWriter.println("try{");for (String uri :mStaticRemoteUriList) {printWriter.println("Remote.putRemoteUriDefaultPattern(\""+uri+"\");");}printWriter.println("}catch(NotFoundClassException e){");printWriter.println("e.printStackTrace();");printWriter.println("}");printWriter.println("}");printWriter.println("}");printWriter.flush();printWriter.close();} catch (Exception e) {e.printStackTrace();}}/*** 创建class文件* Create class** package com.mmyz.remote;* public class AutoRegisterRemote{* public void autoRegister(){* Page_Login.autoInvoke();* }* }*/private void patchModulesClass(Set<? extends Element> modules) {try {TypeElement moduleTypeElement= (TypeElement) modules.iterator().next();JavaFileObject file = mFiler.createSourceFile(Config.CLASS_NAME, moduleTypeElement);PrintWriter writer = new PrintWriter(file.openWriter());writer.println("package "+ Config.PACKAGE_NAME+";");writer.println("public class "+ Config.CLASS_NAME +" {");writer.println("public static void "+ Config.METHOD_NAME +" () {");Modules modulesAnnotation = moduleTypeElement.getAnnotation(Modules.class);String[] value = modulesAnnotation.value();for (String item :value) {writer.println(Config.PACKAGE_NAME+"."+ Config.PAGE_PREFIX+item+"."+ Config.PAGE_METHOD_NAME +"();");}writer.println("}");writer.println("}");writer.flush();writer.close();} catch (IOException e) {e.printStackTrace();mMessager.printMessage(Diagnostic.Kind.ERROR,e.getMessage());}}}创建反射调用类
1234567891011121314151617public class RemoteRegister {public static void register(){try {Class<?> clazz = Class.forName(Config.PACKAGE_NAME + "." + Config.CLASS_NAME);Method method = clazz.getDeclaredMethod(Config.METHOD_NAME);method.invoke(null);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}}改进Remote类——添加两个自动处理方法
123456789101112131415161718192021222324252627282930313233/*** 根据默认规则自动解析Uri* @param uri 路由地址 Ac*/public static RemoteOperatorManager putRemoteUriDefaultPattern(String uri) throws NotFoundClassException {// (activity://com.mmyz.account.LoginActivity)Pattern pattern = Pattern.compile("[/]+");String[] infos = pattern.split(uri);String protocol = infos[0];String page = infos[1];try {putRemoteUri(uri,Class.forName(page));} catch (ClassNotFoundException e) {throw new NotFoundClassException(page, uri);}return RemoteOperatorManager.get();}/*** Activity路由跳转* @param context Context* @param uri 路由地址* @param invokeCallback 调用回调,处理Intent传值*/public static void startActivity(Context context,String uri,BaseInvokeCallback<Intent> invokeCallback){Intent intent = invoke(context, uri);intent = invokeCallback.invokeCallback(intent);if (intent != null){context.startActivity(intent);}else {throw new NotFoundIntentException();}}
3、具体调用
将两个Module编译到每个模块中
123456//remoteannotationcompile project(':remoteannotation')//remotecompile project(':remote')//compilercompile project(':remotecompiler')在App模块的Application中调用注册
12345678910111213141516171819202122@Modules({RemoteModuleConfig.ACCOUNT_MODULE,RemoteModuleConfig.PRODUCT_MODULE,RemoteModuleConfig.ORDER_MODULE})public class RemoteApp extends Application {@Overridepublic void onCreate() {super.onCreate();initRemote();}private void initRemote() {// Remote.putRemoteUri(ActivityIntentOperator.PROTOCOL+ IRemoteUrlConfig.LOGIN_REMOTE_URL, LoginActivity.class);RemoteRegister.register();// try {// Remote.putRemoteUriDefaultPattern(ActivityIntentOperator.PROTOCOL+ LoginActivity.REMOTE_URL);// } catch (NotFoundClassException e) {// Log.e("=========",e.getMessage());// e.printStackTrace();// }}}在被需要的页面添加@Module和@StaticRemote注解
123@Module(RemoteModuleConfig.ACCOUNT_MODULE)@StaticRemote(ActivityIntentOperator.PROTOCOL+ RemoteUrlConfig.REGISTER_REMOTE_URL)public class RegisterActivity extends AppCompatActivity {调用
123456789btnProduct.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Remote.startActivity(MainActivity.this,ActivityIntentOperator.PROTOCOL + RemoteUrlConfig.PRODUCT_REMOTE_URL,new BaseInvokeCallback<Intent>());}});
四、总结
通过多Module的划分,可以很好解决多人协作开发中代码冲突问题。另外通过路由方式是也可以很好的解决在采用多Module划分后,页面之间的跳转问题,同时让页面跳转逻辑更加清晰。所以相对来说本人还是比较推崇,因为无论是解耦程度,可扩展性,可维护性都得到了极大的提升。使用和学习成本都不高。当然目前这个是Demo项目用于讲解,如果真的想开发实际项目还需进一步完善,或者去搜集大手写的路由框架