我過我要的生活
不是生活過我就好
只要是我想要的
期待多久都有情調(diào)
開篇推薦一首歌曲,送給一直默默奮斗、堅持,但有時又感到迷茫的你。猛戳《過我的生活》
項目GitHub地址:https://github.com/ms-liu/AndroidRemoteDemo
一、項目為什么要分成多Module
先感受下傳統(tǒng)的兩種劃分模式:業(yè)務(wù)功能分包、不同組件分包。


這兩種分包都是放在一個App這一個Module里面的。
每次編譯往往都是需要將項目的所有業(yè)務(wù)都編譯一遍,過程繁重。對于單元測試也只是想想就好,因為和普通測試根本沒差。另外經(jīng)常在開發(fā)人員之間發(fā)生的事情,就是為誰修改了誰的代碼而發(fā)生互懟事件 [斜眼笑]。
總體體現(xiàn):
- 編譯困難
- 單元測試困難
- 開發(fā)、維護困難
因此將項目分成多個Module是很有必要的,這樣可以:
- 提高靈活性——每次只需要編譯我們自己想要的Module即可,同時提升編譯速度。
- 方便單元測試——可以輕松的針對每個Module作出單元測試。
- 讓職責分工明確——可以更好的讓開發(fā)人員去維護各自的模塊

二、解決Module之間頁面跳轉(zhuǎn)問題
1、問題分析
項目被分成多Module之后,由于項目業(yè)務(wù)往往是交叉的,所以Module當中包含的頁面跳轉(zhuǎn)往往也是相互的。
為了能夠讓頁面Module之間的頁面能夠相互跳轉(zhuǎn),我們往往需要將每個Module之間相互compile,這樣無異于又增加了模塊之間耦合性,并且還有可能會報出一個循環(huán)編譯的警告(warning recycle compile)

2、問題解決
通過參考Web頁面的跳轉(zhuǎn)方式,不難想到的是我們完全可以仿照Web跳轉(zhuǎn),也先去為每一個Activity注冊一個地址,然后,然后.....(PS:然后就沒有思路了,MDZZ)。這時看圖一張:

首先去關(guān)心每一個需要被其它Module調(diào)用的頁面,我們將它配置到Common Module中的Remote Configure文件。
下面我們就是在Remote模塊當中編寫一些具體Router Operator類。下面我們就需要思考對一個路由地址究竟需要進行怎樣的處理了。
- 我們需要拿到這個地址(注冊)——通過put/add方法
- 我們需要讓路由地址生效(調(diào)用)——通過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);
/**
* 執(zhí)行路由路線
* @param context Context
* @param uri 路由地址
* @return {@link BaseIntentOperator#invoke(Context, String)}
*/
K invoke(Context context,String uri);
/**
* 檢查當前路由路線 是否存在
* @param uri 路由地址
* @return
*/
boolean check(String uri);
}
(二)基礎(chǔ)實現(xiàn)類
因為是Demo講解,所以只是針對Intent這一種
/**
* ==============================================
* <p>
* 類名:BaseIntentOperator
*   返回類型是Intent的基礎(chǔ)操作類
* <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這種協(xié)議,在Demo中是預(yù)留,因為沒有處理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 路由協(xié)議 {@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);
}
/**
* 根據(jù)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 路由協(xié)議
* @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中調(diào)用,并執(zhí)行跳轉(zhuǎn)
Intent invoke = Remote.invoke(MainActivity.this, ActivityIntentOperator.PROTOCOL + IRemoteUrlConfig.LOGIN_REMOTE_URL);
startActivity(invoke);
至此,我們已經(jīng)完成了一個路由框架,已經(jīng)可以解決多Module之間,頁面跳轉(zhuǎn)問題。
(三)拔高與提升
如果已經(jīng)消化完上面內(nèi)容,那么就可以再跟隨我們做一些拔高與提升了。
下面我們將會使整個框架提升到通過注解Annotation的形式,來完成所有的工作,達到簡化使用的目的。
1、創(chuàng)建RemoteAnnotation Module
- 創(chuàng)建注解@Module——指明當前頁面所在Module
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Module {
String value()default "";
}
- 創(chuàng)建注解@Modules——指明當前有多少個Module
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Modules {
String[]value();
}
2、創(chuàng)建RemoteCompiler Module
- 創(chuàng)建配置文件
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";
}
- 創(chuàng)建RemoteProcessor——注解處理類
對此處不太了解的可以移步到 http://hannesdorfmann.com/annotation-processing/annotationprocessing101
@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();
}
/**
* 指明需要關(guān)心的注解
* 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());
}
}
/**
* 創(chuàng)建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();
}
}
/**
* 創(chuàng)建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());
}
}
}
- 創(chuàng)建反射調(diào)用類
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類——添加兩個自動處理方法
/**
* 根據(jù)默認規(guī)則自動解析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路由跳轉(zhuǎn)
* @param context Context
* @param uri 路由地址
* @param invokeCallback 調(diào)用回調(diào),處理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、具體調(diào)用
- 將兩個Module編譯到每個模塊中
//remoteannotation
compile project(':remoteannotation')
//remote
compile project(':remote')
//compiler
compile project(':remotecompiler')
- 在App模塊的Application中調(diào)用注冊
@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 {
- 調(diào)用
btnProduct.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Remote.startActivity(
MainActivity.this,
ActivityIntentOperator.PROTOCOL + RemoteUrlConfig.PRODUCT_REMOTE_URL,
new BaseInvokeCallback<Intent>());
}
});
****
####四、總結(jié)
通過多Module的劃分,可以很好解決多人協(xié)作開發(fā)沖突問題,通過路由方式是很好的解決了,由于采用多Module劃分,產(chǎn)生的耦合問題。并且讓頁面跳轉(zhuǎn)邏輯更加清晰。所以相對來說本人還是比較推崇,因為無論是**解耦程度**,**可擴展性**,**可維護性**都得到了極大的**提升**。使用和學習成本都不高。當然目前這個是Demo項目用于講解,如果真的想開發(fā)實際項目還需進一步完善,或者去搜集大手寫的路由框架
#### 實際代碼請下載或者Frok項目,若果能給start那就萬分感謝。
#### [項目GitHub地址:https://github.com/ms-liu/AndroidRemoteDemo](https://github.com/ms-liu/AndroidRemoteDemo)
####歡迎大家給出中肯的建議和提高意見,你的鼓勵將是我最大的動力。
#### 個人郵箱:ms_liu163@163.com