天天看點

Android 路由架構ARouter最佳實踐

轉載請标明出處:http://blog.csdn.net/zhaoyanjun6/article/details/76165252

本文出自【趙彥軍的部落格】

一:什麼是路由?

說簡單點就是映射頁面跳轉關系的,當然它也包含跳轉相關的一切功能。

二:為什麼需要路由

Android系統已經給我們提供了api來做頁面跳轉,比如startActivity,為什麼還需要路由架構呢?我們來簡單分析下路由架構存在的意義:

  • 在一些複雜的業務場景下(比如電商),靈活性比較強,很多功能都是營運人員動态配置的,比如下發一個活動頁面,我們事先并不知道具體的目标頁面,但如果事先做了約定,提前做好頁面映射,便可以自由配置。
  • 随着業務量的增長,用戶端必然随之膨脹,開發人員的工作量越來越大,比如64K問題,比如協作開發問題。App一般都會走向元件化、插件化的道路,而元件化、插件化的前提就是解耦,那麼我們首先要做的就是解耦頁面之間的依賴關系。
  • 簡化代碼。數行跳轉代碼精簡成一行代碼。
  • 其他...

三:ARouter 簡介

是ARouter是阿裡巴巴開源的Android平台中對頁面、服務提供路由功能的中間件,提倡的是簡單且夠用。

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

四:ARouter 優勢

從 ARouter Github 了解到它的優勢:

  • 支援直接解析标準URL進行跳轉,并自動注入參數到目标頁面中
  • 支援多子產品工程使用
  • 支援添加多個攔截器,自定義攔截順序
  • 支援依賴注入,可單獨作為依賴注入架構使用
  • 支援InstantRun
  • 支援MultiDex(Google方案)
  • 映射關系按組分類、多級管理,按需初始化
  • 支援使用者指定全局降級與局部降級政策
  • 頁面、攔截器、服務等元件均自動注冊到架構
  • 支援多種方式配置轉場動畫
  • 支援擷取Fragment
  • 完全支援Kotlin以及混編

典型的應用:

  • 從外部URL映射到内部頁面,以及參數傳遞與解析
  • 跨子產品頁面跳轉,子產品間解耦
  • 攔截跳轉過程,處理登陸、埋點等邏輯
  • 跨子產品API調用,通過控制反轉來做元件解耦

五:ARouter 配置

android {
    defaultConfig {
	...
	javaCompileOptions {
	    annotationProcessorOptions {
		arguments = [ moduleName : project.getName() ]
	    }
	}
    }
}

dependencies {
    compile 'com.alibaba:arouter-api:1.2.1.1'
    annotationProcessor 'com.alibaba:arouter-compiler:1.1.2.1'
}


           

api 的版本和 compiler 的版本号需要用最新的。最新的版本在 Github上可以找到。

六:ARouter 初始化

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        ARouter.openLog();     // 列印日志
        ARouter.openDebug();   // 開啟調試模式(如果在InstantRun模式下運作,必須開啟調試模式!線上版本需要關閉,否則有安全風險)
        ARouter.init( this ); // 盡可能早,推薦在Application中初始化
    }
}
           

七:ARouter 注解發起路由

建立一個 Activity1 作為測試 ,在 Activity1 添加注解的代碼如下:

package com.router;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.alibaba.android.arouter.facade.annotation.Route;
// 在支援路由的頁面上添加注解(必選)
// 這裡的路徑需要注意的是至少需要有兩級,/xx/xx

@Route(path = "/com/Activity1")
public class Activity1 extends AppCompatActivity {

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

           

在 MainActivity 的布局中添加一個按鈕

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.router.MainActivity">

    <TextView
        android:id="@+id/bt1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="應用内跳轉"
        android:gravity="center"
        android:padding="10dp"
        android:background="#666666"
        />

</LinearLayout>

           

MainActivity 裡面的跳轉邏輯是:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

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

        findViewById( R.id.bt1).setOnClickListener( this );

    }

    @Override
    public void onClick(View v) {
        switch ( v.getId() ){
            case R.id.bt1 :
                //發起路由跳轉
                ARouter.getInstance().build("/com/Activity1").navigation();
                break;
        }
    }
}
           

效果圖如下:

如果你想實作像 startActivityForResult() 功能,可以這樣使用:

navigation(Activity mContext, int requestCode)

           

具體使用如下:

ARouter.getInstance()
       .build("/com/Activity1")
       .navigation( this , 100 );
           

如果 Path 路徑不正确會發生什麼,我們來測試一下,現在我們把路由操作改為

ARouter.getInstance().build("/com/Test").navigation();
           

效果如下:

可以看到有一個 Toast 提示我們找不到 "/com/Test" 路徑,我們來看看源碼

通過源碼我們可以看到在 debug 狀态下,當找不到路由路徑目标是,會有 Toast 提示。

八:監聽路由過程

在路由跳轉的過程中,我們可以監聽路由的過程,隻需要使用:

navigation(Context context, NavigationCallback callback)
           

NavigationCallback 的源碼如下:

public interface NavigationCallback {

    /**
     * Callback when find the destination.
     * 找到了
     * @param postcard meta
     */
    void onFound(Postcard postcard);

    /**
     * Callback after lose your way.
     * 找不到了
     * @param postcard meta
     */
    void onLost(Postcard postcard);

    /**
     * Callback after navigation.
     * 跳轉完了
     * @param postcard meta
     */
    void onArrival(Postcard postcard);

    /**
     * Callback on interrupt.
     * 被攔截了
     * @param postcard meta
     */
    void onInterrupt(Postcard postcard);
}


           

具體使用如下

ARouter.getInstance()
       .build("/com/Activity1")
       .navigation(this, new NavCallback() {

             @Override
              public void onFound(Postcard postcard) {
                   Log.e("zhao", "onArrival: 找到了 ");
              }

             @Override
              public void onLost(Postcard postcard) {
                   Log.e("zhao", "onArrival: 找不到了 ");
              }

             @Override
              public void onArrival(Postcard postcard) {
                   Log.e("zhao", "onArrival: 跳轉完了 ");
              }

             @Override
              public void onInterrupt(Postcard postcard) {
                    Log.e("zhao", "onArrival: 被攔截了 ");
              }
              });                
           

九:發起路由并且傳遞參數

ARouter.getInstance()
       .build("/com/Activity1")
       .withString( "key" , "123")  //參數:鍵:key 值:123
       .navigation();
           

ARouter 提供了豐富大量的參數類型,供我們選擇。

//基礎類型

.withString( String key, String value )
.withBoolean( String key, boolean value)
.withChar( String key, char value )
.withShort( String key, short value)
.withInt( String key, int value)
.withLong( String key, long value)
.withDouble( String key, double value)
.withByte( String key, byte value)
.withFloat( String key, float value)
.withCharSequence( String key,  CharSequence value)

//數組類型

.withParcelableArrayList( String key, ArrayList<? extends Parcelable > value)
.withStringArrayList( String key,  ArrayList<String> value)
.withIntegerArrayList( String key, ArrayList<Integer> value)
.withSparseParcelableArray( String key, SparseArray<? extends Parcelable> value)
.withCharSequenceArrayList( String key, ArrayList<CharSequence> value)
.withShortArray( String key,  short[] value)
.withCharArray( String key, char[] value)
.withFloatArray( String key, float[] value)
.withCharSequenceArray( String key,  CharSequence[] value)

//Bundle 類型

.with( Bundle bundle )
         
//Activity 跳轉動畫

.withTransition(int enterAnim, int exitAnim)
                
//其他類型
       
.withParcelable( String key, Parcelable value)
.withParcelableArray( String key,  Parcelable[] value)
.withSerializable( String key, Serializable value)
.withByteArray( String key, byte[] value)
.withTransition(int enterAnim, int exitAnim)

           

十:路由分組

在前面我們講到在對 Activity1 做注解的時候,用到了

@Route(path = "/com/Activity1")
public class Activity1 extends AppCompatActivity {

}
           

在 path 這個字元串裡面,"com" 就代表組的辨別;“Activity1" 代表是 Activity1 類的具體表示。組的辨別和類的辨別都可以自己定義的,需要記住的是組辨別和類辨別之間用斜杠來區分 '''' .

什麼是組?

這裡就需要提下,ARouter架構是分組管理,按需加載。提起來很高深的樣子呢!其實解釋起來就是,在編譯期架構掃描了所有的注冊頁面/服務/字段/攔截器等,那麼很明顯運作期不可能一股腦全部加載進來,這樣就太不和諧了。是以就分組來管理,ARouter在初始化的時候隻會一次性地加載所有的root結點,而不會加載任何一個Group結點,這樣就會極大地降低初始化時加載結點的數量。比如某些Activity分成一組,組名就叫test,然後在第一次需要加載組内的某個頁面時再将test這個組加載進來。

測試一下:

ARouter.getInstance()
       .build("/com/Activity1")
       .navigation(this, new NavCallback() {
            @Override
            public void onArrival(Postcard postcard) {
                String group = postcard.getGroup();
                Log.e("zhao", "分組是: " + group);
            }
        });

           

結果是

07-27 17:32:17.880 19449-19449/com.router E/zhao: 分組是: com
           

ARouter 預設情況下的分組就是第一個 / / 之間的内容。

自定義分組:

建立 CustomGroupActivity 并且添加 注解,并且指定路由分組。自定義分組的就是在原來的注解上添加 group 字段, 如下所示。

@Route(path = "/com/CustomGroupActivity" , group = "customGroup")
public class CustomGroupActivity extends AppCompatActivity {

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

           

自定義分組,發起路由:第二個參數就是路由的分組

build(String path, String group)
           

具體實作如下所示:

ARouter.getInstance().build("/com/CustomGroupActivity", "customGroup").navigation();

           

十一:Fragment 路由

建立 Fragment 類,并且添加路由注解

@Route(path = "/com/TestFragment")
public class TestFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_test, container, false);
        return view ;
    }
}

           

擷取 Fragment 執行個體

Fragment fragment = (Fragment) ARouter.getInstance().build( "/com/TestFragment" ).navigation();
           

十二:URL 跳轉

web url 跳轉流程圖

建立URL 中間跳轉頁

建立 URLReceiveActivity

/**
 * URL 中轉Activity
 */
public class URLReceiveActivity extends AppCompatActivity {

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

        setContentView( R.layout.activity_url_receive );

        //對URI 資料分發
        Uri uri = getIntent().getData();
        ARouter.getInstance().build(uri).navigation(this, new NavCallback() {
            @Override
            public void onArrival(Postcard postcard) {
                finish();
            }
        });
    }
}

           

URLReceiveActivity 添加注冊

<activity android:name=".URLReceiveActivity">
      <!-- Schame -->
      <intent-filter>
            <data
                android:host="zhaoyanjun"
                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>

           

這裡面的 host 、scheme 字段很重要。點選 url 會根據這兩個字段會調起本地的 Activity 。

下面是一段 HTML 片段

<h2>1:URL普通跳轉</h2>

<p><a href="arouter://zhaoyanjun/com/URLActivity1">arouter://zhaoyanjun/com/URLActivity1 </a>
</p>

<h2>2:URL普通跳轉攜帶參數</h2>

<p>
<a href="arouter://zhaoyanjun/com/URLActivity2?name=alex&age=18&boy=true&high=180&obj=%7b%22name%22%3a%22jack%22%2c%22id%22%3a666%7d">arouter://zhaoyanjun/test/URLActivity2?name=alex&age=18&boy=true&high=180&obj={"name":"jack","id":"666"}
</a>
</p>


           

注意 a 标簽裡面的 arouter://zhaoyanjun 分别代表着 scheme 、host ;/com/URLActivity1 就是目标 Activity 的注解。

如果需要接收 URL 中的參數,需要在 Activity 調用自動注入初始化方法;

ARouter.getInstance().inject(this);
           

需要注意的是,如果不使用自動注入,那麼可以不寫 ARouter.getInstance().inject(this),但是需要取值的字段仍然需要标上 @Autowired 注解,因為 隻有标上注解之後,ARouter才能知道以哪一種資料類型提取URL中的參數并放入Intent中,這樣您才能在intent中擷取到對應的參數

具體的代碼如下:

@Route(path = "/com/URLActivity2")
public class URLActivity2 extends AppCompatActivity{

    private TextView textView;

    @Autowired
    String name;

    @Autowired
    int age;

    @Autowired
    boolean boy;

    @Autowired
    int high;

    @Autowired
    String obj ;

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

        textView = (TextView) findViewById(R.id.tv);

        //解析參數
        Bundle bundle = getIntent().getExtras();
        String name1 = bundle.getString("name");

        textView.setText("參數是: " + "name: " + name + "  age: " + age
                + " boy: " + boy + " name1: " + name1 + " obj: " + obj.toString() );
    }
}

           

十三:暴露服務

這裡說到的服務不是Android四大元件中的Service,這裡的服務是接口開發的概念,就是将一部分功能群組件封裝起來成為接口,以接口的形式對外提供能力,是以在這部分就可以将每個功能作為一個服務,而服務的實作就是具體的業務功能。

我們先自定義一個接口 IService 并且繼承 IProvider 。IService 接口裡面有一個 sayHello() 方法,具體代碼如下。

public interface IService extends IProvider {
    void sayHello(Context context );
}
           

先定義一個 IService 的實作類 MyService 并且添加注解,代碼如下

@Route(path = "/service/hello", name = "測試服務")
public class MyService implements IService {

    @Override
    public void sayHello( Context context ) {
        Toast.makeText(  context , "hello", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void init(Context context) {

    }
}


           

發現服務,首先定義服務對象,并且添加注解,我們不需要知道接口的具體實作類。

@Autowired(name = "/service/hello")
IService service;

           

然後添加注解初始化,自動指派。

ARouter.getInstance().inject(this);
           

最後我們調用 service 裡面的 sayHello() 方法。

service.sayHello(this);
           

發現服務這個功能的特點在于,我們隻需要知道接口,不需要關心接口的實作類,很好了實作了解耦。

十四:其他

  • 關閉路由,這個操作慎用
ARouter.getInstance().destroy();
           

十五:混淆說明

-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}
           

總結

所有的代碼都上傳至Github: https://github.com/zyj1609wz/Router

參考資料

ARouter解析一:基本使用及頁面注冊源碼解析

ARouter解析二:頁面跳轉源碼分析

個人微信号:

zhaoyanjun125

, 歡迎關注

繼續閱讀