組件是如何通訊的?揭祕ARouter路由機制

面試官: 有沒有使用過組件化,組件化通訊如何作到的,ARouter有用過嗎

心理分析:組件化通常在架構常常被考到,組件化的內容比較多,跟咱們平時的開發mvc的單體應用不同,組件化是團隊做戰,須要設計複雜的組件通訊與交互android

求職者: 從組件化的由來,優點 弊端開始,最後引出組件化的劣勢,組件通訊git

1 組件化

1.1 組件化初衷

  • APP版本不斷的迭代,新功能的不斷增長,業務也會變的愈來愈複雜,維護成本高。
  • 業務耦合度高,代碼愈來愈臃腫,團隊內部多人協做開發困難。
  • Android項目在編譯代碼的時候電腦會很是卡,又由於單一工程下代碼耦合嚴重,每修改一處代碼後都要從新編譯打包測試,致使很是耗時。
  • 方便單元測試,改動單獨一個業務模塊,不須要着重於關注其餘模塊被影響。

1.2 什麼是組件化

組件化就是將一個app分紅多個Module,以下圖,每一個Module都是一個組件(也能夠是一個基礎庫供組件依賴),開發的過程當中咱們能夠單獨調試部分組件,組件間不須要互相依賴,但能夠相互調用,最終發佈的時候全部組件以lib的形式被主app工程依賴並打包成一個apk。github

1.3 組件化優點

  • 組件化就是將通用模塊獨立出來,統一管理,以提升複用,將頁面拆分爲粒度更小的組件,組件內部除了包含UI實現,還包含數據層和邏輯層。
  • 每一個工程均可以獨立編譯、加快編譯速度,獨立打包。
  • 每一個工程內部的修改,不會影響其餘工程。
  • 業務庫工程能夠快速拆分出來,集成到其餘App中。
  • 迭代頻繁的業務模塊採用組件方式,業務線研發能夠互不干擾、提高協做效率,並控制產品質量,增強穩定性。
  • 並行開發,團隊成員只關注本身的開發的小模塊,下降耦合性,後期維護方便等。

2 組件化通訊

2.1 組件化通訊

​組件化互相不直接依賴,若是組件A想調用組件B的方法是不行的。不少開發者由於組件化之間通訊比較複雜 則放棄了組件化的使用面試

組件通訊有如下幾種方式:api

1.本地廣播
本地廣播,也就是LoacalBroadcastRecevier。更可能是用在同一個應用內的不一樣系統規定的組件進行通訊,好處在於:發送的廣播只會在本身的APP內傳播,不會泄漏給其餘的APP,其餘APP沒法向本身的APP發送廣播,不用被其餘APP干擾。本地廣播比如對講通訊,成本低,效率高,但有個缺點就是二者通訊機制所有委託與系統負責,咱們沒法干預傳輸途中的任何步驟,不可控制,通常在組件化通訊過程當中採用比例不高。
2.進程間的AIDL
​進程間的AIDL。這個粒度在於進程,而咱們組件化通訊過程每每是在線程中,何況AIDL通訊也是屬於系統級通訊,底層以Binder機制,雖然說Android提供模板供咱們實現,但每每使用者很差理解,交互比較複雜,每每也不適用應用於組件化通訊過程當中。
3.匿名的內存共享
匿名的內存共享。好比用Sharedpreferences,在處於多線程場景下,每每會線程不安全,這種更可能是存儲一一些變化不多的信息,好比說組件裏的配置信息等等
4.Intent Bundle傳遞
Intent Bundle傳遞。包括顯性和隱性傳遞,顯性傳遞須要明確包名路徑,組件與組件每每是須要互相依賴,這背離組件化中SOP(關注點分離原則),若是走隱性的話,不只包名路徑不能重複,須要定義一套規則,只有一個包名路徑出錯,排查起來也稍顯麻煩,這個方式每每在組件間內部傳遞會比較合適,組件外與其餘組件打交道則使用場景很少。

2.2 目前主流作法之一就是引入第三者,好比圖中的Base Module。

3 ARouter組件通訊框架

3.1 ARouter 簡介

是ARouter是阿里巴巴開源的Android平臺中對頁面、服務提供路由功能的中間件,提倡的是簡單且夠用。主要用做組件化通訊瀏覽器

GitHub:https://github.com/alibaba/ARouter安全

前言多線程

Intent intent = new Intent(mContext, XxxActivity.class);
 intent.putExtra("key","value");
 startActivity(intent);
 
 Intent intent = new Intent(mContext, XxxActivity.class);
 intent.putExtra("key","value");
 startActivityForResult(intent, 666);

上面一段代碼,在Android開發中,最多見也是最經常使用的功能就是頁面的跳轉,咱們常常須要面對從瀏覽器或者其餘App跳轉到本身App中頁面的需求,不過就算是簡簡單單的頁面跳轉,隨着時間的推移,也會遇到一些問題:架構

  1. 集中式的URL管理:談到集中式的管理,老是比較蛋疼,多人協同開發的時候,你們都去AndroidManifest.xml中定義各類IntentFilter,使用隱式Intent,最終發現AndroidManifest.xml中充斥着各類Schame,各類Path,須要常常解決Path重疊覆蓋、過多的Activity被導出,引起安全風險等問題
  2. 可配置性較差:Manifest限制於xml格式,書寫麻煩,配置複雜,能夠自定義的東西也較少
  3. 跳轉過程當中沒法插手:直接經過Intent的方式跳轉,跳轉過程開發者沒法干預,一些面向切面的事情難以實施,比方說登陸、埋點這種很是通用的邏輯,在每一個子頁面中判斷又很不合理,畢竟activity已經實例化了
  4. 跨模塊沒法顯式依賴:在App小有規模的時候,咱們會對App作水平拆分,按照業務拆分紅多個子模塊,之間徹底解耦,經過打包流程控制App功能,這樣方便應對大團隊多人協做,互相邏輯不干擾,這時候只能依賴隱式Intent跳轉,書寫麻煩,成功與否難以控制。

爲了解決以上問題,咱們須要一款可以解耦、簡單、功能多、定製性較強、支持攔截邏輯的路由組件:咱們選擇了Alibaba的ARouter,偷個懶,直接貼ARouter的中文介紹文檔:mvc

3.2 ARouter 優點

從 ARouter Github 瞭解到它的優點:

支持直接解析標準URL進行跳轉,並自動注入參數到目標頁面中 支持多模塊工程使用 支持添加多個攔截器,自定義攔截順序 支持依賴注入,可單獨做爲依賴注入框架使用 支持InstantRun 支持MultiDex(Google方案) 映射關係按組分類、多級管理,按需初始化 支持用戶指定全局降級與局部降級策略 頁面、攔截器、服務等組件均自動註冊到框架 支持多種方式配置轉場動畫 支持獲取Fragment 徹底支持Kotlin以及混編 典型的應用:

從外部URL映射到內部頁面,以及參數傳遞與解析 跨模塊頁面跳轉,模塊間解耦 攔截跳轉過程,處理登錄、埋點等邏輯

跨模塊API調用,經過控制反轉來作組件解耦

3、典型應用場景

  1. 從外部URL映射到內部頁面,以及參數傳遞與解析
  2. 跨模塊頁面跳轉,模塊間解耦
  3. 攔截跳轉過程,處理登錄、埋點等邏輯
  4. 跨模塊API調用,模塊間解耦(註冊ARouter服務的形式,經過接口互相調用)

4、基礎功能

  1. 添加依賴和配置
  2. apply plugin: 'com.neenbedankt.android-apt'
buildscript {
 repositories {
 jcenter()
 }
 dependencies {
 classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
 }
 }
 apt {
 arguments {
 moduleName project.getName();
 }
 }
 dependencies {
 apt 'com.alibaba:arouter-compiler:x.x.x'
 compile 'com.alibaba:arouter-api:x.x.x'
 ...
 }
添加註解
// 在支持路由的頁面、服務上添加註解(必選)
 // 這是最小化配置,後面有詳細配置
 @Route(path = "/test/1")
 public class YourActivity extend Activity {
 ...
 }
初始化SDK
ARouter.init(mApplication); // 儘量早,推薦在Application中初始化

發起路由操做 // 1. 應用內簡單的跳轉(經過URL跳轉在'中階使用'中) ARouter.getInstance().build("/test/1").navigation();

// 2. 跳轉並攜帶參數
 ARouter.getInstance().build("/test/1")
 .withLong("key1", 666L)
 .withString("key3", "888")
 .navigation();

5、進階用法

經過URL跳轉

// 新建一個Activity用於監聽Schame事件
 // 監聽到Schame事件以後直接傳遞給ARouter便可
 // 也能夠作一些自定義玩法,比方說改改URL之類的
 // http://www.example.com/test/1
 public class SchameFilterActivity extends Activity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 // 外面用戶點擊的URL
 Uri uri = getIntent().getData();
 // 直接傳遞給ARouter便可
 ARouter.getInstance().build(uri).navigation();
 finish();
 }
 }
 // AndroidManifest.xml 中 的參考配置
 <activity android:name=".activity.SchameFilterActivity">
 <!-- Schame -->
 <intent-filter>
 <data
 android:host="m.aliyun.com"
 android:scheme="arouter"/>
 <action android:name="android.intent.action.VIEW"/>
 <category android:name="android.intent.category.DEFAULT"/>
 <category android:name="android.intent.category.BROWSABLE"/>
 </intent-filter>
 <!-- App Links -->
 <intent-filter android:autoVerify="true">
 <action android:name="android.intent.action.VIEW"/>
 <category android:name="android.intent.category.DEFAULT"/>
 <category android:name="android.intent.category.BROWSABLE"/>
 <data
 android:host="m.aliyun.com"
 android:scheme="http"/>
 <data
 android:host="m.aliyun.com"
 android:scheme="https"/>
 </intent-filter>
 </activity>

使用ARouter協助解析參數類型

// URL中的參數會默認以String的形式保存在Bundle中
 // 若是但願ARouter協助解析參數(按照不一樣類型保存進Bundle中)
 // 只須要在須要解析的參數上添加 @Param 註解
 @Route(path = "/test/1")
 public class Test1Activity extends Activity {
 @Param // 聲明以後,ARouter會從URL中解析對應名字的參數,並按照類型存入Bundle
 public String name;
 @Param
 private int age;
 @Param(name = "girl") // 能夠經過name來映射URL中的不一樣參數
 private boolean boy;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 name = getIntent().getStringExtra("name");
 age = getIntent().getIntExtra("age", -1);
 boy = getIntent().getBooleanExtra("girl", false); // 注意:使用映射以後,要從Girl中獲取,而不是boy
 }
 }

開啓ARouter參數自動注入(實驗性功能,不建議使用,正在開發保護策略)

// 首先在Application中重寫 attachBaseContext方法,並加入ARouter.attachBaseContext();
 @Override
 protected void attachBaseContext(Context base) {
 super.attachBaseContext(base);
 ARouter.attachBaseContext();
 }
 // 設置ARouter的時候,開啓自動注入
 ARouter.enableAutoInject();
 // 至此,Activity中的屬性,將會由ARouter自動注入,無需 getIntent().getStringExtra("xxx")等等

聲明攔截器(攔截跳轉過程,面向切面搞事情)

// 比較經典的應用就是在跳轉過程當中處理登錄事件,這樣就不須要在目標頁重複作登錄檢查
 // 攔截器會在跳轉之間執行,多個攔截器會按優先級順序依次執行
 @Interceptor(priority = 666, name = "測試用攔截器")
 public class TestInterceptor implements IInterceptor {
 /**
 * The operation of this interceptor.
 *
 * @param postcard meta
 * @param callback cb
 */
 @Override
 public void process(Postcard postcard, InterceptorCallback callback) {
 ...
 callback.onContinue(postcard); // 處理完成,交還控制權
 // callback.onInterrupt(new RuntimeException("我以爲有點異常")); // 以爲有問題,中斷路由流程
 // 以上兩種至少須要調用其中一種,不然會超時跳過
 }
 /**
 * Do your init work in this method, it well be call when processor has been load.
 *
 * @param context ctx
 */
 @Override
 public void init(Context context) {
 }
 }

處理跳轉結果

// 經過兩個參數的navigation方法,能夠獲取單次跳轉的結果
 ARouter.getInstance().build("/test/1").navigation(this, new NavigationCallback() {
 @Override
 public void onFound(Postcard postcard) {
 ...
 }
 @Override
 public void onLost(Postcard postcard) {
 ...
 }
 });

自定義全局降級策略

// 實現DegradeService接口,並加上一個Path內容任意的註解便可
 @Route(path = "/xxx/xxx") // 必須標明註解
 public class DegradeServiceImpl implements DegradeService {
 /**
 * Router has lost.
 *
 * @param postcard meta
 */
 @Override
 public void onLost(Context context, Postcard postcard) {
 // do something.
 }
 /**
 * Do your init work in this method, it well be call when processor has been load.
 *
 * @param context ctx
 */
 @Override
 public void init(Context context) {
 }
 }

爲目標頁面聲明更多信息

// 咱們常常須要在目標頁面中配置一些屬性,比方說"是否須要登錄"之類的
 // 能夠經過 Route 註解中的 extras 屬性進行擴展,這個屬性是一個 int值,換句話說,單個int有4字節,也就是32位,能夠配置32個開關
 // 剩下的能夠自行發揮,經過字節操做能夠標識32個開關
 @Route(path = "/test/1", extras = Consts.XXXX)

使用ARouter管理服務(一) 暴露服務

/**
 * 聲明接口
 */
 public interface IService extends IProvider {
 String hello(String name);
 }
 /**
 * 實現接口
 */
 @Route(path = "/service/1", name = "測試服務")
 public class ServiceImpl implements IService {
 @Override
 public String hello(String name) {
 return "hello, " + name;
 }
 /**
 * Do your init work in this method, it well be call when processor has been load.
 *
 * @param context ctx
 */
 @Override
 public void init(Context context) {
 }
 }

使用ARouter管理服務(二) 發現服務

1. 能夠經過兩種API來獲取Service,分別是ByName、ByType
 IService service = ARouter.getInstance().navigation(IService.class); // ByType
 IService service = (IService) ARouter.getInstance().build("/service/1").navigation(); // ByName
 service.hello("zz");
 2. 注意:推薦使用ByName方式獲取Service,ByType這種方式寫起來比較方便,但若是存在多實現的狀況時,SDK不保證能獲取到你想要的實現

使用ARouter管理服務(三) 管理依賴

能夠經過ARouter service包裝您的業務邏輯或者sdk,在service的init方法中初始化您的sdk,不一樣的sdk使用ARouter的service進行調用,
每個service在第一次使用的時候會被初始化,即調用init方法。
這樣就能夠告別各類亂七八糟的依賴關係的梳理,只要能調用到這個service,那麼這個service中所包含的sdk等就已經被初始化過了,徹底不須要
關心各個sdk的初始化順序。

6、更多功能

初始化中的其餘設置

ARouter.openLog(); // 開啓日誌
ARouter.printStackTrace(); // 打印日誌的時候打印線程堆棧

詳細的API說明

// 構建標準的路由請求
 ARouter.getInstance().build("/home/main").navigation();
 // 構建標準的路由請求,並指定分組
 ARouter.getInstance().build("/home/main", "ap").navigation();
 // 構建標準的路由請求,經過Uri直接解析
 Uri uri;
 ARouter.getInstance().build(uri).navigation();
 // 構建標準的路由請求,startActivityForResult
 // navigation的第一個參數必須是Activity,第二個參數則是RequestCode
 ARouter.getInstance().build("/home/main", "ap").navigation(this, 5);
 // 直接傳遞Bundle
 Bundle params = new Bundle();
 ARouter.getInstance()
 .build("/home/main")
 .with(params)
 .navigation();
 // 指定Flag
 ARouter.getInstance()
 .build("/home/main")
 .withFlags();
 .navigation();
 // 以爲接口不夠多,能夠直接拿出Bundle賦值
 ARouter.getInstance()
 .build("/home/main")
 .getExtra();
 // 使用綠色通道(跳過全部的攔截器)
 ARouter.getInstance().build("/home/main").greenChannal().navigation();

資料免費領取方式:如今關注我而且加入羣聊
羣號:1018342383

image.png

相關文章
相關標籤/搜索