Android組件化架構(gòu) - 3. 組件間跳轉(zhuǎn) & ARouter路由

組件間跳轉(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ù)選型

我是今陽(yáng),如果想要進(jìn)階和了解更多的干貨,歡迎關(guān)注公眾號(hào)”今陽(yáng)說(shuō)“接收我的最新文章
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容