带你打造一个多 Module 路由框架

我过我要的生活
不是生活过我就好
只要是我想要的
期待多久都有情调               

开篇推荐一首歌曲,送给一直默默奋斗、坚持,但有时又感到迷茫的你。猛戳《过我的生活》

项目GitHub地址:https://github.com/ms-liu/AndroidRemoteDemo

一、项目为什么要分成多Module

  先感受下传统的两种划分模式:业务功能分包、不同组件分包。

《带你打造一个多 Module 路由框架》

业务功能划分
《带你打造一个多 Module 路由框架》

不同组件划分

  这两种分包都是放在一个App这一个Module里面的。
  每次编译往往都是需要将项目的所有业务都编译一遍,过程繁重。对于单元测试也只是想想就好,因为和普通测试根本没差。另外经常在开发人员之间发生的事情,就是为谁修改了谁的代码而发生互怼事件 [斜眼笑]
  总体体现:

  • 编译困难
  • 单元测试困难
  • 开发、维护困难

  因此将项目分成多个Module是很有必要的,这样可以:

  • 提高灵活性——每次只需要编译我们自己想要的Module即可,同时提升编译速度。
  • 方便单元测试——可以轻松的针对每个Module作出单元测试。
  • 让职责分工明确——可以更好的让开发人员去维护各自的模块

《带你打造一个多 Module 路由框架》

多Module划分

二、解决Module之间页面跳转问题

1、问题分析

  项目被分成多Module之后,由于项目业务往往是交叉的,所以Module当中包含的页面跳转往往也是相互的。
  为了能够让页面Module之间的页面能够相互跳转,我们往往需要将每个Module之间相互compile,这样无异于又增加了模块之间耦合性,并且还有可能会报出一个循环编译的警告(warning recycle compile)

《带你打造一个多 Module 路由框架》

一般多模块之间关系

2、问题解决

  通过参考Web页面的跳转方式,不难想到的是我们完全可以仿照Web跳转,也先去为每一个Activity注册一个地址,然后,然后…..(PS:然后就没有思路了,MDZZ)。这时看图一张:

《带你打造一个多 Module 路由框架》

多Module路由关系

  首先去关心每一个需要被其它Module调用的页面,我们将它配置到Common Module中的Remote Configure文件。
  下面我们就是在Remote模块当中编写一些具体Router Operator类。下面我们就需要思考对一个路由地址究竟需要进行怎样的处理了。

  • 我们需要拿到这个地址(注册)——通过put/add方法
  • 我们需要让路由地址生效(调用)——通过invoke方法
3、具体代码编写
(一)定义操作接口

核心方法就是put和invoke

/**
 * ==============================================
 * <p>
 * 类名:IOperator
 * &nbsp 基本操作接口
 * <p>
 * 作者:M-Liu
 * <p>
 * 时间:2017/3/27
 * <p>
 * 邮箱:ms_liu163@163.com
 * <p>
 * ==============================================
 */

public interface IOperator<T,K> {
    /**
     * 添加路由地址
     * @param uri 路由地址
     * @param clazz 路由类型
     */
    void put(String uri,Class<T> clazz);

    /**
     * 执行路由路线
     * @param context Context
     * @param uri 路由地址
     * @return {@link BaseIntentOperator#invoke(Context, String)}
     */
    K invoke(Context context,String uri);

    /**
     * 检查当前路由路线 是否存在
     * @param uri 路由地址
     * @return
     */
    boolean check(String uri);
}
(二)基础实现类

因为是Demo讲解,所以只是针对Intent这一种

/** * ============================================== * <p> * 类名:BaseIntentOperator * &nbsp 返回类型是Intent的基础操作类 * <p> * 作者:M-Liu * <p> * 时间:2017/3/27 * <p> * 邮箱:ms_liu163@163.com * <p> * ============================================== */

public abstract class BaseIntentOperator<T> implements IOperator<T,Intent> {
    private HashMap<String,Class<T>> mIntentContainer;
    public BaseIntentOperator(){
        mIntentContainer = new LinkedHashMap<>();
    }


    /** * {@inheritDoc} */
    @Override
    public void put(String uri, Class<T> clazz) {
        if (mIntentContainer != null){
            mIntentContainer.put(uri,clazz);
        }
    }

    /** * {@inheritDoc} */
    @Override
    public Intent invoke(Context context, String uri) {
        Class<T> clazz = null;
        if (check(uri)){
            clazz = mIntentContainer.get(uri);
        }
        if (clazz == null){
            throwException(uri);
        }
        return new Intent(context,clazz);
    }

    public abstract void throwException(String uri);

    /** * {@inheritDoc} */
    @Override
    public boolean check(String uri) {
        return mIntentContainer != null && mIntentContainer.keySet().contains(uri);
    }
}
(三)具体Activity这种处理

  当中定义了PROTOCOL 常量,相当于Http这种协议,在Demo中是预留,因为没有处理Service等其它组件的情况,所以没有用到。

/** * ============================================== * <p> * 类名:ActivityIntentOperator * &nbsp 针对Activity路由操作 * <p> * 作者:M-Liu * <p> * 时间:2017/3/28 * <p> * 邮箱:ms_liu163@163.com * <p> * ============================================== */

public class ActivityIntentOperator extends BaseIntentOperator<AppCompatActivity> {
    public static final String PROTOCOL = "activity://";

    @Override
    public void throwException(String uri) {
        throw new NotFoundRuleException(ActivityIntentOperator.class.getCanonicalName(),uri);
    }
}
(四)Manager管理者编写
public class RemoteOperatorManager {

    private static RemoteOperatorManager mRemoteManager;
    //路由操作管理池
    private HashMap<String,IOperator> mOperatorPool;
    private RemoteOperatorManager(){
        mOperatorPool = new LinkedHashMap<>();
        putDefaultOperator();
    }

    //初始化默认路由操作
    private void putDefaultOperator() {
        if (mOperatorPool != null){
            mOperatorPool.put(ActivityIntentOperator.PROTOCOL,new ActivityIntentOperator());
        }
    }

    /** * 获取RemoteOperatorManager * @return RemoteOperatorManager */
    public static RemoteOperatorManager get(){
        if (mRemoteManager == null){
            synchronized (RemoteOperatorManager.class){
                mRemoteManager = new RemoteOperatorManager();
            }
        }
        return mRemoteManager;
    }

    /** * 添加自定义 路由操作 * @param protocol 路由协议 {@link ActivityIntentOperator#PROTOCOL} * @param operator 具体操作类 */
    public RemoteOperatorManager putCustomOperator(String protocol,IOperator operator){
        if (mOperatorPool != null){
            mOperatorPool.put(protocol,operator);
        }
        return mRemoteManager;
    }


    /** * 检查当前路由操作 是否存在 * @param uri 路由地址 * @return false 不存在 true 存在 */
    public boolean checkOperatorForURI(String uri){
        if (!TextUtils.isEmpty(uri)){
            IOperator<?, ?> operator = getOperator(uri);
            if (operator == null){
                throw new NotFoundRuleException(uri);
            }
            return true;
        }else {
            throw new NotFountRemotePathException();
        }
    }

    public boolean checkOpratorForProtocol(String protocol){
        return mOperatorPool != null && mOperatorPool.keySet().contains(protocol);
    }

    /** * 根据Uri获取路由操作类 * @param uri 路由地址 */
    public  <T,V> IOperator<T,V> getOperator(String uri){
        IOperator<T,V> operator = null;
        if (mOperatorPool != null){
            Set<String> protocols = mOperatorPool.keySet();
            for (String protocol :
                    protocols) {
                if (uri.startsWith(protocol)){
                    operator = mOperatorPool.get(protocol);
                    break;
                }
            }
        }
        return operator;
    }

    public <T> RemoteOperatorManager putRemoteUri(String uri, Class<T> clazz) {
        if (checkOperatorForURI(uri)){
            IOperator<T, ?> operator = getOperator(uri);
            operator.put(uri,clazz);
        }
        return mRemoteManager;
    }
}
(五)代言者——Remote编写

为什么是代言者,是因为其实每一个具体方法都是由Manager去完成的

public class Remote {

    private final static String PATTERN = "";

    /** * 添加自定义路由操作 * @param protocol 路由协议 * @param operator 路由操作类 * @return */
    public static RemoteOperatorManager putCoustomOprator(String protocol, IOperator operator){
        return RemoteOperatorManager.get().putCustomOperator(protocol,operator);
    }

    /** * 添加路由地址 * @param uri 路由地址 * @return */
    public static<T> RemoteOperatorManager putRemoteUri(String uri,Class<T> clazz){
        return RemoteOperatorManager.get().putRemoteUri(uri,clazz);
    }

    /** * 启用路由地址 * @param ctx Context * @param uri 路由地址 * @return */
    public static <V> V invoke(Context ctx, String uri){
        if (checkUri(uri)){
            IOperator<?, V> operator = RemoteOperatorManager.get().getOperator(uri);
            return operator.invoke(ctx,uri);
        }else {
            throw new NotFoundRuleException(uri);
        }
    }

    /** * 路由地址检查 * @param uri 路由地址 * @return */
    public static boolean checkUri(String uri){
        return RemoteOperatorManager.get().checkOperatorForURI(uri);
    }

}
(六)测试
  • 在Application中添加、注册路由

    public class RemoteApp extends Application {
      @Override
      public void onCreate() {
          super.onCreate();
          initRemote();
      }
    
      private void initRemote() {
          Remote.putRemoteUri(ActivityIntentOperator.PROTOCOL+ IRemoteUrlConfig.LOGIN_REMOTE_URL, LoginActivity.class);
    
      }
    }
  • 在MainActivity中调用,并执行跳转
    Intent invoke = Remote.invoke(MainActivity.this, ActivityIntentOperator.PROTOCOL + IRemoteUrlConfig.LOGIN_REMOTE_URL);
    startActivity(invoke);

      至此,我们已经完成了一个路由框架,已经可以解决多Module之间,页面跳转问题。

(三)拔高与提升

  如果已经消化完上面内容,那么就可以再跟随我们做一些拔高与提升了。
  下面我们将会使整个框架提升到通过注解Annotation的形式,来完成所有的工作,达到简化使用的目的。

1、创建RemoteAnnotation Module
  • 创建注解@Module——指明当前页面所在Module
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.CLASS)
    public @interface Module {
     String value()default "";
    }
  • 创建注解@Modules——指明当前有多少个Module
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.CLASS)
    public @interface Modules {
      String[]value();
    }
    2、创建RemoteCompiler Module
  • 创建配置文件
    public class Config {
      public static final String PACKAGE_NAME = "com.mmyz.router";
      public static final String PAGE_PREFIX = "Module_";
      public static final String CLASS_NAME = "AutoRegisterRemote";
      public static final String METHOD_NAME = "autoRegister";
      public static final String PAGE_METHOD_NAME = "autoInvoke";
    }
  • 创建RemoteProcessor——注解处理类
    对此处不太了解的可以移步到 hannesdorfmann.com/annotation-…

    @AutoService(Processor.class)
    public class RemoteProcessor extends AbstractProcessor {
    
      private Filer mFiler;
      private Messager mMessager;
    
      private List<String> mStaticRemoteUriList = new ArrayList<>();
    
      @Override
      public synchronized void init(ProcessingEnvironment processingEnvironment) {
          super.init(processingEnvironment);
          //文件操作
          mFiler = processingEnvironment.getFiler();
          //消息输出
          mMessager = processingEnvironment.getMessager();
      }
    
      /** * 当前java 源码版本 * java compiler version * @return */
      @Override
      public SourceVersion getSupportedSourceVersion() {
          return SourceVersion.latest();
      }
    
      /** * 指明需要关心的注解 * need handle Annotation type * @return */
      @Override
      public 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 */
      @Override
      public 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());
          }
      }
    }
  • 创建反射调用类
    “`
    public 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类——添加两个自动处理方法

      /** * 根据默认规则自动解析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编译到每个模块中
      //remoteannotation
      compile project(':remoteannotation')
      //remote
      compile project(':remote')
      //compiler
      compile project(':remotecompiler')
  • 在App模块的Application中调用注册

    @Modules({
            RemoteModuleConfig.ACCOUNT_MODULE,
            RemoteModuleConfig.PRODUCT_MODULE,
            RemoteModuleConfig.ORDER_MODULE})
    public class RemoteApp extends Application {
      @Override
      public 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注解

    @Module(RemoteModuleConfig.ACCOUNT_MODULE)
    @StaticRemote(ActivityIntentOperator.PROTOCOL+ RemoteUrlConfig.REGISTER_REMOTE_URL)
    public class RegisterActivity extends AppCompatActivity {
  • 调用

          btnProduct.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  Remote.startActivity(
                          MainActivity.this,
                          ActivityIntentOperator.PROTOCOL + RemoteUrlConfig.PRODUCT_REMOTE_URL,
                          new BaseInvokeCallback<Intent>());
              }
          });

四、总结

  通过多Module的划分,可以很好解决多人协作开发冲突问题,通过路由方式是很好的解决了,由于采用多Module划分,产生的耦合问题。并且让页面跳转逻辑更加清晰。所以相对来说本人还是比较推崇,因为无论是解耦程度可扩展性可维护性都得到了极大的提升。使用和学习成本都不高。当然目前这个是Demo项目用于讲解,如果真的想开发实际项目还需进一步完善,或者去搜集大手写的路由框架

实际代码请下载或者Frok项目,若果能给start那就万分感谢。

项目GitHub地址:https://github.com/ms-liu/AndroidRemoteDemo

欢迎大家给出中肯的建议和提高意见,你的鼓励将是我最大的动力。

个人邮箱:ms_liu163@163.com

    原文作者:Android
    原文地址: https://juejin.im/entry/58f427d25c497d006c93f1a1
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞