組件化后不同模塊之間Activity的跳轉(zhuǎn)
組件化后,只有主工程模塊依賴其他業(yè)務(wù)模塊,而各個業(yè)務(wù)模塊之間沒有互相依賴關(guān)系。一個模塊可以調(diào)用被依賴模塊的類和方法,而被依賴的模塊不能引用依賴模塊的類(依賴只能單向傳遞)。所以,單個業(yè)務(wù)模塊無法調(diào)用主模塊和其他業(yè)務(wù)模塊的類和方法,那么業(yè)務(wù)間Activity的跳轉(zhuǎn)就需要使用隱式跳轉(zhuǎn):
- 直接跳轉(zhuǎn)包名:
startActivity(new Intent(“com.example.boost”)) - 使用manifest中的action、category、data進行隱式跳轉(zhuǎn):
startActivity(new Intent(Intent.ACTION_VIEW, Uri.prase("xxxx"))) - 利用反射
以上這些方法,雖然都能實現(xiàn)不同模塊間的Activity跳轉(zhuǎn)。但是,隱式跳轉(zhuǎn)容易被非法應(yīng)用劫持,反射最大的弊端在于代碼結(jié)構(gòu)發(fā)生變化后,就需要修改相應(yīng)的反射路徑。
目前比較推薦的方法就是使用一套統(tǒng)一的路由框架,所有的業(yè)務(wù)模塊、主模塊都依賴于這個路由模塊,路由模塊中包含所有Activity的引用,這樣,每個模塊都可以互相調(diào)用不同的模塊的Activity。

搭建路由模塊
路由模塊需要包含所有Activity類的引用,并能進行跳轉(zhuǎn),所有需要建立一個Android Library。
[圖片上傳失敗...(image-5ab90b-1571132614922)]
Route路由類的設(shè)計:
- Route類作為路由需要在每個模塊中可以直接調(diào)用,那最方便的方法就是使用單例模式
- Route中要包含所有Activity的引用,那么就需要使用一個map將Activity和對應(yīng)的名稱關(guān)聯(lián)起來
- 在使用路由進行頁面跳轉(zhuǎn)的時候需要有一個方法,方法要傳入待跳轉(zhuǎn)的Activity的名稱和需要的參數(shù)
- Route需要一個方法,將Activity的引用和對應(yīng)的名稱傳入路由中
具體的代碼如下:
package com.example.route;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import dalvik.system.DexFile;
/**
* @Description: 路由
* @Author: jiazhu
* @Date: 2019-09-19 09:29
* @ClassName: Route
*/
public class Route {
private static Route route = new Route();
private Context mContext;
private Map<String, Class<? extends Activity>> activityList;
private Route() {
activityList = new HashMap<>();
}
public static Route getInstance() {
return route;
}
public void init(Application application) {
mContext = application;
List<String> classNames = getClassName("com.example.util");
for (String s : classNames) {
try {
Class<?> aClass = Class.forName(s);
if (IRoute.class.isAssignableFrom(aClass)) {
IRoute iRoute = (IRoute) aClass.newInstance();
iRoute.putActivity();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private List<String> getClassName(String packageName) {
ArrayList<String> classList = new ArrayList<>();
String path = null;
try {
path = mContext.getPackageManager().getApplicationInfo(mContext.getPackageName(), 0).sourceDir;
DexFile dexFile = new DexFile(path);
Enumeration<String> entries = dexFile.entries();
while (entries.hasMoreElements()) {
String name = entries.nextElement();
if (name.contains(packageName)) {
classList.add(name);
}
}
return classList;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public void putActivity(String path, Class<? extends Activity> clazz) {
if (TextUtils.isEmpty(path)) {
return;
}
if (clazz == null) {
return;
}
activityList.put(path, clazz);
}
public void jumpActivity(String path, Bundle bundle) {
Class<? extends Activity> aClass = activityList.get(path);
if (aClass == null) {
return;
}
Intent intent = new Intent().setClass(mContext, aClass);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (bundle != null) {
intent.putExtra("bundle", bundle);
}
mContext.startActivity(intent);
}
}
對于init方法,在整個應(yīng)用啟動的時候需要被調(diào)用,這樣可以獲取Application作為以后跳轉(zhuǎn)的context上下文;除此之外,還需要將每一個模塊中的Activity獲取到,傳入Route中。
Route中有一個putActivity()的方法,這個方法可以將Activity的引用和名稱傳入Route中,那么在每一個模塊中如何才能調(diào)用這個方法,將本模塊的Activity傳遞給Route呢?
使用APT(注解處理工具)將每一個模塊的Activity傳入Route路由
APT全名:Annotation Processiong Tool。
使用APT需要有兩個部分組成,一個是注解,另一個是處理注解。
1. 注解
這里不需要Android相關(guān)的內(nèi)容,所有新建一個Java Library:

這個模塊中只需要創(chuàng)建注解類。
一個注解類主要有以下幾個部分構(gòu)成:
-
@Target()指定這個注解是針對哪類數(shù)據(jù)進行注解:TYPE:類;FIELD:元素;METHOD:方法;PARAMETER:參數(shù)... -
@Retention()指定這個注解的生效階段:SOURCE:在源碼階段有效;CLASS:在編譯期有效;RUNTIME:在運行時有效 -
@interface xxx{}注解類名稱 - 注解的方法
針對路由框架,實現(xiàn)的注解如下:
/**
* @author jiazhu
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface BindPath {
String value();
}
可以看出:
- 這個注解類是針對類進行注解的
- 注解在編譯期有效
- 注解BindPath中有一個方法:value(),這個方法獲取一個String類型值
BindPath獲取的值作為Activity的名稱,Activity的引用可以在后面通過注解處理器進行獲取。
2.處理注解
處理注解也是不需要Android相關(guān)的內(nèi)容,所有新建一個Java Library。
做一個注解處理器的類,需要集成AbstractProcessor注解處理器抽象類。
在處理注解之前,需要實現(xiàn)三個方法:
-
初始化這個注解處理器,并拿到一個創(chuàng)建文件的對象:
@Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); mFiler = processingEnvironment.getFiler(); } -
配置需要處理的注解類
@Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new HashSet<>(); types.add(BindPath.class.getCanonicalName()); return types; } -
設(shè)置支持的代碼版本
@Override public SourceVersion getSupportedSourceVersion() { return processingEnv.getSourceVersion(); }
然后需要重寫父類的process方法,實現(xiàn)注解的處理。處理的步驟如下:
- 獲取需要處理的被注解標(biāo)注的對象,并獲取對象的注解
- 將對象的名稱和注解方法返回的值提取出來(對象名稱就是Activity,注解方法的返回值就是Activity的名稱)
- 通過Filer創(chuàng)建每個模塊對應(yīng)的添加路由的類,并用Writer寫入剛才通過注解獲取的Activity和名稱。
最終代碼如下:
/**
* @author jiazhu
*/
@AutoService(Processor.class)
public class AnnotationCompiler extends AbstractProcessor {
Filer mFiler;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(BindPath.class.getCanonicalName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return processingEnv.getSourceVersion();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindPath.class);
HashMap<String, String> map = new HashMap<>();
String packageName = null;
for (Element element : elements) {
if (element instanceof TypeElement) {
TypeElement typeElement = (TypeElement) element;
BindPath annotation = typeElement.getAnnotation(BindPath.class);
String path = annotation.value();
String activityName = typeElement.getQualifiedName().toString();
if (packageName == null) {
packageName = activityName.substring(0, activityName.lastIndexOf("."));
packageName = packageName.replace('.', '_');
}
map.put(path, activityName);
}
}
if (map.size() > 0) {
Writer writer = null;
String utilName = "ActivityUtil_" + packageName;
try {
JavaFileObject javaFileObject = mFiler.createSourceFile("com.example.util." + utilName);
writer = javaFileObject.openWriter();
Iterator<String> iterator = map.keySet().iterator();
writer.write("package com.example.util;\n" +
"\n" +
"import com.example.route.IRoute;\n" +
"import com.example.route.Route;\n" +
"\n" +
"public class " + utilName + " implements IRoute {\n" +
" @Override\n" +
" public void putActivity() {\n");
while (iterator.hasNext()) {
String path = iterator.next();
String value = map.get(path);
writer.write("Route.getInstance().putActivity(\"" + path + "\"," + value + ".class);\n");
}
writer.write(" }\n" +
"}\n");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return false;
}
}
通過注解,將各個模塊的Activity添加注解,然后對代碼進行一次編譯。
在每一個模塊生成的代碼中會多出來一個類,這個類會將這個模塊中的所有已注解的Activity和它們的名稱傳入Route類中。

當(dāng)我們跳轉(zhuǎn)某個模塊的Activity的時候,只需要如下代碼:
Route.getInstance().jumpActivity( "mapp/main", null);