GoRouter
一個(gè)用于幫助 Android App 進(jìn)行組件化改造的框架 —— 支持模塊間的路由绍弟、通信技即、解耦
簡(jiǎn)介
之前一直在用阿里開源的ARouter項(xiàng)目,因?yàn)锳Router多年未更新樟遣,ARouter開始有些不太適合了而叼,所以重新開發(fā)了這款A(yù)ndroid路由框架郭脂,同樣的API,更多的功能澈歉,遷移請(qǐng)參見文末8-11。
GoRouter和ARouter功能差異對(duì)比
功能 | ARouter | GoRouter | 描述 |
---|---|---|---|
自動(dòng)生成路由幫助類 | 不支持 | 支持 | 調(diào)用更方便屿衅,不需要知道目標(biāo)頁面參數(shù)名埃难、參數(shù)類型、是否必傳等元素涤久,通過幫助類提供的方法涡尘,可輕松實(shí)現(xiàn)路由跳轉(zhuǎn)和服務(wù)獲取,參見7-1 |
模塊Application生命周期 | 不支持 | 支持 | 主動(dòng)分發(fā)到業(yè)務(wù)模塊响迂,讓模塊無侵入的獲取Application生命周期考抄,參見6-1 |
路由頁面事件 | 不支持 | 支持 | 頁面事件解耦,提供更多蔗彤、更方便的API川梅,參見5-10 |
路由注冊(cè)方式 | 注解 | 注解、java | GoRouter不僅提供了注解然遏,還能使用java方式注冊(cè)贫途,參見5-6 |
動(dòng)態(tài)注冊(cè)攔截器 | 不支持 | 支持 | ARouter只能動(dòng)態(tài)注冊(cè)路由,不能動(dòng)態(tài)注冊(cè)攔截器 |
重寫跳轉(zhuǎn)URL服務(wù) | 支持 | 不支持 | 可以在IPretreatmentService 里實(shí)現(xiàn)相同功能待侵,參見8-10 |
獲取元數(shù)據(jù) | 不支持 | 支持 | 有些場(chǎng)景需要判斷某個(gè)頁面當(dāng)前是否存在等需求丢早,就需要獲取頁面class等信息,參見5-1 |
inject(T) | 單一 | 更多 | ARouter不能在onNewIntent() 方法里使用秧倾,也不能檢查required 怨酝,GoRouter提供了更多使用場(chǎng)景,性能更好那先,參見4-2 |
按組分類农猬、按需初始化 | 支持 | 支持 | ARouter不允許多個(gè)module中存在相同的分組,GoRouter允許 |
生成路由文檔 | 支持 | 支持 | ARouter會(huì)生成多個(gè)模塊文檔胃榕,GoRouter提供Gradle任務(wù)一鍵生成項(xiàng)目總文檔盛险,更加豐富,參見5-9 |
一勋又、功能介紹
- 支持直接解析標(biāo)準(zhǔn)URL進(jìn)行跳轉(zhuǎn)苦掘,并自動(dòng)注入?yún)?shù)到目標(biāo)頁面中
- 支持多模塊工程使用
- 支持添加多個(gè)攔截器,自定義攔截順序
- 支持依賴注入楔壤,可單獨(dú)作為依賴注入框架使用
- 支持InstantRun
- 支持MultiDex(Google方案)
- 映射關(guān)系按組分類鹤啡、多級(jí)管理,按需初始化
- 支持用戶指定全局降級(jí)與局部降級(jí)策略
- 頁面蹲嚣、攔截器递瑰、服務(wù)等組件均自動(dòng)注冊(cè)到框架
- 支持多種方式配置轉(zhuǎn)場(chǎng)動(dòng)畫
- 支持獲取Fragment
- 完全支持Kotlin以及混編
- 支持第三方 App 加固
- 支持一鍵生成路由文檔
- 支持增量編譯
- 支持動(dòng)態(tài)注冊(cè)路由祟牲、路由組、服務(wù)和攔截器
- 支持模塊Application生命周期
- 支持路由頁面事件
- 自動(dòng)生成路由幫助類
二抖部、典型應(yīng)用
- 從外部URL映射到內(nèi)部頁面说贝,以及參數(shù)傳遞與解析
- 跨模塊頁面跳轉(zhuǎn),模塊間解耦
- 攔截跳轉(zhuǎn)過程慎颗,處理登陸乡恕、埋點(diǎn)等邏輯
- 跨模塊API調(diào)用,通過控制反轉(zhuǎn)來做組件解耦
三俯萎、基礎(chǔ)功能
1. 添加依賴和配置
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
api 'com.github.wyjsonGo.GoRouter:GoRouter-Api:2.5.2'
}
// Kotlin配置參見8-1
2. 在module項(xiàng)目下添加注解處理器依賴和配置
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [GOROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
annotationProcessor 'com.github.wyjsonGo.GoRouter:GoRouter-Compiler:2.5.2'
}
module_user模塊Demo示例module_user/build.gradle
3. 添加路由
// 在支持路由的頁面上添加注解
// 這里的路徑需要注意的是至少需要有兩級(jí)傲宜,/xx/xx
@Route(path = "/test/activity")
public class TestActivity extend Activity {
...
}
4. 初始化SDK
if (BuildConfig.DEBUG) {
GoRouter.openDebug(); // 開啟調(diào)試,查看路由詳細(xì)加載和跳轉(zhuǎn)過程日志
}
GoRouter.autoLoadRouteModule(this); // 盡可能早夫啊,推薦在Application中初始化
5. 發(fā)起路由操作
經(jīng)典方式:
// 1. 應(yīng)用內(nèi)簡(jiǎn)單的跳轉(zhuǎn)(通過URL跳轉(zhuǎn)在'進(jìn)階用法'中)
GoRouter.getInstance().build("/test/activity").go();
// 2. 跳轉(zhuǎn)并攜帶參數(shù)
GoRouter.getInstance().build("/test/fragment")
.withString("name", "Wyjson")
.withObject("test", new TestModel(123, "Jack"))
.withInt("age", 35)
.go();
Helper方式:(開啟Helper功能函卒,參見7-1)
// 1. 應(yīng)用內(nèi)簡(jiǎn)單的跳轉(zhuǎn)
TestActivityGoRouter.go();
// 2. 跳轉(zhuǎn)并攜帶參數(shù)
TestFragmentGoRouter.go("Wyjson", testModel, 35);
6. 使用Gradle插件實(shí)現(xiàn)路由表的自動(dòng)加載,支持Gradle8.0+
// 項(xiàng)目根目錄下的settings.gradle
pluginManagement {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
// 項(xiàng)目根目錄下的build.gradle
buildscript {
dependencies {
classpath 'com.github.wyjsonGo.GoRouter:GoRouter-Gradle-Plugin:2.5.2'
}
}
// app目錄下的build.gradle
plugins {
...
id 'com.wyjson.gorouter'
}
- 支持Gradle8.0+撇眯。
- 開發(fā)階段構(gòu)建加速參見5-8(最好在開發(fā)階段開啟,節(jié)省build時(shí)間)报嵌。
- 可選使用,通過GoRouter提供的注冊(cè)插件進(jìn)行路由表的自動(dòng)加載叛本,默認(rèn)通過掃描dex的方式進(jìn)行加載(在運(yùn)行時(shí)注冊(cè),節(jié)省打包時(shí)間)沪蓬,通過gradle插件進(jìn)行自動(dòng)注冊(cè)可以縮短運(yùn)行時(shí)初始化時(shí)間(在打包時(shí)注冊(cè),節(jié)省運(yùn)行時(shí)間),解決應(yīng)用加固導(dǎo)致無法直接訪問dex文件来候。
四跷叉、進(jìn)階用法
1. 通過URL跳轉(zhuǎn)
// 新建一個(gè)Activity用于監(jiān)聽Scheme事件,之后直接把url傳遞給GoRouter即可(消息欄和外部所有URL跳轉(zhuǎn)都可以統(tǒng)一用這個(gè)Activity接收后分發(fā)跳轉(zhuǎn))
public class SchemeFilterActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri uri = getIntent().getData();
GoRouter.getInstance().build(uri).go();
finish();
}
}
AndroidManifest.xml
<activity android:name=".activity.SchemeFilterActivity">
<!-- Scheme -->
<intent-filter>
<data
android:host="m.wyjson.com"
android:scheme="gorouter"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
2. 解析參數(shù)
在帶有@Route
注解的Activity、Fragment頁面使用@Param
注解营搅,會(huì)自動(dòng)生成類名+$$Param.java
注入類云挟,可調(diào)用inject()
或injectCheck()
方法自動(dòng)注入?yún)?shù)。
// 為每一個(gè)參數(shù)聲明一個(gè)字段(不能是private)转质,并使用 @Param 標(biāo)注
// URL中不能傳遞Parcelable類型數(shù)據(jù)园欣,通過GoRouter api可以傳遞Parcelable對(duì)象
@Route(path = "/param/activity")
public class ParamActivity extends BaseParamActivity {
@Param
int age = 18;
// 可以自定義參數(shù)name
@Param(name = "nickname", remark = "昵稱", required = true)
String name;
/**
* 使用 withObject 傳遞 List 和 Map 的實(shí)現(xiàn)了 Serializable 接口的實(shí)現(xiàn)類(ArrayList/HashMap)的時(shí)候,
* 接收該對(duì)象的地方不能標(biāo)注具體的實(shí)現(xiàn)類類型應(yīng)僅標(biāo)注為 List 或 Map休蟹,
* 否則會(huì)影響序列化中類型的判斷, 其他類似情況需要同樣處理
*/
@Param(name = "test", remark = "自定義類型", required = true)
TestModel testModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// inject()方法會(huì)自動(dòng)對(duì)字段進(jìn)行賦值
ParamActivity$$Param.inject(this);
// 或使用
// injectCheck()方法會(huì)自動(dòng)對(duì)字段進(jìn)行賦值沸枯,
// 并檢查標(biāo)記@Param(required = true)的字段,
// 檢查不通過會(huì)拋出ParamException()類型的異常,
// 可通過e.getParamName()獲取參數(shù)名自行處理赂弓。
try {
ParamActivity$$Param.injectCheck(this);
} catch (ParamException e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
finish();
return;
}
Log.d("param", "base:" + base + "age:" + age + ",name:" + name);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
// inject()方法參數(shù)支持intent绑榴、bundle
ParamActivity$$Param.inject(this, intent);
}
}
// 支持父類字段自動(dòng)注入
public class BaseParamActivity extends Activity {
@Param(remark = "我是一個(gè)父類字段")
protected int base;
}
如果使用了withObject()
方法,需要實(shí)現(xiàn)JSON服務(wù)
// 實(shí)現(xiàn)IJsonService接口
@Service(remark = "json服務(wù)")
public class JsonServiceImpl implements IJsonService {
@Override
public void init() {
}
@Override
public String toJson(Object instance) {
// 這里演示使用了gson,也可以使用其他json轉(zhuǎn)換工具
return new Gson().toJson(instance);
}
@Override
public <T> T parseObject(String input, Type typeOfT) {
return new Gson().fromJson(input, typeOfT);
}
}
3. 聲明攔截器(攔截跳轉(zhuǎn)過程盈魁,面向切面編程)
// 比較經(jīng)典的應(yīng)用就是在跳轉(zhuǎn)過程中處理登陸事件翔怎,這樣就不需要在目標(biāo)頁重復(fù)做登陸檢查
// 攔截器會(huì)在跳轉(zhuǎn)之前執(zhí)行,多個(gè)攔截器會(huì)按序號(hào)從小到大順序依次執(zhí)行
@Interceptor(ordinal = 1, remark = "測(cè)試攔截器")
public class TestInterceptor implements IInterceptor {
@Override
public void process(Card card, InterceptorCallback callback) {
...
callback.onContinue(card); // 處理完成,交還控制權(quán)
// callback.onInterrupt(card, new RouterException("我覺得有點(diǎn)異常")); // 覺得有問題赤套,中斷路由流程
// 以上兩種至少需要調(diào)用其中一種飘痛,否則不會(huì)繼續(xù)路由
}
@Override
public void init() {
// 攔截器的初始化,會(huì)在sdk初始化的時(shí)候調(diào)用該方法容握,僅會(huì)調(diào)用一次
}
}
4. 處理跳轉(zhuǎn)結(jié)果
// 使用兩個(gè)參數(shù)的go方法宣脉,可以獲取單次跳轉(zhuǎn)的結(jié)果
GoRouter.getInstance().build("/test/activity").go(this, new GoCallback() {
@Override
public void onFound(Card card) {
}
@Override
public void onLost(Card card) {
}
@Override
public void onArrival(Card card) {
}
@Override
public void onInterrupt(Card card,@NonNull Throwable exception) {
}
});
5. 自定義全局降級(jí)策略
// 實(shí)現(xiàn)IDegradeService接口
@Service(remark = "全局降級(jí)策略")
public class DegradeServiceImpl implements IDegradeService {
@Override
public void onLost(Context context, Card card) {
// do something.
}
@Override
public void init() {
}
}
6. 為目標(biāo)頁面聲明更多信息
// 我們經(jīng)常需要在目標(biāo)頁面中配置一些屬性,比方說"是否需要登陸"之類的
// 可以通過@Route注解的tag屬性進(jìn)行擴(kuò)展剔氏,這個(gè)屬性是一個(gè)int值脖旱,換句話說,單個(gè)int有4字節(jié)介蛉,可以配置31個(gè)開關(guān)
@Route(path = "/user/info/activity", tag = LOGIN | AUTHENTICATION)
// 在攔截器里通過Card對(duì)象拿到這個(gè)標(biāo)記進(jìn)行業(yè)務(wù)邏輯判斷
card.isTagExist(LOGIN); // 判斷是否存在登錄標(biāo)識(shí)
tag使用示例UserInfoActivity.java,
tag處理示例SignInInterceptor.java
7. 通過依賴注入解耦:服務(wù)管理
// 暴露服務(wù)
// 聲明接口并繼承IService接口,其他組件通過接口來調(diào)用服務(wù)
public interface HelloService extends IService {
String sayHello(String name);
}
// 實(shí)現(xiàn)接口
@Service(remark = "服務(wù)描述")
public class HelloServiceImpl implements HelloService {
@Override
public void init() {
}
@Override
public String sayHello(String name) {
return "hello, " + name;
}
}
// 發(fā)現(xiàn)服務(wù)
HelloService helloService = GoRouter.getInstance().getService(HelloService.class);
if (helloService != null) {
helloService.sayHello("Wyjson");
}
進(jìn)階用法溶褪,如需多個(gè)實(shí)現(xiàn)服務(wù)可指定@Service(alias = "xxx")
// 暴露支付服務(wù)
public interface PayService extends IService {
String getPayType();
}
// 實(shí)現(xiàn)阿里支付接口
@Service(alias = "Alipay", remark = "AliPay服務(wù)")
public class AliPayServiceImpl implements PayService {
@Override
public void init() {
}
@Override
public String getPayType() {
return "AliPay";
}
}
// 實(shí)現(xiàn)微信支付接口
@Service(alias = "WechatPay", remark = "微信Pay服務(wù)")
public class WechatPayServiceImpl implements PayService {
@Override
public void init() {
}
@Override
public String getPayType() {
return "WechatPay";
}
}
// 發(fā)現(xiàn)阿里支付服務(wù)
PayService alipayService = GoRouter.getInstance().getService(PayService.class, "Alipay");
if (alipayService != null) {
alipayService.getPayType();
}
// 發(fā)現(xiàn)微信支付服務(wù)
PayService wechatPayService = GoRouter.getInstance().getService(PayService.class, "WechatPay");
if (wechatPayService != null) {
wechatPayService.getPayType();
}
8. 跳轉(zhuǎn)前預(yù)處理
// 比如跳轉(zhuǎn)登錄頁面币旧,只要簡(jiǎn)單的調(diào)用go就可以了,一些必須的參數(shù)和標(biāo)識(shí)可以放到預(yù)處理里來做猿妈。
// 或者一些埋點(diǎn)的處理
// 實(shí)現(xiàn)IPretreatmentService接口
@Service(remark = "預(yù)處理服務(wù)")
public class PretreatmentServiceImpl implements IPretreatmentService {
@Override
public void init() {
}
@Override
public boolean onPretreatment(Context context, Card card) {
// 登錄頁面預(yù)處理
if ("/user/sign_in/activity".equals(card.getPath())) {
card.withFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP);
}
return true; // 跳轉(zhuǎn)前預(yù)處理吹菱,如果需要自行處理跳轉(zhuǎn),該方法返回 false 即可
}
}
五彭则、更多功能
1. 根據(jù)路徑獲取元數(shù)據(jù)
// 有些場(chǎng)景需要判斷某個(gè)頁面當(dāng)前是否存在等需求,就需要獲取頁面class等信息鳍刷,可以使用此方法getCardMete()
CardMeta cardMeta = GoRouter.getInstance().build("/user/info/activity").getCardMeta();
if (cardMeta != null) {
cardMeta.getPathClass();
}
2. 詳細(xì)的API說明
// 標(biāo)準(zhǔn)的路由請(qǐng)求
GoRouter.getInstance().build("/main/activity").go();
// 通過Uri直接解析(外部、h5等調(diào)用native頁面攜帶參數(shù)可以使用此方式)
Uri uri = Uri.parse("/new/param/activity?age=9&name=jack&base=123");
GoRouter.getInstance().build(uri).go();
// 構(gòu)建標(biāo)準(zhǔn)的路由請(qǐng)求俯抖,startActivityForResult()
// go的第一個(gè)參數(shù)必須是Activity输瓜,第二個(gè)參數(shù)則是RequestCode
// 當(dāng)然也支持registerForActivityResult()方法
GoRouter.getInstance().build("/main/activity").go(this, 5);
// 直接傳遞Bundle
Bundle params = new Bundle();
GoRouter.getInstance()
.build("/main/activity")
.with(params)
.go();
// 指定Flag
GoRouter.getInstance()
.build("/main/activity")
.withFlags()
.go();
// 獲取Fragment
Fragment fragment = (Fragment) GoRouter.getInstance().build("/test/fragment").go();
// 序列化對(duì)象傳遞
GoRouter.getInstance().build("/main/activity")
.withSerializable("user",new User())
.go();
// 自定義對(duì)象傳遞
GoRouter.getInstance().build("/main/activity")
.withObject("test", new TestModel(123, "Jack"))
.go();
// 覺得接口不夠多,可以直接拿出Bundle賦值
GoRouter.getInstance()
.build("/main/activity")
.getExtras();
// 轉(zhuǎn)場(chǎng)動(dòng)畫(常規(guī)方式)
GoRouter.getInstance()
.build("/test/activity")
.withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom)
.go();
// 轉(zhuǎn)場(chǎng)動(dòng)畫(API16+)
ActivityOptionsCompat compat = ActivityOptionsCompat.
makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2, 0, 0);
// ps. makeSceneTransitionAnimation 使用共享元素的時(shí)候芬萍,需要在go方法中傳入當(dāng)前Activity
GoRouter.getInstance()
.build("/test/activity")
.withActivityOptionsCompat(compat)
.go();
// 使用綠色通道(跳過所有的攔截器)
GoRouter.getInstance().build("/main/activity").greenChannel().go();
// 使用自己的日志工具打印日志
GoRouter.setLogger();
// 使用自己提供的線程池
GoRouter.setExecutor();
// 開啟openDebug()后,打印日志的時(shí)候打印線程堆棧
GoRouter.printStackTrace();
3. 獲取原始的URI
String uriStr = GoRouter.getInstance().getRawURI(this);
4. 獲取當(dāng)前頁面路徑
String path = GoRouter.getInstance().getCurrentPath(this);
5. 獲取路由注冊(cè)模式
// true: Gradle插件進(jìn)行自動(dòng)注冊(cè)(在打包時(shí)注冊(cè),節(jié)省運(yùn)行時(shí)間)
// false: 掃描dex的方式(在運(yùn)行時(shí)注冊(cè),節(jié)省打包時(shí)間)
GoRouter.getInstance().isRouteRegisterMode();
6. java方式動(dòng)態(tài)注冊(cè)路由信息
適用于插件化架構(gòu)的App以及需要?jiǎng)討B(tài)注冊(cè)路由尤揣、路由組、服務(wù)和攔截器的場(chǎng)景柬祠。目標(biāo)頁面北戏、服務(wù)和攔截器可以不標(biāo)注@Route
、@Service
漫蛔、@Interceptor
注解嗜愈。
// 動(dòng)態(tài)注冊(cè)服務(wù)
GoRouter.getInstance().addService(UserServiceImpl.class);
// 動(dòng)態(tài)注冊(cè)服務(wù)多個(gè)實(shí)現(xiàn)
GoRouter.getInstance().addService(WechatPayServiceImpl.class, "WechatPay");
GoRouter.getInstance().addService(AliPayServiceImpl.class, "Alipay");
// 注冊(cè)攔截器(重復(fù)添加相同序號(hào)會(huì)catch)
GoRouter.getInstance().addInterceptor(1, TestInterceptor.class);
// 動(dòng)態(tài)注冊(cè)攔截器(重復(fù)添加相同序號(hào)會(huì)覆蓋(更新))
GoRouter.getInstance().setInterceptor(1, TestInterceptor.class);
// 動(dòng)態(tài)注冊(cè)路由分組,按需加載路由(注意:同一批次僅允許相同group的路由信息注冊(cè))
GoRouter.getInstance().addRouterGroup("show", new IRouteModuleGroup() {
@Override
public void load() {
GoRouter.getInstance().build("/show/list/activity").commitActivity(ShowListActivity.class);
GoRouter.getInstance().build("/show/info/fragment").commitFragment(ShowInfoFragment.class);
...
}
});
// 當(dāng)然,你也可以直接注冊(cè)Activity、Fragment路由
// 動(dòng)態(tài)注冊(cè)Activity
GoRouter.getInstance().build("/user/info/activity").putTag(3).commitActivity(UserInfoActivity.class);
// 動(dòng)態(tài)注冊(cè)Fragment
GoRouter.getInstance().build("/new/param/fragment").putInt("age").putString("name").commitFragment(ParamFragment.class);
// 自動(dòng)注入?yún)?shù)(開啟混淆的情況下,使用此方法需要配置混淆規(guī)則,參見8-7)
GoRouter.getInstance().inject(this);
7. 自定義模塊路由加載
如不使用Gradle插件[3-6]進(jìn)行自動(dòng)注冊(cè)莽龟,也不想走默認(rèn)掃描dex的方式蠕嫁,可以不調(diào)用GoRouter.autoLoadRouteModule(this)
方法,但需要自行調(diào)用模塊生成的路由加載類轧房。
模塊項(xiàng)目里至少使用一條注解@Route
拌阴、@Service
、@Interceptor
奶镶,就會(huì)生成對(duì)應(yīng)路由表的加載類迟赃。路由表加載類命名規(guī)則會(huì)根據(jù)GOROUTER_MODULE_NAME
設(shè)置的模塊名稱轉(zhuǎn)換成大寫駝峰命名+$$Route.java
陪拘,所有模塊生成的路由表加載類都會(huì)放到com.wyjson.router.module.route
包下。
例如模塊名稱module_user
會(huì)生成ModuleUser$$Route.java
// 可在任意地方調(diào)用模塊路由加載類
new ModuleUser$$Route().load();
8. Gradle插件自定義執(zhí)行的任務(wù)
由于在開發(fā)階段需要經(jīng)常build項(xiàng)目纤壁,每次運(yùn)行都走gradle插件自動(dòng)注冊(cè)后左刽,會(huì)導(dǎo)致gradle自帶任務(wù)dexBuilderDebug
(轉(zhuǎn)換class文件為dex文件)會(huì)很耗時(shí),所以在開發(fā)階段最好忽略buildType等于debug的情況,debug的情況就會(huì)走默認(rèn)掃描dex方式注冊(cè)酌媒,節(jié)省開發(fā)build時(shí)間欠痴。
// app目錄下的build.gradle
plugins {
...
id 'com.wyjson.gorouter'
}
// 不寫下面的配置,默認(rèn)android.buildTypes任務(wù)全部執(zhí)行自動(dòng)注冊(cè)秒咨。
GoRouter {
// 允許執(zhí)行自動(dòng)注冊(cè)任務(wù)的集合
runAutoRegisterBuildTypes "release", "sandbox", "more"
}
9. 生成路由文檔
使用gradle命令一鍵生成路由文檔
./gradlew generateRouteDocDebug
當(dāng)然你也可以使用圖形頁面執(zhí)行任務(wù)
生成的路由文檔會(huì)保存到項(xiàng)目下的/app/項(xiàng)目名-變體名-route-doc.json
喇辽,Demo示例/app/GoRouter-release-route-doc.json
10. 路由頁面事件
在之前跨模塊頁面事件通知的流程是,使用EventBus庫雨席,在module_common模塊里定義event類菩咨,頁面注冊(cè)訂閱者接收事件,實(shí)現(xiàn)事件處理并注解標(biāo)識(shí)陡厘,頁面銷毀解除注冊(cè)抽米。
這一套流程下來步驟很多,會(huì)出現(xiàn)很多event類糙置,而且這些類只在一個(gè)頁面訂閱云茸,還要去module_common模塊里定義,發(fā)布基礎(chǔ)數(shù)據(jù)類型谤饭,會(huì)導(dǎo)致所有訂閱者都會(huì)收到标捺,也無法檢測(cè)頁面生命周期狀態(tài)。
顯然EventBus適合任意處發(fā)布多處訂閱的場(chǎng)景揉抵,而我們需要任意處發(fā)布一處訂閱的場(chǎng)景宜岛,這樣就可以訂閱基礎(chǔ)數(shù)據(jù)類型了,自定義類型也不需要再包裹一層新的Event類發(fā)布出去功舀。
典型場(chǎng)景
- 消息Fragment通知主頁Activity的tab刷新未讀消息數(shù)萍倡。
- 代替startActivityForResult(),獲取返回來的數(shù)據(jù)辟汰。
- Activity顯示其他模塊Dialog列敲,實(shí)時(shí)顯示Dialog里選擇的數(shù)據(jù)。
使用
// 在Activity/Fragment上訂閱String類型事件
GoRouter.getInstance().registerEvent(this, String.class, new Observer<String>() {
@Override
public void onChanged(String value) {
// do something.
}
});
// 在任意地方向MainActivity發(fā)送String類型數(shù)據(jù)
GoRouter.getInstance().postEvent("/main/activity", "Go!");
更多用法
// 訂閱自定義類型事件
GoRouter.getInstance().registerEvent(this, UserEntity.class, new Observer<UserEntity>() {
@Override
public void onChanged(UserEntity data) {
// do something.
}
});
// 向UserFragment發(fā)送自定義類型數(shù)據(jù)
GoRouter.getInstance().postEvent("/user/fragment", new UserEntity(89, "Wyjson"));
// 手動(dòng)解除String類型全部訂閱
GoRouter.getInstance().unRegisterEvent(this, String.class);
// 手動(dòng)解除String類型指定observer訂閱
GoRouter.getInstance().unRegisterEvent(this, String.class, observer);
-
registerEvent()
和registerEventForever()
支持在Activity帖汞、Fragment上使用戴而。 -
registerEvent()
方法只有頁面處于活躍狀態(tài)下才會(huì)收到,registerEventForever()
方法無論頁面處于何種狀態(tài)下都會(huì)收到翩蘸。 -
postEvent()
方法支持主線程和子線程調(diào)用所意。多次調(diào)用的情況下,在頁面生命周期處于onPause
下,registerEvent()
方法只會(huì)收到最后一次數(shù)據(jù)扶踊,registerEventForever()
會(huì)全部收到泄鹏。在頁面生命周期處于onResume
下,兩個(gè)方法會(huì)收到全部數(shù)據(jù)秧耗。 -
unRegisterEvent()
方法無需主動(dòng)調(diào)用备籽,框架會(huì)在頁面銷毀時(shí)自動(dòng)解除訂閱,除非你想立刻取消訂閱分井。 - 頁面可以訂閱多個(gè)相同類型的事件和多個(gè)不同類型的事件车猬。
- 路由頁面事件功能內(nèi)部使用
LiveData
。
Demo示例EventActivity.java
六尺锚、模塊Application生命周期
Application生命周期主動(dòng)分發(fā)到組件珠闰,讓模塊無侵入的獲得Application生命周期
1. 在模塊中添加@ApplicationModule
注解,實(shí)現(xiàn)IApplicationModule
接口
@ApplicationModule
public class UserApplication implements IApplicationModule {
@Override
public void onCreate(Application app) {
// do something.
}
/**
* 優(yōu)化啟動(dòng)速度,一些不著急的初始化可以放在這里做,子線程
*/
@Override
public void onLoadAsync(Application app) {
// do something.
}
}
Demo示例CommonApplication.java
2. 在主Application添加分發(fā)
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 調(diào)用callAMOnCreate()方法瘫辩,觸發(fā)模塊Application的onCreate()铸磅、onLoadAsync()方法。
GoRouter.callAMOnCreate(this);
}
Demo示例MyApplication.java
3. 進(jìn)階用法
- 指定模塊Application優(yōu)先級(jí)杭朱,可以重寫
setPriority()
方法,它將按照從大到小的執(zhí)行順序執(zhí)行吹散。 -
IApplicationModule
接口不僅提供了onCreate
弧械、onLoadAsync
方法,還提供了onTerminate
空民、onConfigurationChanged
刃唐、onLowMemory
、onTrimMemory
方法界轩,如需監(jiān)聽記得在主Application中添加對(duì)應(yīng)的分發(fā)方法GoRouter.callAMOnCreate()
画饥、GoRouter.callAMOnTerminate()
、GoRouter.callAMOnConfigurationChanged(newConfig)
浊猾、GoRouter.callAMOnLowMemory()
抖甘、GoRouter.callAMOnTrimMemory(level)
。
七葫慎、自動(dòng)生成路由幫助類
開啟此功能可以大幅提升開發(fā)效率衔彻,避免錯(cuò)誤調(diào)用的情況。不需要知道目標(biāo)頁面參數(shù)名偷办、參數(shù)類型艰额、是否必傳等元素,通過幫助類提供的方法椒涯,可輕松實(shí)現(xiàn)路由跳轉(zhuǎn)和服務(wù)獲取柄沮。
1. 添加配置
// app目錄下的build.gradle添加配置
plugins {
...
id 'com.wyjson.gorouter'
}
GoRouter {
...
// 指定根模塊項(xiàng)目名稱,開啟自動(dòng)生成路由幫助類功能
helperToRootModuleName "module_common"
}
指定helperToRootModuleName
配置參數(shù)后會(huì)開啟自動(dòng)生成路由幫助類功能,這個(gè)參數(shù)需要指定一個(gè)根模塊項(xiàng)目名(指定的這個(gè)模塊項(xiàng)目只要保證其他業(yè)務(wù)模塊都能調(diào)用到种吸,并且頁面?zhèn)鬟f參數(shù)用到的實(shí)體類也能引用到就可以鸳址,推薦指向module_common
模塊或者新建一個(gè)模塊單獨(dú)存放這些生成的路由幫助類)。
2. 使用
// 經(jīng)典:訪問/main/activity
GoRouter.getInstance().build("/main/activity").go();
// helper:訪問/main/activity(框架會(huì)根據(jù)path生成對(duì)應(yīng)類名GoRouter.java)
MainActivityGoRouter.go();
// 經(jīng)典:帶參數(shù)訪問
GoRouter.getInstance().build("/new/param/activity")
.withString("nickname", "Wyjson")
.withObject("test", testModel)
.withInt("age", 78)
.withInt("base", 7758)
.go();
// helper:帶參數(shù)訪問
// 必傳參數(shù)
NewParamActivityGoRouter.go("Wyjson", testModel);
// 所有參數(shù)(必傳參數(shù)和非必傳參數(shù)一起)
NewParamActivityGoRouter.go("Wyjson", testModel, base, 78);
// 非必傳參數(shù)可以鏈?zhǔn)秸{(diào)用,解決了經(jīng)典方式需要知道類型和參數(shù)名的問題
NewParamActivityGoRouter.create("Wyjson", testModel)// 必傳參數(shù)
.setAge(78)// 非必傳參數(shù)
.setBase(7758)// 非必傳參數(shù)
.build()
.go();
// 經(jīng)典:訪問Fragment
Fragment fragment = (Fragment) GoRouter.getInstance().build("/user/card/fragment").go();
// helper:訪問Fragment
Fragment fragment = UserCardFragmentGoRouter.go();
// 經(jīng)典:獲取元數(shù)據(jù)
CardMeta cardMeta = GoRouter.getInstance().build("/user/info/activity").getCardMeta();
// helper:獲取元數(shù)據(jù)
CardMeta cardMeta = UserInfoActivityGoRouter.getCardMeta()
// 經(jīng)典:獲取User服務(wù)
UserService service = GoRouter.getInstance().getService(UserService.class);
// helper:獲取User服務(wù)
UserService service = UserServiceGoRouter.get()
// 經(jīng)典:獲取帶別名的服務(wù)
PayService service = GoRouter.getInstance().getService(PayService.class, "Alipay");
// helper:獲取帶別名的服務(wù)
PayService service = PayServiceForAlipayGoRouter.get();
// 經(jīng)典:向/main/activity發(fā)送事件
GoRouter.getInstance().postEvent("/main/activity", "Go!");
// helper:向/main/activity發(fā)送事件
MainActivityGoRouter.postEvent("Go!");
// helper:獲取path
String path = MainActivityGoRouter.getPath()
// 也可以通過helper類get/set其他屬性
UserSignInActivityGoRouter.build()
.withAction(...)
.withFlags(...)
.go(this, ...);
-
@Route(deprecated = true)
和打開了openDebug()的情況下窿克,框架跳轉(zhuǎn)到該頁將Toast提示其他開發(fā)人員和測(cè)試人員棕硫,并且生成的幫助類也會(huì)被自動(dòng)標(biāo)記@Deprecated
,代碼上也會(huì)提示過時(shí),提醒開發(fā)人員更新跳轉(zhuǎn)代碼髓涯,這在多人開發(fā)時(shí)會(huì)很有用。@Service
不會(huì)有這個(gè)問題哈扮,service直接在暴漏的服務(wù)接口上標(biāo)記@Deprecated
纬纪,其他模塊調(diào)用者就能看到過時(shí)標(biāo)記。 -
@Route(ignoreHelper = true)
的情況下滑肉,框架不會(huì)為這個(gè)頁面生成幫助類包各,適用于僅本模塊調(diào)用的頁面。
3. 設(shè)計(jì)思路
此功能所生成的幫助類會(huì)存放到com.wyjson.router.helper
包下靶庙,按照模塊.路由分組.幫助類.java
分別存放问畅,請(qǐng)勿手動(dòng)更改這些類。
最早設(shè)計(jì)的是整個(gè)項(xiàng)目生成一個(gè)GoRouterHelper.java
類六荒,調(diào)用上比現(xiàn)在的每個(gè)頁面生成一個(gè)對(duì)應(yīng)的幫助類會(huì)更統(tǒng)一护姆,
但是這樣會(huì)導(dǎo)致多人開發(fā)每個(gè)人生成的GoRouterHelper.java
類代碼合并沖突,所以想到把GoRouterHelper.java
存放到根項(xiàng)目build
目錄掏击,
存放到build
目錄確實(shí)可以解決代碼沖突問題卵皂,但是新的問題也來了,在首次開啟生成幫助類的功能砚亭,項(xiàng)目里還沒有調(diào)用這個(gè)GoRouterHelper.java
類的時(shí)候是不會(huì)有問題的灯变,
如果項(xiàng)目里已經(jīng)使用了這個(gè)幫助類,build
目錄被清除或者重新clone代碼捅膘,此時(shí)項(xiàng)目就沒法運(yùn)行了添祸,因?yàn)槿鄙龠@個(gè)幫助類。
這里不得不說一下幫助類生成流程和AndroidbuildConfig
任務(wù)的執(zhí)行順序寻仗,正常項(xiàng)目clone下來刃泌,idea提示報(bào)錯(cuò)缺少buildConfig.java
類,
你去build
項(xiàng)目署尤,Gradle會(huì)執(zhí)行buildConfig
任務(wù)蔬咬,生成缺少的buildConfig.java
類,再去生成common模塊aar沐寺。
路由幫助類在根項(xiàng)目里還不知道上層其他模塊里有什么頁面和服務(wù)的時(shí)候林艘,它的執(zhí)行順序是,先build
出來common模塊混坞,在去build
其他頁面業(yè)務(wù)模塊狐援,
最后在app項(xiàng)目匯總钢坦,此時(shí)知道了所有頁面和服務(wù)去生成幫助類到根模塊項(xiàng)目。說回剛才的話題啥酱,項(xiàng)目里使用了幫助類爹凹,而幫助類又存放到build
里,
此時(shí)要是缺少了build
目錄镶殷,會(huì)導(dǎo)致項(xiàng)目無法運(yùn)行禾酱,其實(shí)有其他方案解決這個(gè)問題,但都不是很好绘趋,
所以最后我把這些幫助類存放到了根項(xiàng)目/src/main/java/com/wyjson/router/helper/...
目錄下颤陶,按照頁面和服務(wù)生成對(duì)應(yīng)的幫助類,
解決了多人開發(fā)代碼沖突的問題陷遮。雖然這些類不在build
目錄里滓走,也不用擔(dān)心無用的類,框架在每次生成新的代碼的時(shí)候會(huì)自動(dòng)刪除無用的類帽馋,
和build
目錄機(jī)制一樣搅方。如果其他業(yè)務(wù)模塊你不是通過本地引用,而是通過aar等方式引入绽族,那框架只會(huì)更新本地引用模塊項(xiàng)目的幫助類目錄姨涡,
不會(huì)更改和刪除aar模塊在根項(xiàng)目里的幫助類,這樣做到了你所開發(fā)模塊幫助類的更新而不影響其他團(tuán)隊(duì)生成的幫助類吧慢。
八涛漂、其他
1. Kotlin項(xiàng)目中的配置方式
plugins {
...
id 'kotlin-kapt'
}
kapt {
arguments {
arg("GOROUTER_MODULE_NAME", project.getName())
}
}
dependencies {
kapt 'com.github.wyjsonGo.GoRouter:GoRouter-Compiler:2.5.2'
}
module_kotlin模塊Demo示例module_kotlin/build.gradle
2. Kotlin類中的字段無法注入如何解決?
首先娄蔼,Kotlin中的字段是可以自動(dòng)注入的,但是注入代碼為了不使用反射底哗,使用的字段賦值的方式來注入的岁诉,Kotlin默認(rèn)會(huì)生成set/get方法,并把屬性設(shè)置為private 所以只要保證Kotlin中字段可見性不是private即可跋选,簡(jiǎn)單解決可以在字段上添加 @JvmField涕癣。Demo示例KotlinActivity.kt
3. 路由中的分組概念
- 路由路徑支持(a-zA-Z0-9_-.#)
- SDK中針對(duì)所有的路徑
/test/1
、/test/2
進(jìn)行分組前标,分組只有在分組中的某一個(gè)路徑第一次被訪問的時(shí)候坠韩,該分組才會(huì)被初始化。分組使用路徑中第一段字符串(/*/)作為分組炼列,這里的路徑需要注意的是至少需要有兩級(jí)/xx/xx
只搁。 - GRouter允許一個(gè)module中存在多個(gè)分組,也允許多個(gè)module中存在相同的分組俭尖,但是最好不要在多個(gè)module中存在相同的分組氢惋,因?yàn)樵谧?cè)路由組時(shí)發(fā)現(xiàn)存在相同的分組洞翩,會(huì)立即注冊(cè)老的路由組里的全部路由,然后更新新的路由組信息焰望。
4. 攔截器和服務(wù)的異同
- 攔截器和服務(wù)所需要實(shí)現(xiàn)的接口不同骚亿,但是結(jié)構(gòu)類似,都存在
init()
方法熊赖,但是兩者的調(diào)用時(shí)機(jī)不同来屠。 - 攔截器因?yàn)槠涮厥庑裕粚?duì)Activity路由有效震鹉,攔截器會(huì)在GoRouter首次調(diào)用的時(shí)候初始化俱笛。
- 服務(wù)沒有該限制,某一服務(wù)可能在App整個(gè)生命周期中都不會(huì)用到足陨,所以服務(wù)只有被調(diào)用的時(shí)候才會(huì)觸發(fā)初始化操作嫂粟。
5. 使用java方式注冊(cè)服務(wù)
- 實(shí)現(xiàn)相同服務(wù)(HelloService)的實(shí)現(xiàn)類(HelloServiceImpl)多次調(diào)用
addService()
方法會(huì)被覆蓋(更新),全局唯一墨缘,使用getService(service)
方法獲取星虹。 - 實(shí)現(xiàn)相同服務(wù)(PayService)的多個(gè)實(shí)現(xiàn)類(AliPayServiceImpl、WechatPayServiceImpl)調(diào)用
addService(service, alias)
方法注冊(cè)镊讼,使用getService(service, alias)
方法獲取宽涌。
6. 使用java方式注冊(cè)攔截器
-
addInterceptor(ordinal, interceptor)
重復(fù)添加相同序號(hào)會(huì)catch。 -
setInterceptor(ordinal, interceptor)
重復(fù)添加相同序號(hào)會(huì)覆蓋(更新)蝶棋。
7. 混淆
框架已經(jīng)做了混淆處理卸亮,開發(fā)者無需關(guān)心。
# 如果使用了 GoRouter.getInstance().inject(this) 方法玩裙,需添加下面規(guī)則兼贸,保護(hù)字段
-keepclassmembers class * {
@com.wyjson.router.annotation.Param <fields>;
}
8. inject()
工作原理
- 2.3.2版本之前,
GoRouter.getInstance().inject(this)
方法會(huì)先通過this
參數(shù)拿到bundle
對(duì)象吃溅,再去獲取當(dāng)前頁面的path
溶诞,通過path
拿到CardMeta
數(shù)據(jù),利用java反射進(jìn)行數(shù)據(jù)的綁定决侈。 - 2.3.2版本起螺垢,使用
@Param
注解會(huì)自動(dòng)生成參數(shù)注入類,內(nèi)部代碼是原生寫法赖歌,性能更好枉圃,使用方法參見4-2。
9. go()
無參方法
如果你沒有使用自動(dòng)加載路由表方法GoRouter.autoLoadRouteModule(this)
庐冯,也沒有使用多模塊applicationGoRouter.callAMOnCreate(this)
方法孽亲,此時(shí)你去使用go()
無參方法需要在application里調(diào)用GoRouter.setApplication(...)
設(shè)置一個(gè)上下文。
10. 重寫跳轉(zhuǎn)URL服務(wù)
本庫刪除了PathReplaceService
展父,可以在IPretreatmentService
里實(shí)現(xiàn)相同功能墨林。使用card.setPath(...)
和card.setUri(...)
方法赁酝,效果一樣。
11. 開啟調(diào)試旭等,查看日志可以檢查使用java方式注冊(cè)的路由是否有重復(fù)提交的情況
route path[/xx/xx] duplicate commit!!!
route pathClass[class xx.xx] duplicate commit!!!
GoRouter日志tag為GoRouter
酌呆,GoRouter-Compiler日志tag為GoRouter::Compiler
,GoRouter-Gradle-Plugin日志tag為GoRouter::Gradle-Plugin
搔耕。
12. ARouter遷移指南
ARouter | GoRouter |
---|---|
ARouter | GoRouter |
init(app) | autoLoadRouteModule(app) |
navigation() | go() |
NavigationCallback | GoCallback |
IProvider | IService |
DegradeService | IDegradeService |
PretreatmentService | IPretreatmentService |
SerializationService | IJsonService |
Postcard | Card |
@Route | @Route |
@Route | @Service |
@Route | @Interceptor |
@Autowired | @Param |