在路由框架之前,我們先了解什麼是APT,並實踐ButterKnife繫結findById的小功能。為什麼先要講解apt,因為路由的實現apt是核心的程式碼.看下面連結 APT 實踐。
為什麼需要路由
我們知道路由就是實現頁面的跳轉,然而Android原生已經支援app頁面間的跳轉。
一般來說我們會這樣寫:
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("dataKey", "dataValue");
startActivity(intent);
複製程式碼
如果在封裝一層這樣寫:
public class ManagerActivity extends BaseActivity {
public static void launch(Context context, int startTab) {
Intent i = new Intent(context, ManagerActivity.class);
i.putExtra(INTENT_IN_INT_START_TAB, startTab);
context.startActivity(i);
}
}
複製程式碼
其實無論那種實現方式,本質上都是生成Intent,然後通過context.startActivity/context.startActivityForResult來實現頁面的跳轉。但是這種方法的不足是:當包含多個模組,且模組之間沒有相互依賴的跳轉就會變的很困難,如果知道模組對應的類名和路徑,可以通過Intent.setComponent(Component)方法啟動其他模組的頁面。同時還要寫大量重複程式碼,要記錄每個模組的類名和路徑,如果類名和路徑發生改變的話。。。。。。我相信你的內心肯定是崩潰的。
存在的一些缺點:
- 顯示Intent:專案龐大以後,類依賴耦合太大,不適合元件化拆分
- 隱式Intent:協作困難,呼叫時候不知道調什麼引數
- 每個註冊了Scheme的Activity都可以直接開啟,有安全風險
- AndroidMainfest集中式管理比較臃腫
- 無法動態修改路由,如果頁面出錯,無法動態降級
- 無法動態攔截跳轉,譬如未登入的情況下,開啟登入頁面,登入成功後接著開啟剛才想開啟的頁面
- H5、Android、iOS地址不一樣,不利於統一跳轉
頁面路由的意義:
路由最先被應用於網路中,路由的定義可以通過互聯的網路把資訊從源地址傳輸到目的地址的過程。每個頁面可以定義為一個統一的資源標示符,在網路當中能夠被別人訪問,也可以訪問已經被定義了的頁面。
路由常見的使用場景:
- App接收到一個通知,點選通知開啟App的某個頁面
- 瀏覽器App中點選某個連結開啟App的某個頁面
- App的H5活動頁面開啟一個連結,可能是H5跳轉,也可能是跳轉到某一個native頁面
- 開啟頁面需要某些條件,先驗證完條件,再去開啟那個頁面(如驗證是否登入)
- 不合法的開啟App的頁面被遮蔽掉
- App內的跳轉,可以減少手動構建Intent的成本,同時可以統一攜帶部分引數到下一個頁面
- App存在就開啟頁面,不存在就去下載頁面下載,只有Google的App Link支援
路由框架實現思路
通過上述的路由的應用和APT開發,相信你對APT有了一定的瞭解,那麼路由框架要如何實現呢?實現思路是怎樣的?
路由設計的思路
- 通過註解 Activity 類,註解處理器處理註解(APT)動態生成路由資訊。
- 收集路由:通過定義的包名,找到所有動態生成的類,將路由資訊儲存到本地倉庫 (rootMap).
- 頁面跳轉:根據註解的路由地址,從本地倉庫中找到相關的路由資訊,獲取到要跳轉的類,然後實現跳轉。
只需要在使用路由的頁面定義一個類註解@Router即可,頁面路由的使用也相當簡單
下面我們看下具體程式碼的實現思路
- 首先實現自定義註解,新建一個java lib 取名:primrouter-annotation
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Router {
/**
* 設定路由地址/main/text
* @return 地址
*/
String path();
/**
* 設定路由組,若為空以路由地址的第一個來作為組名main
* @return 路由組
*/
String group() default "";
}
複製程式碼
- 然後我們要清楚動態生成的類,其實主要就是儲存一些跳轉的Activity的類資訊和路由地址等,同時為了邏輯更加清晰,給不同的module的進行分組。當路由跳轉的時候可以通過路由group 得到分組表,然後通過路由地址path得到分組表中儲存的路由物件,來實現跳轉。
如以下自動生成的類:
module 儲存的分組資訊
public class PrimRouter$$Root$$moudle1 implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routers) {
routers.put("module", PrimRouter$$Group$$module.class);
}
}
複製程式碼
具體分組:
public class PrimRouter$$Group$$module implements IRouteGroup {
@Override
public void loadInto(Map<String, RouterMeta> atlas) {
atlas.put("/module/test",RouterMeta.build(RouterMeta.Type.ACTIVITY,MainActivity.class,"/module/test","module"));
atlas.put("/module/test2",RouterMeta.build(RouterMeta.Type.ACTIVITY,Main2Activity.class,"/module/test2","module"));
}
}
複製程式碼
RouterMeta 就是儲存了一些路由分組表的資訊
/**
* 儲存的路由物件
*/
public class RouterMeta {
/**
* 路由的型別列舉
*/
public enum Type {
ACTIVITY, INSTANCE
}
/**
* 路由的型別
*/
private Type type;
/**
* 節點 activity
*/
private Element element;
/**
* 註解使用的類物件
*/
private Class<?> destination;
/**
* 路由地址
*/
private String path;
/**
* 路由組
*/
private String group;
public static RouterMeta build(Type type, Class<?> destination, String path, String
group) {
return new RouterMeta(type, null, destination, path, group);
}
public RouterMeta() {
}
public RouterMeta(Type type, Router router, Element element) {
this(type, element, null, router.path(), router.group());
}
public RouterMeta(Type type, Element element, Class<?> destination, String path, String group) {
this.type = type;
this.destination = destination;
this.element = element;
this.path = path;
this.group = group;
}
public void setType(Type type) {
this.type = type;
}
public void setElement(Element element) {
this.element = element;
}
public void setDestination(Class<?> destination) {
this.destination = destination;
}
public void setPath(String path) {
this.path = path;
}
public void setGroup(String group) {
this.group = group;
}
public Type getType() {
return type;
}
public Element getElement() {
return element;
}
public Class<?> getDestination() {
return destination;
}
public String getPath() {
return path;
}
public String getGroup() {
return group;
}
}
複製程式碼
- 動態生成Java class類,新建一個 java lib 取名:prim_compiler 這一步通過APT來實現。這一步其實很簡單分析完要生成的類,然後通過javaopt 來動態生成,如果還不懂APT 可以看這一篇文章APT 實踐。
gradle 配置:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.auto.service:auto-service:1.0-rc4'
implementation 'com.squareup:javapoet:1.11.1'
implementation project(':primrouter-annotation')
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
複製程式碼
新建一個processor類
//AnnotationProcessor register
@AutoService(Processor.class)
/**
* compiled java version {@link AbstractProcessor#getSupportedSourceVersion()}
*/
@SupportedSourceVersion(RELEASE_7)
/**
* Allow AnnotationProcessor process annotation,{@link AbstractProcessor#getSupportedAnnotationTypes()}
*/
@SupportedAnnotationTypes({"com.prim.router.primrouter_annotation.Router"})
/**
* 註解處理器接收的引數 {@link AbstractProcessor#getSupportedOptions()}
*/
@SupportedOptions({Consts.ARGUMENTS_NAME})
/**
* Router 註解處理器
*/
public class RouterProcessor extends AbstractProcessor {
private Messager messager;
/**
* 檔案生成器 類/資源
*/
private Filer filer;
/**
*
*/
private Locale locale;
/**
* 獲取傳遞的引數
*/
private Map<String, String> options;
/**
* compiled java version
*/
private SourceVersion sourceVersion;
/**
* 型別工具類
*/
private Types typeUtils;
/**
* 節點工具類
*/
private Elements elementUtils;
/**
* 模組的名稱
*/
private String moduleName;
/**
* key 組名 value 類名
*/
private Map<String, String> rootMap = new TreeMap<>();
/**
* 分組 key 組名 value 對應組的路由資訊
*/
private Map<String, List<RouterMeta>> groupMap = new HashMap<>();
private Log log;
/**
* init process environment utils
*
* @param processingEnvironment
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
messager = processingEnvironment.getMessager();
log = Log.newLog(messager);
filer = processingEnvironment.getFiler();
locale = processingEnvironment.getLocale();
options = processingEnvironment.getOptions();
sourceVersion = processingEnvironment.getSourceVersion();
typeUtils = processingEnvironment.getTypeUtils();
elementUtils = processingEnvironment.getElementUtils();
// 獲取傳遞的引數
if (!options.isEmpty()) {
moduleName = options.get(Consts.ARGUMENTS_NAME);
log.i("module:" + moduleName);
}
if (Utils.isEmpty(moduleName)) {
throw new NullPointerException("Not set Processor Parmaters.");
}
}
/**
* process annotation
*
* @param set The set of nodes that support processing annotations
* @param roundEnvironment Current or previous operating environment,annotation that can be found by this object
* @return true already processed ,follow-up will not be dealt with
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (!set.isEmpty()) {
//Set nodes annotated by Router
Set<? extends Element> annotatedWith = roundEnvironment.getElementsAnnotatedWith(Router.class);
if (annotatedWith != null) {
processRouter(annotatedWith);
}
return true;
}
return false;
}
/**
* 處理被註解的節點集合
*
* @param annotatedWith
*/
private void processRouter(Set<? extends Element> annotatedWith) {
RouterMeta routerMeta = null;
//獲得Activity類的節點資訊
TypeElement activity = elementUtils.getTypeElement(Consts.Activity);
//單個的節點
for (Element element : annotatedWith) {
// 獲取類資訊 如Activity類
TypeMirror typeMirror = element.asType();
// 獲取節點的註解資訊
Router annotation = element.getAnnotation(Router.class);
//只能指定的類上面使用
if (typeUtils.isSubtype(typeMirror, activity.asType())) {
//儲存路由相關的資訊
routerMeta = new RouterMeta(RouterMeta.Type.ACTIVITY, annotation, element);
} else if (typeUtils.isSubtype(typeMirror, activity.asType())) {
} else {
throw new RuntimeException("Just Support Activity Router!");
}
//檢查是否配置group如果沒有配置 則從path中擷取組名
checkRouterGroup(routerMeta);
}
//獲取 primrouter-core IRouterGroup 類節點
TypeElement routeGroupElement = elementUtils.getTypeElement(Consts.ROUTEGROUP);
//獲取 primrouter-core IRouterRoot 類節點
TypeElement routeRootElement = elementUtils.getTypeElement(Consts.ROUTEROOT);
//生成 $$Group$$ 記錄分組表
generatedGroupTable(routeGroupElement);
//生成 $$Root$$ 記錄路由表
generatedRootTable(routeRootElement, routeGroupElement);
}
}
複製程式碼
檢查路由地址是否配置正確,這一步可以自己定義相關的路由地址規則
/**
* 檢查設定路由組
*
* @param routerMeta
*/
private void checkRouterGroup(RouterMeta routerMeta) {
if (routerVerify(routerMeta)) {
List<RouterMeta> routerMetas = groupMap.get(routerMeta.getGroup());
if (Utils.isEmpty(routerMetas)) {
routerMetas = new ArrayList<>();
routerMetas.add(routerMeta);
groupMap.put(routerMeta.getGroup(), routerMetas);
} else {
routerMetas.add(routerMeta);
}
} else {
log.i("router path no verify,please check");
}
}
/**
* 驗證路由地址配置是否正確合法性
*
* @param routerMeta 儲存的路由bean物件
* @return true 路由地址配置正確 false 路由地址配置不正確
*/
private boolean routerVerify(RouterMeta routerMeta) {
String path = routerMeta.getPath();
if (Utils.isEmpty(path)) {
throw new NullPointerException("@Router path not to be null or to length() == 0");
}
if (!path.startsWith("/")) {//路由地址必須以/開頭
throw new IllegalArgumentException("@Router path must / first");
}
String group = routerMeta.getGroup();
if (Utils.isEmpty(group)) {
String defaultGroup = path.substring(1, path.indexOf("/", 1));
//擷取的還是為空
if (Utils.isEmpty(defaultGroup)) {
return false;
}
//設定group
routerMeta.setGroup(defaultGroup);
}
return true;
}
複製程式碼
生成分組表class 類
/**
* 生成分組表class 類
*
* @param routeGroupElement primrouter-core IRouterGroup 類節點
*/
private void generatedGroupTable(TypeElement routeGroupElement) {
//建立引數型別 Map<String,RouterMeta>
ParameterizedTypeName atlas = ParameterizedTypeName.get(ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouterMeta.class));
//建立引數 Map<String,RouterMeta> atlas
ParameterSpec altlas = ParameterSpec
.builder(atlas, Consts.GROUP_PARAM_NAME)//引數名
.build();//建立引數
//遍歷分組 每一個分組 建立一個 $$Group$$類
for (Map.Entry<String, List<RouterMeta>> entry : groupMap.entrySet()) {
MethodSpec.Builder builder = MethodSpec.methodBuilder(Consts.GROUP_METHOD_NAME)//函式名
.addModifiers(PUBLIC)//作用域
.addAnnotation(Override.class)//新增一個註解
.addParameter(altlas);//新增引數
//Group組中
List<RouterMeta> groupData = entry.getValue();
//遍歷 生成函式體
for (RouterMeta meta : groupData) {
//$S = String
//$T = class
//新增函式體
builder.addStatement(Consts.GROUP_PARAM_NAME+".put($S,$T.build($T.$L,$T.class,$S,$S))",
meta.getPath(),
ClassName.get(RouterMeta.class),
ClassName.get(RouterMeta.Type.class),
meta.getType(),
ClassName.get((TypeElement) meta.getElement()),
meta.getPath(),
meta.getGroup());
}
MethodSpec loadInto = builder.build();//函式建立完成loadInto();
String groupClassName = Consts.GROUP_CLASS_NAME + entry.getKey();
TypeSpec typeSpec = TypeSpec.classBuilder(groupClassName)//類名
.addSuperinterface(ClassName.get(routeGroupElement))//實現介面IRouteGroup
.addModifiers(PUBLIC)//作用域
.addMethod(loadInto)//新增方法
.build();//類建立完成
//生成Java檔案
JavaFile javaFile = JavaFile
.builder(Consts.PAGENAME, typeSpec)//包名和類
.build();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
rootMap.put(entry.getKey(), groupClassName);
}
}
複製程式碼
生成路由表class 類
/**
* 生成路由表class 類
*
* @param routeRootElement
*/
private void generatedRootTable(TypeElement routeRootElement, TypeElement routeGroupElement) {
////型別 Map<String,Class<? extends IRouteGroup>> routes>
ParameterizedTypeName atlas = ParameterizedTypeName.get(ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(
ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(routeGroupElement))));
//建立引數 Map<String,Class<? extends IRouteGroup>>> routes
ParameterSpec altlas = ParameterSpec
.builder(atlas, Consts.ROOT_PARAM_NAME)//引數名
.build();//建立引數
//public void loadInfo(Map<String,Class<? extends IRouteGroup>> routes> routes)
//函式 public void loadInfo(Map<String,Class<? extends IRouteGroup>> routes> routes)
MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder
(Consts.ROOT_METHOD_NAME)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(altlas);
//函式體
for (Map.Entry<String, String> entry : rootMap.entrySet()) {
loadIntoMethodOfRootBuilder.addStatement(Consts.ROOT_PARAM_NAME + ".put($S, $T.class)", entry
.getKey(), ClassName.get(Consts.PAGENAME, entry.getValue
()));
}
//生成 $Root$類
String rootClassName = Consts.ROOT_CLASS_NAME + moduleName;
try {
JavaFile.builder(Consts.PAGENAME,
TypeSpec.classBuilder(rootClassName)
.addSuperinterface(ClassName.get(routeRootElement))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfRootBuilder.build())
.build()
).build().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
複製程式碼
- 檢查動態生成的類是否正確
在app的gradle 中配置:
annotationProcessor project(':primrouter-compiler')
implementation project(':primrouter-annotation')
複製程式碼
在MainActivity中新增註解
@Router(path = "/app/main")
public class MainActivity extends AppCompatActivity {
}
複製程式碼
會生成以下兩個類:
則說明動態生成類正確。
- 最核心的部分,編寫核心程式碼庫:primrouter-core 首先我們需要在Application中初始化 路由表,也就是說將生成的路由表存到本地的倉庫HashMap 中
如何收集路由表?我們可以根據生成路由表的類的包名來找到路由表的類名,因為生成類的包名是我們自己定製的,那麼我們完全可以找到這個包下面的所有的類。 程式碼如下:
public static final String PAGENAME = "com.prim.router.generated";
public static final String GROUP_CLASS_NAME = "PrimRouter$$Group$$";
public static final String ROOT_CLASS_NAME = "PrimRouter$$Root$$";
複製程式碼
/**
* 根據包名 找到包下的類
*
* @param application
* @param pageName
* @return
*/
public static Set<String> getFileNameByPackageName(Application application, final String pageName) throws InterruptedException {
final Set<String> classNams = new HashSet<>();
List<String> sourcePath = getSourcePath(application);//apk 的資源路徑
//使用同步計數器判斷均處理完成
final CountDownLatch countDownLatch = new CountDownLatch(sourcePath.size());
ThreadPoolExecutor threadPoolExecutor = DefaultPoolExecutor.newDefaultPoolExecutor(sourcePath.size());
for (final String path : sourcePath) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
DexFile dexFile = null;
try {
//載入apk中的dex遍歷 獲得所有包名為pageName的類
dexFile = new DexFile(path);
Enumeration<String> entries = dexFile.entries();
while (entries.hasMoreElements()) {
String className = entries.nextElement();
if (className.startsWith(pageName)) {
classNams.add(className);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != dexFile) {
try {
dexFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//釋放1個
countDownLatch.countDown();
}
}
});
}
//等待執行完成
countDownLatch.await();
return classNams;
}
複製程式碼
找到類之後,我們就可以收集路由來儲存到本地倉庫了
/**
* 初始化-收集路由表,必須在Application中初始化
*
* @param application
*/
public void initRouter(Application application) {
this.application = application;
try {
loadRouteTable();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 收集載入路由表
*/
private void loadRouteTable() throws InterruptedException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
if (application == null) {
throw new IllegalArgumentException("initRouter(Application application) must Application init");
}
//獲取所有APT 生成的路由類的全類名
Set<String> routerMap = Utils.getFileNameByPackageName(application, PAGENAME);
for (String className : routerMap) {
//獲取root中註冊的路由分組資訊,儲存到本地倉庫中。
if (className.startsWith(PAGENAME + "." + ROOT_CLASS_NAME)) {
Object instance = Class.forName(className).getConstructor().newInstance();
if (instance instanceof IRouteRoot) {
IRouteRoot routeRoot = (IRouteRoot) instance;
routeRoot.loadInto(Depository.rootMap);
}
}
Log.e(TAG, "路由表分組資訊 「");
for (Map.Entry<String, Class<? extends IRouteGroup>> entry : Depository.rootMap.entrySet()) {
Log.e(TAG, "【key --> " + entry.getKey() + ": value --> " + entry.getValue() + "]");
}
Log.e(TAG, " 」");
}
}
複製程式碼
- 路由的跳轉實現,這一步同樣也很簡單,既然路由的資訊我們都儲存起來了,那麼就可以根據路由地址來找到相關的Activity全類名,然後就可以跳轉了,核心邏輯如下:
public JumpCard jump(String path) {
if (TextUtils.isEmpty(path)) {
throw new RuntimeException("路由地址無效");
} else {
return new JumpCard(path, getGroupName(path));
}
}
public JumpCard jump(String path, String group) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new RuntimeException("路由地址無效");
} else {
return new JumpCard(path, group);
}
}
public JumpCard jump(String path, String group, Bundle bundle) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new RuntimeException("路由地址無效");
} else {
return new JumpCard(path, group, bundle);
}
}
public Object navigation(final Context context, final JumpCard jumpCard, final int requestCode, Object o1) {
if (context == null) {
return null;
}
produceJumpCard(jumpCard);
switch (jumpCard.getType()) {
case ACTIVITY:
final Intent intent = new Intent(context, jumpCard.getDestination());
intent.putExtras(jumpCard.getExtras());
if (jumpCard.getFlags() != -1) {
intent.setFlags(jumpCard.getFlags());
} else if (!(context instanceof Activity)) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
mHandler.post(new Runnable() {//在主執行緒中跳轉
@Override
public void run() {
//可能需要返回碼
if (requestCode > 0) {
ActivityCompat.startActivityForResult((Activity) context, intent,
requestCode, jumpCard.getOptionsBundle());
} else {
ActivityCompat.startActivity(context, intent, jumpCard
.getOptionsBundle());
}
if ((0 != jumpCard.getEnterAnim() || 0 != jumpCard.getExitAnim()) &&
context instanceof Activity) {
//老版本
((Activity) context).overridePendingTransition(jumpCard
.getEnterAnim()
, jumpCard.getExitAnim());
}
//跳轉完成
// if (null != callback) {
// callback.onArrival(postcard);
// }
}
});
break;
}
return null;
}
/**
* 準備跳卡
*/
private void produceJumpCard(JumpCard card) {
//獲取倉庫中儲存的 具體每個組的資訊
RouterMeta routerMeta = Depository.groupMap.get(card.getPath());
if (routerMeta == null) {//沒有記錄在倉庫中,從路由表的分組資訊中查詢
Class<? extends IRouteGroup> groupClass = Depository.rootMap.get(card.getGroup());
if (groupClass == null) {
throw new RuntimeException("沒有找到對應的路由表資訊:" + card.getGroup() + ":" + card.getPath());
}
IRouteGroup routeGroup = null;
try {
routeGroup = groupClass.getConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("路由對映表資訊記錄失敗");
}
routeGroup.loadInto(Depository.groupMap);
produceJumpCard(card);//再次進入
} else {
//設定要跳轉的類
card.setDestination(routerMeta.getDestination());
//設定要跳轉的型別
card.setType(routerMeta.getType());
}
}
/**
* 通過路由地址獲取分組名
*
* @param path
* @return
*/
private String getGroupName(String path) {
if (!path.startsWith("/")) {
throw new RuntimeException(path + ": 不能有效的提取group");
}
try {
String group = path.substring(1, path.indexOf("/", 1));
if (TextUtils.isEmpty(group)) {
throw new RuntimeException("不能有效的提取group");
}
return group;
} catch (Exception e) {
return null;
}
}
複製程式碼
- 核心程式碼基本寫完那麼我們來測試以下:
app 跳轉到一個到module Activity,和 module 之間的跳轉。
@Router(path = "/app/main")
public class MainActivity extends AppCompatActivity {
private Button appToModule;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
appToModule = findViewById(R.id.appToModule);
appToModule.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
module1();
}
});
}
public void module1() {
PrimRouter.getInstance().jump("/module/test2").navigation(this);
}
}
複製程式碼
module
@Router(path = "/module/test2")
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
}
}
複製程式碼
比較穩定的路由框架,有好多這裡就不一一例舉了:
Android的元件化專題: 元件化配置