我过我要的生活
不是生活过我就好
只要是我想要的
期待多久都有情调
开篇推荐一首歌曲,送给一直默默奋斗、坚持,但有时又感到迷茫的你。猛戳《过我的生活》
项目GitHub地址:https://github.com/ms-liu/AndroidRemoteDemo
一、项目为什么要分成多Module
先感受下传统的两种划分模式:业务功能分包、不同组件分包。
业务功能划分
不同组件划分
这两种分包都是放在一个App这一个Module里面的。
每次编译往往都是需要将项目的所有业务都编译一遍,过程繁重。对于单元测试也只是想想就好,因为和普通测试根本没差。另外经常在开发人员之间发生的事情,就是为谁修改了谁的代码而发生互怼事件 [斜眼笑]。
总体体现:
- 编译困难
- 单元测试困难
- 开发、维护困难
因此将项目分成多个Module是很有必要的,这样可以:
- 提高灵活性——每次只需要编译我们自己想要的Module即可,同时提升编译速度。
- 方便单元测试——可以轻松的针对每个Module作出单元测试。
- 让职责分工明确——可以更好的让开发人员去维护各自的模块
多Module划分
二、解决Module之间页面跳转问题
1、问题分析
项目被分成多Module之后,由于项目业务往往是交叉的,所以Module当中包含的页面跳转往往也是相互的。
为了能够让页面Module之间的页面能够相互跳转,我们往往需要将每个Module之间相互compile,这样无异于又增加了模块之间耦合性,并且还有可能会报出一个循环编译的警告(warning recycle compile)
一般多模块之间关系
2、问题解决
通过参考Web页面的跳转方式,不难想到的是我们完全可以仿照Web跳转,也先去为每一个Activity注册一个地址,然后,然后…..(PS:然后就没有思路了,MDZZ)。这时看图一张:
多Module路由关系
首先去关心每一个需要被其它Module调用的页面,我们将它配置到Common Module中的Remote Configure文件。
下面我们就是在Remote模块当中编写一些具体Router Operator类。下面我们就需要思考对一个路由地址究竟需要进行怎样的处理了。
- 我们需要拿到这个地址(注册)——通过put/add方法
- 我们需要让路由地址生效(调用)——通过invoke方法
3、具体代码编写
(一)定义操作接口
核心方法就是put和invoke
/**
* ==============================================
* <p>
* 类名:IOperator
*   基本操作接口
* <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 *   返回类型是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 *   针对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项目用于讲解,如果真的想开发实际项目还需进一步完善,或者去搜集大手写的路由框架