1. 程式人生 > >ARouter路由框架解析

ARouter路由框架解析

一、ARouter介紹及主要應用場景:

1、介紹:

是ARouter是阿里巴巴開源的Android平臺中對頁面、服務提供路由功能的中介軟體,提倡的是簡單且夠用。

2、原生的路由方案存在的問題

首先談一談原生的路由方案存在的問題以及為什麼需要路由框架。我們所使用的原生路由方案一般是通過顯式intent和隱式intent兩種方式實現的,而在顯式intent的情況下,因為會存在直接的類依賴的問題,導致耦合非常嚴重;而在隱式intent情況下,則會出現規則集中式管理,導致協作變得非常困難。而且一般而言配置規則都是在Manifest中的,這就導致了擴充套件性較差。除此之外,使用原生的路由方案會出現跳轉過程無法控制的問題,因為一旦使用了StartActivity()就無法插手其中任何環節了,只能交給系統管理,這就導致了在跳轉失敗的情況下無法降級,而是會直接丟擲運營級的異常。
image.png


這時候如果考慮使用自定義的路由元件就可以解決以上的一些問題,比如通過URL索引就可以解決類依賴的問題;通過分散式管理頁面配置可以解決隱式intent中集中式管理Path的問題;自己實現整個路由過程也可以擁有良好的擴充套件性,還可以通過AOP的方式解決跳轉過程無法控制的問題,與此同時也能夠提供非常靈活的降級方式。

3、為什麼要用路由元件

為什麼要用路由元件
前面提到的主要是開發與協作中的問題,而使用一款路由框架時還會涉及到其他的兩個大方面:一方面是元件化,而另一方面就是Native和H5的問題。剛才所提到的主要是開發和協作中作為開發者所需要面對的問題,而一旦一款APP達到一定體量的時候,業務就會膨脹得比較嚴重,而開發團隊的規模也會越來越大,這時候一般都會提出元件化的概念。元件化就是將APP按照一定的功能和業務拆分成多個小元件,不同的元件由不同的開發小組來負責,這樣就可以解決大型APP開發過程中的開發與協作的問題,將這些問題分散到小的APP中。目前而言元件化已經有非常多比較成熟的方案了,而自定義路由框架也可以非常好地解決整個APP完成元件化之後模組之間沒有耦合的問題,因為沒有耦合時使用原生的路由方案肯定是不可以的。
image.png


另外一個問題就是Native與H5的問題,因為現在的APP很少是純Native的,也很少會有純H5的,一般情況下都是將兩者進行結合。這時候就需要非常便捷並且統一的跳轉方案,因為在H5中是無法使用StartActivity()跳轉到Native頁面的,而從Native跳轉到H5頁面也只能通過配置瀏覽器的方式實現。

4、路由框架的特點

為了解決以上的問題就需要實現一個自定義的路由框架,而路由框架一般都具有以下的三種特點:
分發:把一個URL或者請求按照一定的規則分配給一個服務或者頁面來處理,這個流程就是分發,分發是路由框架最基本的功能,當然也可以理解成為簡單的跳轉。
管理:將元件和頁面按照一定的規則管理起來,在分發的時候提供搜尋、載入、修改等操作,這部分就是管理,也是路由框架的基礎,上層功能都是建立在管理之上。
控制

:就像路由器一樣,路由的過程中,會有限速、遮蔽等一些控制操作,路由框架也需要在路由的過程中,對路由操作做一些定製性的擴充套件,比方剛才提到的AOP,後期的功能更新,也是圍繞這個部分來做的。

5、ARouter的7個優勢

image.png
優勢一:直接解析URL路由,解析引數並賦值到對應目標欄位的頁面中。
優勢二:支援多模組專案,因為現在很少有APP是單模組的專案,一般都是多模組單工程的,由不同的團隊負責不同的模組開發,這時候支援多模組專案開發就顯得尤為重要。
優勢三:支援InstantRun,目前很多路由框架並不支援InstantRun,而InstantRun是Google在AndroidStudio2.0阿爾法版本中提供的新功能,其類似於程式碼的日更新,其只不過面向的是開發過程,這樣做可以在開發的過程中減少開發和編譯的次數,可以簡單地將程式碼修改即時地同步到APK中,從而可以大規模降低開發複雜度。
優勢四:允許自定義攔截器,ARouter是支援攔截器的,而攔截器其實就是AOP的實現,可以自定義多個攔截器解決一些面向行為程式設計上出現的問題。
優勢五:ARouter可以提供IoC容器,IoC其實就是控制反轉,這一部分做過服務端開發的朋友可能比較瞭解,因為服務端開發經常用到的Spring框架能夠提供的一個非常重要的能力就是控制反轉。
優勢六:對映關係自動註冊,在頁面不是很多的小型APP上面,自動註冊並不會體現出太大優勢,但是對於大型APP而言,可能頁面數量已經達到的幾十個或者數百個,在這樣的情況下,自動註冊就顯得非常重要了,因為不可能將每一個頁面都通過程式碼的方式進行註冊。
優勢七:靈活的降級策略,ARouter可以提供很多種降級策略供使用者自行選擇,而原生的路由方案存在無法靈活降級的問題,StartActivity()一旦失敗將會丟擲運營級異常。

6、應用場景:
  • 從外部URL對映到內部頁面,以及引數傳遞與解析
  • 跨模組頁面跳轉,模組間解耦
  • 攔截跳轉過程,處理登陸、埋點等邏輯

二、ARouter的基本使用步驟:

1、新增依賴和配置
android {
    defaultConfig {
    ...
    javaCompileOptions {
        annotationProcessorOptions {
        arguments = [ moduleName : project.getName() ]
        }
    }
    }
}

dependencies {
    // 替換成最新版本, 需要注意的是api
    // 要與compiler匹配使用,均使用最新版可以保證相容
    compile 'com.alibaba:arouter-api:x.x.x'
    annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
    ...
}
// 替換成最新版本, 需要注意的是api
// 要與compiler匹配使用,均使用最新版可以保證相容
compile 'com.alibaba:arouter-api:x.x.x'
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'

- 這裡替換相應的Api和compiler

2、添加註解及初始化
  • ARouter初始化
if (isDebug()) {           // 這兩行必須寫在init之前,否則這些配置在init過程中將無效
    ARouter.openLog();     // 列印日誌
    ARouter.openDebug();   // 開啟除錯模式(如果在InstantRun模式下執行,必須開啟除錯模式!線上版本需要關閉,否則有安全風險)
}
ARouter.init(mApplication); // 儘可能早,推薦在Application中初始化
  • 在需要跳轉的Activity上進行註解配置
// 在支援路由的頁面上添加註解(必選)
// 這裡的路徑需要注意的是至少需要有兩級,/xx/xx
@Route(path = "/com/ARouter2Activity")
public class ARouter2Activity extends AppCompatActivity {
      super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_arouter2);
    //....
}
  • 應用中的簡單跳轉
 ARouter.getInstance().build("/com/ARouter2Activity").navigation();
  • 應用中攜帶引數的跳轉
ARouter.getInstance().build("/com/ARouter2Activity")
                        .withString("name","onex")
                        .withInt("age",22)
                        .navigation();
  • 在目標Activity中接受相關的引數
    為每一個引數宣告一個欄位,並使用 @Autowired 標註
    URL中不能傳遞Parcelable型別資料,通過ARouter api可以傳遞
Parcelable物件
@Route(path = "/com/ARouter2Activity")
public class ARouter2Activityextends Activity {
    @Autowired
    public String name;
    @Autowired
    int age;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ARouter.getInstance().inject(this);

    // ARouter會自動對欄位進行賦值,無需主動獲取
    Log.d("param", name + age + boy);
    }
}

這裡需要注意的是

 ARouter.getInstance().inject(this); 

三、進階使用

1、Activity之間傳遞自定義物件
  • 如果需要傳遞自定義物件,需要實現 SerializationService,並使用@Route註解標註(方便使用者自行選擇序列化方式)
@Route(path = "/service/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);
    }
}
  • 以下是ARouter支援的傳遞的資料型別
                TestParcelable testParcelable = new TestParcelable("jack", 666);
                TestObj testObj = new TestObj("Rose", 777);
                List<TestObj> objList = new ArrayList<>();
                objList.add(testObj);

                Map<String, List<TestObj>> map = new HashMap<>();
                map.put("testMap", objList);

                ARouter.getInstance().build("/test/activity1")
                        .withString("name", "老王")
                        .withInt("age", 18)
                        .withBoolean("boy", true)
                        .withLong("high", 180)
                        .withString("url", "https://a.b.c")
                        .withParcelable("pac", testParcelable)
                        .withObject("obj", testObj)
                        .withObject("objList", objList)
                        .withObject("map", map)
                        .navigation();

需要注意的是:我們在傳遞的時候使用的是什麼樣的KEY,在目標Activity的地方使用相同的KEY,進行宣告變數例如:

Man man = new Man("OneX","男");
ARouter.getInstance().build("/com/ARouter2Activity")
       .withObject("obj",man)
       .navigation();

目標頁面Code

    @Autowired
    Man obj;
2、通過Url進行跳轉
  • 新建一個Activity用於監聽Schame事件,之後直接把url傳遞給ARouter即可(類似於轉發路由的Activity)
public class SchameFilterActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Uri uri = getIntent().getData();
    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>
</activity>
3、設定攔截器
  • 比較經典的應用就是在跳轉過程中處理登陸事件,這樣就不需要在目標頁重複做登陸檢攔截器會在跳轉之間執行
  • 多個攔截器會按優先順序順序依次執行
@Interceptor(priority = 8, name = "測試用攔截器")
public class TestInterceptor implements IInterceptor {
    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
    ...
    callback.onContinue(postcard);  // 處理完成,交還控制權
    // callback.onInterrupt(new RuntimeException("我覺得有點異常"));      // 覺得有問題,中斷路由流程

    // 以上兩種至少需要呼叫其中一種,否則不會繼續路由
    }

    @Override
    public void init(Context context) {
    // 攔截器的初始化,會在sdk初始化的時候呼叫該方法,僅會呼叫一次
    }
}
4、監聽路由跳轉結果
// 使用兩個引數的navigation方法,可以獲取單次跳轉的結果
ARouter.getInstance().build("/test/1").navigation(this, new NavigationCallback() {
    @Override
    public void onFound(Postcard postcard) {
      ...
    }

    @Override
    public void onLost(Postcard postcard) {
    ...
    }
});
5、獲取Fragment的物件
// 獲取Fragment
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
6、通過依賴注入解耦:服務管理,暴露服務
// 宣告介面,其他元件通過介面來呼叫服務
public interface HelloService extends IProvider {
    String sayHello(String name);
}

// 實現介面
@Route(path = "/service/hello", name = "測試服務")
public class HelloServiceImpl implements HelloService {

    @Override
    public String sayHello(String name) {
    return "hello, " + name;
    }

    @Override
    public void init(Context context) {

    }
}

在Class中的相關使用

public class Test {
    @Autowired
    HelloService helloService;

    @Autowired(name = "/service/hello")
    HelloService helloService2;

    HelloService helloService3;

    HelloService helloService4;

    public Test() {
    ARouter.getInstance().inject(this);
    }

    public void testService() {
     // 1. (推薦)使用依賴注入的方式發現服務,通過註解標註欄位,即可使用,無需主動獲取
     // Autowired註解中標註name之後,將會使用byName的方式注入對應的欄位,不設定name屬性,會預設使用byType的方式發現服務(當同一介面有多個實現的時候,必須使用byName的方式發現服務)
    helloService.sayHello("Vergil");
    helloService2.sayHello("Vergil");

    // 2. 使用依賴查詢的方式發現服務,主動去發現服務並使用,下面兩種方式分別是byName和byType
    helloService3 = ARouter.getInstance().navigation(HelloService.class);
    helloService4 = (HelloService) ARouter.getInstance().build("/service/hello").navigation();
    helloService3.sayHello("Vergil");
    helloService4.sayHello("Vergil");
    }
}

四、注意事項及混淆配置

1、初始化中的其他配置
ARouter.openLog(); // 開啟日誌
ARouter.openDebug(); // 使用InstantRun的時候,需要開啟該開關,上線之後關閉,否則有安全風險
ARouter.printStackTrace(); // 列印日誌的時候列印執行緒堆疊
2、詳細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();

// 獲取Fragment
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();

// 物件傳遞
ARouter.getInstance()
    .withObject("key", new TestObj("Jack", "Rose"))
    .navigation();

// 覺得介面不夠多,可以直接拿出Bundle賦值
ARouter.getInstance()
        .build("/home/main")
        .getExtra();

// 轉場動畫(常規方式)
ARouter.getInstance()
    .build("/test/activity2")
    .withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom)
    .navigation(this);

// 轉場動畫(API16+)
ActivityOptionsCompat compat = ActivityOptionsCompat.
    makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2, 0, 0);

// ps. makeSceneTransitionAnimation 使用共享元素的時候,需要在navigation方法中傳入當前Activity

ARouter.getInstance()
    .build("/test/activity2")
    .withOptionsCompat(compat)
    .navigation();

// 使用綠色通道(跳過所有的攔截器)
ARouter.getInstance().build("/home/main").greenChannel().navigation();

// 使用自己的日誌工具列印日誌
ARouter.setLogger();
3、路由中的分組概念
  • SDK中針對所有的路徑(/test/1 /test/2)進行分組,分組只有在分組中的某一個路徑第一次被訪問的時候,該分組才會被初始化
  • 通過 @Route 註解主動指定分組,否則使用路徑中第一段字串(/*/)作為分組
  • 注意:一旦主動指定分組之後,應用內路由需要使用 ARouter.getInstance().build(path, group) 進行跳轉,手動指定分組,否則無法找到
@Route(path = "/test/1", group = "app")
4、新增混淆規則(如果使用了Proguard)
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}

# 如果使用了 byType 的方式獲取 Service,需新增下面規則,保護介面
-keep interface * implements com.alibaba.android.arouter.facade.template.IProvider

# 如果使用了 單類注入,即不定義介面實現 IProvider,需新增下面規則,保護實現
-keep class * implements com.alibaba.android.arouter.facade.template.IProvider