組件間跳轉(zhuǎn) & ARouter路由
常見的跳轉(zhuǎn),對(duì)于用戶來(lái)說(shuō)就是頁(yè)面跳轉(zhuǎn),對(duì)應(yīng)Android中就是activity或fragment間的跳轉(zhuǎn),那我們肯定第一時(shí)間就想到,
用startActivity()發(fā)送一個(gè)包裝好的intent,將intent交給ActivityManagerService完成新的Activity創(chuàng)建。
但是組件化中,兩個(gè)功能模塊是不存在直接依賴關(guān)系的(通過(guò)baseModule間接依賴),那么包裝intent時(shí)就會(huì)發(fā)現(xiàn)引用不了其他module
中的activity類(xxx.class)。
這如何解決呢,靈光一閃,我們Android中的intent跳轉(zhuǎn)可是分為顯示和隱式,雖然上面常用的顯示跳轉(zhuǎn)行不通,但我們還有隱式跳轉(zhuǎn);
于是乎,隱式跳轉(zhuǎn)緩緩起身,拍了拍身上積落的灰塵,扶了扶頭上的斗笠,穩(wěn)了穩(wěn)腰間的佩劍,從墻角向我們款款走來(lái)
1. 隱式跳轉(zhuǎn)
//隱式跳轉(zhuǎn)的目的activity
class ImplicitActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_implicit)
}
}
//清單文件注冊(cè)
<activity android:name=".activity.ImplicitActivity">
<intent-filter>
<action android:name="com.jinyang.implicitAct"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
//使用隱式acition跳轉(zhuǎn)
fun onBtnClick(view: View) {
when (view.id) {
R.id.button_implicit -> startActivity(Intent("com.jinyang.implicitAct"))
}
}
//隱式跳轉(zhuǎn)還可以通過(guò)app包名 + 目標(biāo)activity全路徑跳轉(zhuǎn)
val intent = Intent()
intent.setClassName(packageName, "com.jinyang.mylibrary.Test01Activity")
intent.component = ComponentName(packageName, "com.jinyang.mylibrary.Test01Activity")
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
}
這里還有個(gè)安全隱患,其他app也可以通過(guò)隱式intent跳轉(zhuǎn)到我們的activity,需要清單文件中設(shè)置exported=false,確保只有自己的app能啟動(dòng)組件;
隱式跳轉(zhuǎn)是原生的,和廣播一樣,范圍是整個(gè)Android系統(tǒng)都能收到,是否有更好的方式呢
2. ARouter路由跳轉(zhuǎn)
原理借用計(jì)算機(jī)網(wǎng)絡(luò)中的路由器概念,將各組件看成不同的局域網(wǎng),通過(guò)路由做中轉(zhuǎn)站,這個(gè)中轉(zhuǎn)站可以攔截一些不安全的跳轉(zhuǎn),或者設(shè)定一些特定的攔截服務(wù);
路由和原生跳轉(zhuǎn)的對(duì)比:
1. 原生顯示跳轉(zhuǎn)是直接的類依賴,耦合嚴(yán)重;路由通過(guò)URL索引,無(wú)需依賴;
2. 原生隱式跳轉(zhuǎn)通過(guò)AndroidManifest集中管理,協(xié)作開發(fā)困難;路由則是分布式管理頁(yè)面配置;
3. 原生需要在 AndroidManifest中注冊(cè),擴(kuò)展性差;路由用注解來(lái)注冊(cè),方便擴(kuò)展;
4. 原生只要調(diào)用了startActivity就交由Android系統(tǒng)控制,過(guò)程無(wú)法干預(yù),失敗無(wú)法降級(jí);
路由使用AOP切面編程可以進(jìn)行控制跳轉(zhuǎn)的過(guò)濾,有靈活的降級(jí)方式;
ARouter是阿里巴巴開源的路由框架;
如何使用:
//1. 在base module中添加依賴
implementation 'com.alibaba:arouter-api:1.4.0'
annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'
//2. 各模塊build.gradle的defaultConfig中加入
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
//各模塊build.gradle的dependencies中加入
annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'
//3. 在application中初始化ARouter
if (BuildConfig.isDebug){
ARouter.openLog();
ARouter.openDebug();
//需要在init之前配置才有效
}
ARouter.init(MyApplication.this);
//4.在支持路由的頁(yè)面上添加注解(必選)
// 這里的路徑需要注意的是至少需要有兩級(jí),/xx/xx
@Route(path = ARouterPath.ACTIVITY_PATH_MAIN)
public class MainActivity extends BaseActivity<IBaseView, MainPresenter> {
...
}
//5.跳轉(zhuǎn)
ARouter.getInstance()
.build(ARouterPath.ACTIVITY_PATH_FRAGMENTSACTIVITY)
.navigation();
//也可以攜帶參數(shù)或用uri跳轉(zhuǎn)
Uri uri = Uri.parse(ARouterPath.ACTIVITY_PATH_AROUTER2);
ARouter.getInstance()
.build(uri)
// .build(ARouterPath.ACTIVITY_PATH_AROUTER2)
.withTransition(R.anim.anim_act_in, R.anim.anim_act_out)
.withInt("key1", 1024)
.withString("key2", "我是ARouter跳轉(zhuǎn)傳遞的字符串啊")
.navigation();
//所傳參數(shù)通過(guò)在聲明變量時(shí)添加注解獲取
@Autowired
int key1;
@Autowired(name = "key2")
String keyString;
// 如果需要傳遞自定義對(duì)象,新建一個(gè)類(并非自定義對(duì)象類),
//然后實(shí)現(xiàn) SerializationService,
并使用@Route注解標(biāo)注(方便用戶自行選擇序列化方式),例如:
@Route(path = "/yourservicegroupname/json")
public class JsonServiceImpl implements SerializationService {
@Override
public void init(Context context) {
}
@Override
public <T> T json2Object(String text, Class<T> clazz) {
return JSON.parseObject(text, clazz);
}
@Override
public String object2Json(Object instance) {
return JSON.toJSONString(instance);
}
}
//使用攔截器(攔截跳轉(zhuǎn)過(guò)程,面向切面編程)
//1. 要使用攔截器需要向在build.gradle中添加插件
apply plugin: 'com.alibaba.arouter'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "com.alibaba:arouter-register:?"
}
}
// 比較經(jīng)典的應(yīng)用就是在跳轉(zhuǎn)過(guò)程中處理登陸事件,這樣就不需要在目標(biāo)頁(yè)重復(fù)做登陸檢查
// 攔截器會(huì)在跳轉(zhuǎn)之間執(zhí)行,多個(gè)攔截器會(huì)按優(yōu)先級(jí)順序依次執(zhí)行
@Interceptor(priority = 8, name = "測(cè)試用攔截器")
public class TestInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
...
callback.onContinue(postcard); // 處理完成,交還控制權(quán)
// callback.onInterrupt(new RuntimeException("我覺得有點(diǎn)異常"));
// 覺得有問題,中斷路由流程
// 以上兩種至少需要調(diào)用其中一種,否則不會(huì)繼續(xù)路由
}
@Override
public void init(Context context) {
// 攔截器的初始化,會(huì)在sdk初始化的時(shí)候調(diào)用該方法,僅會(huì)調(diào)用一次
}
}
// 例如登陸校驗(yàn):
@Interceptor( priority = 1)
public class LoginInterceptor implements IInterceptor {
private Context context;
private Postcard postcard;
private InterceptorCallback callback;
private static final int CAMERA_PERMISSION_RESULT = 101;
@Override
public void init(Context context) {
Log.d("LJY_LOG", "PermissionInterceptor.init");
this.context = context;
}
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
Log.d("LJY_LOG", "PermissionInterceptor.process");
this.postcard = postcard;
this.callback = callback;
if (postcard.getPath().equals("/jinyang/arouterdemo/DemoActivity")) {
if (MyApplication.isLogin) {
callback.onContinue(postcard);
} else {
Log.d("LJY_LOG", "請(qǐng)先登錄");
}
} else {
callback.onContinue(postcard);
}
}
}
//處理跳轉(zhuǎn)結(jié)果
// 使用兩個(gè)參數(shù)的navigation方法,可以獲取單次跳轉(zhuǎn)的結(jié)果
ARouter.getInstance().build("/test/1").navigation(this, new NavigationCallback() {
@Override
public void onFound(Postcard postcard) {
...
}
@Override
public void onLost(Postcard postcard) {
...
}
});
//自定義全局降級(jí)策略
// 實(shí)現(xiàn)DegradeService接口,并加上一個(gè)Path內(nèi)容任意的注解即可
@Route(path = "/xxx/xxx")
public class DegradeServiceImpl implements DegradeService {
@Override
public void onLost(Context context, Postcard postcard) {
// do something.
}
@Override
public void init(Context context) {
}
}
//以上是一些常用的例子,更多的使用可以去https://github.com/alibaba/ARouter查看
3. ARouter路由原理
調(diào)用ARouter.init方法,在LogisticsCenter中通過(guò)編譯時(shí)注解,生成三個(gè)文件,Group(IRouteGroup),
Providers(IProviderGroup),Root(IRouteRoot),使用Warehouse將文件保存到三個(gè)不同的HashMap中,
Warehouse就相當(dāng)于路由表,保存著全部模塊的跳轉(zhuǎn)關(guān)系;
- 通過(guò)ARouter.navigation封裝postcard對(duì)象;
- 通過(guò)ARouter索引傳遞到LogisticsCenter(路由中轉(zhuǎn)站),詢問是否存在跳轉(zhuǎn)對(duì)象;
- 如果存在則設(shè)置綠色通道開關(guān);
- 判斷是否綠色通行和是否能通過(guò)攔截服務(wù);
- 全部通過(guò)就會(huì)調(diào)用ActivityCompat.startActivity方法來(lái)跳轉(zhuǎn)到目的Activity
所以,ARouter實(shí)際還是使用原生的Framework機(jī)制startActivity,只是通過(guò)apt注解的形式制造出跳轉(zhuǎn)規(guī)則,并認(rèn)為的攔截跳轉(zhuǎn)和設(shè)置跳轉(zhuǎn)條件;
4. 組件化最佳路由
說(shuō)了半天路由,那么為什么現(xiàn)在普遍在組件化中使用路由機(jī)制作為跳轉(zhuǎn)方案呢,畢竟其底層也是調(diào)用startActivity,隱式跳轉(zhuǎn)也可以滿足;
考慮如下場(chǎng)景,當(dāng)移除一些功能module和跳轉(zhuǎn)關(guān)系時(shí),使用startActivity將無(wú)法跳轉(zhuǎn),且沒有提示,使用路由方案,可設(shè)置統(tǒng)一的攔截和提示;
還可以在跳轉(zhuǎn)前進(jìn)行登錄狀態(tài)的攔截,雖然這點(diǎn)我們也可以通過(guò)aop注解實(shí)現(xiàn),如下,但路由方案顯然更方便;
//登錄校驗(yàn)的接口
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedLogin {
}
// 登錄校驗(yàn)的實(shí)現(xiàn)
@Around("execution(@NeedLogin * *(..))")
public void isLoginOn(ProceedingJoinPoint joinPoint) throws Throwable {
if (MyApplication.getMyApplication().getAppConfig().isLogged()) {
joinPoint.proceed();
} else {
if (joinPoint.getThis() instanceof BaseActivity) {
ActivityUtils.startActivity(
(BaseActivity) joinPoint.getThis(),
UserLoginActivity.class
);
} else if (joinPoint.getThis() instanceof BaseFragment) {
ActivityUtils.startActivity(
((BaseFragment) joinPoint.getThis()).getActivity(),
UserLoginActivity.class
);
}
}
}
//登錄校驗(yàn)的使用
@NeedLogin
private void setJump(BaseJumpModel itemData) {
//todo something
}
還有就是路由表的引入,就不需要在AndroidManifest中聲明隱式跳轉(zhuǎn);
除了阿里的ARouter,現(xiàn)在的開源路由框架還有很多,如ActivityRouter,天貓統(tǒng)跳協(xié)議,DeepLinkDispatch,OkDeepLink等等,可以根據(jù)自己的項(xiàng)目實(shí)際情況進(jìn)行技術(shù)選型