初探Arouter框架

 

學習Arouter框架

 

最近公司需要重構項目,需要用到jecket page和Arouter框架。利用一晚上的時間將這兩個東西快速地學習一下。

·首先是Arouter

ARouter官方地址:https://github.com/alibaba/ARouter/blob/master/README_CN.md

 

背景

傳統的Activity頁面跳轉有兩種方式,一種是顯示跳轉(通過Intent),一種是隱式跳轉(通過scheme的方式匹配AndroidManifest中的規則)。他們都存在的缺點,

顯示跳轉的缺點:

因爲會存在直接的類依賴的問題,導致耦合非常嚴重

 

隱式跳轉的缺點:

出現規則集中式管理,導致協作變得非常困難。一般而言配置規則都是在Manifest中的,這就導致了擴展性較差

 

共同的缺點:

使用原生的路由方案會出現跳轉過程無法控制的問題,因爲一旦使用了StartActivity()就無法插手其中任何環節了,只能交給系統管理,這就導致了在跳轉失敗的情況下無法降級,而是會直接crash。

 

Arouter是什麼?

一個用於幫助 Android App 進行組件化改造的框架 —— 支持模塊間的路由、通信、解耦。

 

有什麼優點?

簡單來說就是獨立性強,容錯性強,兼容性強,非常適合團隊模塊化開發。

說複雜點就是下面這些了:

  1. 支持直接解析標準URL進行跳轉,並自動注入參數到目標頁面中
  2. 支持多模塊工程使用
  3. 支持添加多個攔截器,自定義攔截順序
  4. 支持依賴注入,可單獨作爲依賴注入框架使用
  5. 支持InstantRun
  6. 支持MultiDex(Google方案)
  7. 映射關係按組分類、多級管理,按需初始化
  8. 支持用戶指定全局降級與局部降級策略
  9. 頁面、攔截器、服務等組件均自動註冊到框架
  10. 支持多種方式配置轉場動畫
  11. 支持獲取Fragment
  12. 完全支持Kotlin以及混編(配置見文末 其他#5)
  13. 支持第三方 App 加固(使用 arouter-register 實現自動註冊)
  14. 支持生成路由文檔
  15. 提供 IDE 插件便捷的關聯路徑和目標類

如果你看到這裏還不知道這個框架是拿來幹嘛的,那麼你可以狹義地把它認爲是一種打開activity的框架,該框架提供了很多實用的功能。

 

用法

零、引入庫

模塊的build.gradle中

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

 

Application,別忘記在AndroidManifest中引用咯

public class MyApp extends Application {
    private boolean isDebug  = true;

    @Override
    public void onCreate() {
        super.onCreate();
        if(isDebug){//需要寫在init前面,否者無效
            ARouter.openLog();
            ARouter.openDebug();
        }
        ARouter.init(this);
    }
}

 

一、簡單跳轉Activity

Test1Activity

// 在支持路由的頁面上添加註解(必選)
// 這裏的路徑需要注意的是至少需要有兩級,/xx/xx
@Route(path = "/test/activity1")
public class Test1Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test1);
    }
}

 

MainActivity  

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void clickLis(View view) {
        switch (view.getId()){
            case R.id.btn_next:
                // 1. 應用內簡單的跳轉(通過URL跳轉在'進階用法'中)
                ARouter.getInstance().build("/test/activity1").navigation();
                break;
        }
    }
}

 

 

二、帶參數跳轉Activity

MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void clickLis(View view) {
        switch (view.getId()){
            case R.id.btn_next:
                // 1. 應用內簡單的跳轉(通過URL跳轉在'進階用法'中)
                ARouter.getInstance().build("/test/activity1").navigation();
                break;
            case R.id.button2:

                List<TestObj> objList = new ArrayList<>();
                objList.add(new TestObj("name1", 21));
                objList.add(new TestObj("name2", 22));


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

                // 2. 跳轉並攜帶參數
                ARouter.getInstance().build("/test/activity2")
                        .withLong("key1", 666L)
                        .withString("key2", "888")
                        .withObject("obj",  new TestObj("Gavin", 27))
                        .withObject("list",  objList)
                        .withObject("map",  hashMap)
                        .navigation();
                break;

        }
    }
}

 

Test2Activity

// 爲每一個參數聲明一個字段,並使用 @Autowired 標註
// URL中不能傳遞Parcelable類型數據,通過ARouter api可以傳遞Parcelable對象
@Route(path = "/test/activity2")
public class Test2Activity extends AppCompatActivity {

    TextView textview;

    @Autowired
     long key1;

    @Autowired(name = "key2")
     String name;

    // 支持解析自定義對象,URL中使用json傳遞
    // 如果需要傳遞自定義對象,新建一個類(並非自定義對象類),
    // 然後實現 SerializationService,並使用@Route註解標註(方便用戶自行選擇序列化方式),例如:JsonServiceImpl
    @Autowired
    TestObj obj;

    // 使用 withObject 傳遞 List 和 Map 的實現了
    // Serializable 接口的實現類(ArrayList/HashMap)
    // 的時候,接收該對象的地方不能標註具體的實現類類型
    // 應僅標註爲 List 或 Map,否則會影響序列化中類型
    // 的判斷, 其他類似情況需要同樣處理
    @Autowired
    List<TestObj> list;

    @Autowired
    Map<String, List<TestObj>> map;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ARouter.getInstance().inject(this);
        setContentView(R.layout.activity_test2);

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("基本數據類型參數-->\n");
        stringBuilder.append("key1 = " + key1 + "  ,key2 = " + name + "\n");


        stringBuilder.append("獲取自定義對象參數-->\n");
        stringBuilder.append(JSON.toJSONString(obj) + "\n");


        stringBuilder.append("獲取List參數-->\n");
        stringBuilder.append(JSON.toJSONString(list)+"\n");

        stringBuilder.append("獲取Map參數-->\n");
        stringBuilder.append(JSON.toJSONString(map));


        textview = findViewById(R.id.textview);
        textview.setText(stringBuilder.toString());

    }
}
然後我們成功地發現會報下面SerialiationService.obj的錯誤:

如果需要傳遞自定義對象,新建一個類(並非自定義對象類),然後實現 SerializationService,並使用@Route註解標註,否則會出現以下錯誤。而且,這個類不需要引用的,存在項目中就好。如下:

// 如果需要傳遞自定義對象,新建一個類(並非自定義對象類),然後實現 SerializationService,並使用@Route註解標註(方便用戶自行選擇序列化方式),例如:
@Route(path = "/yourservicegroupname/json1")
public class JsonServiceImpl implements SerializationService {
    @Override
    public <T> T json2Object(String input, Class<T> clazz) {
//        return null;
        return JSON.parseObject(input, clazz);
    }

    @Override
    public String object2Json(Object instance) {
        return JSON.toJSONString(instance);
    }

    @Override
    public <T> T parseObject(String input, Type clazz) {
        return JSON.parseObject(input, clazz);
    }

    @Override
    public void init(Context context) {

    }
}

加上上面的類即可,結果如下:

三、隱式跳轉Activity

MainActivity


public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void clickLis(View view) {
        switch (view.getId()){
            case R.id.btn_next:
                // 1. 應用內簡單的跳轉(通過URL跳轉在'進階用法'中)
                ARouter.getInstance().build("/test/activity1").navigation();
                break;
            case R.id.button2:

                List<TestObj> objList = new ArrayList<>();
                objList.add(new TestObj("name1", 21));
                objList.add(new TestObj("name2", 22));


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

                // 2. 跳轉並攜帶參數
                ARouter.getInstance().build("/test/activity2")
                        .withLong("key1", 666L)
                        .withString("key2", "888")
                        .withObject("obj",  new TestObj("Gavin", 27))
                        .withObject("list",  objList)
                        .withObject("map",  hashMap)
                        .navigation();
                break;
            case R.id.button3:
                //3. 通過URL跳轉
                Uri uri = Uri.parse("arouter://m.aliyun.com/test/activity3");
                startActivity(new Intent("android.intent.action.VIEW",uri));
                break;
           
        }
    }
}

 

Test3Activity, 跟Test1Activity差不多,只是path不一樣

@Route(path = "/test/activity3")
public class Test3Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test3);
    }
}

AndroidManifest文件中Test3Activity的標籤

<activity android:name=".router.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>

四、跳轉頁面攔截

只要在項目中存在即可

// 比較經典的應用就是在跳轉過程中處理登陸事件,這樣就不需要在目標頁重複做登陸檢查
// 攔截器會在跳轉之間執行,多個攔截器會按優先級順序依次執行

@Interceptor(priority = 8,name = "testinterceptor")
public class TestInterceptor implements IInterceptor {
    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {


        LogUtils.i("interceptor.process ... do something work");

        callback.onContinue(postcard);  // 處理完成,交還控制權
        // callback.onInterrupt(new RuntimeException("我覺得有點異常"));      // 覺得有問題,中斷路由流程

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

    @Override
    public void init(Context context) {
// 攔截器的初始化,會在sdk初始化的時候調用該方法,僅會調用一次

        LogUtils.i("TestInterceptor.init");
    }
}

 

五、跳轉回調

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void clickLis(View view) {
        switch (view.getId()){
            ...
            case R.id.button4:
                // 設置跳轉回調,可以獲得跳轉結果
                ARouter.getInstance().build("/test/activity4").navigation(this, new NavCallback() {
                    @Override
                    public void onFound(Postcard postcard) {
                        LogUtils.i("ARouter - 找到了");
                    }

                    @Override
                    public void onLost(Postcard postcard) {
                        LogUtils.i("ARouter - 找不到了");
                    }

                    @Override
                    public void onArrival(Postcard postcard) {
                        LogUtils.i("ARouter - 跳轉完成");
                    }

                    @Override
                    public void onInterrupt(Postcard postcard) {
                        LogUtils.i("ARouter - 被攔截了");
                    }
                });
                break;
           
        }
    }
}

 

六、全局降級策略

使用系統自帶的StartActivity()啓動後就無法插手其中任何環節了,只能交給系統管理,這就導致了在跳轉失敗的情況下無法降級,而是會導致應用崩潰,這個給用戶的感覺就不是很好。ARouter的降級策略就允許我們在自定義降級服務,在跳轉失敗的時候可以自行處理,比如可以加載一個專門提示頁面丟失的Activity去處理這種錯誤情況。

有2種方式,

1、局部的,通過上面的回調方式,複寫lost方法

2、全局的也就是即將要將的

// 實現DegradeService接口,並加上一個Path內容任意的註解即可
@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) {

}
}

七、爲目標頁面聲明更多信息

設置信息

// 我們經常需要在目標頁面中配置一些屬性,比方說"是否需要登陸"之類的
// 可以通過 Route 註解中的 extras 屬性進行擴展,這個屬性是一個 int值,換句話說,單個int有4字節,也就是32位,可以配置32個開關
// 剩下的可以自行發揮,通過字節操作可以標識32個開關,通過開關標記目標頁面的一些屬性,在攔截器中可以拿到這個標記進行業務邏輯判斷
@Route(path = "/test/activity5", extras = Const.MUST_LOGIN)
public class Test5Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test5);

    }
}

 獲取信息

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void clickLis(View view) {
        switch (view.getId()){
            ...
            case R.id.button5:
                ARouter.getInstance().build("/test/activity5").navigation(this, new NavCallback() {
                    @Override
                    public void onArrival(Postcard postcard) {

                    }

                    @Override
                    public void onFound(Postcard postcard) {
                        if (postcard.getExtra() == Const.MUST_LOGIN) {
                            Toast.makeText(MainActivity.this, "需要登陸", Toast.LENGTH_SHORT).show();
                        }
                        super.onFound(postcard);

                    }
                });
                break;
        }
    }
}

八、通過依賴注入解耦:服務管理

有2種方式,自動和手動,詳情看註釋

// 聲明接口,其他組件通過接口來調用服務
public interface HelloService extends IProvider {
    String sayHello(String name);
}

 

// 實現接口
@Route(path = "/yourservicegroupname/hello",name = "測試服務")
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String name) {
        LogUtils.i(name+" sayHello");
        return name+" sayHello";
    }

    @Override
    public void init(Context context) {

        LogUtils.i("HelloServiceImpl init ");
    }
}
public class TestService {
    @Autowired
    HelloService helloService;

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

    HelloService helloService3;

    HelloService helloService4;

    public TestService() {
        ARouter.getInstance().inject(this);

    }

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


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

        helloService3.sayHello("gavin3");
        helloService4.sayHello("gavin4");
    }
}

 

九、預處理服務

先於攔截器,全局的,從日誌可以看出來!

// 實現 PretreatmentService 接口,並加上一個Path內容任意的註解即可
@Route(path = "/test/preService")
public class PretreatmentServiceImpl implements PretreatmentService {
    @Override
    public boolean onPretreatment(Context context, Postcard postcard) {
        // 跳轉前預處理,如果需要自行處理跳轉,該方法返回 false 即可


        LogUtils.i("預處理服務");

        //需要預處理服務return false,就無法打開activity
        //不需要預處理服務return true
        return true;
    }

    @Override
    public void init(Context context) {

    }
}

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章