ARouter系列2:源碼分析

0、相關文章

探索Android路由框架-ARouter之深挖源碼(二)(3.3k閱讀量,65贊)

ARouter原理剖析及手動實現(1.4w閱讀量,70贊,講的詳細)

阿里ARouter使用及源碼解析(一)(三篇文章,講的詳細)

1、測試項目介紹

爲了介紹方便地介紹源碼,所有的activity寫在了一個module中。

1.1、MainActivity

public class MainActivity extends AppCompatActivity {

    public static final String AROUTER_PATH_TEST1 = "/app/Test1Activity";
    public static final String AROUTER_PATH_TEST2 = "/app/Test2Activity";
    public static final String AROUTER_PATH_TEST3 = "/app/Test3Activity";

    private static Activity activity;

    public static Activity getActivity() {
        return activity;
    }

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

        activity = this;

        //不攜帶參數跳轉
        findViewById(R.id.btn1).setOnClickListener(v -> {
            // 1. 應用內簡單的跳轉
            ARouter.getInstance()
                    .build(AROUTER_PATH_TEST1)
                    .navigation();
        });

        // 攜帶參數跳轉
        findViewById(R.id.btn2).setOnClickListener(v -> {
            ARouter.getInstance()
                    .build(AROUTER_PATH_TEST2)
                    .withString("name", "android")
                    .withInt("age", 33)
                    .withParcelable("test", new Person("張三", 66))
                    .navigation();
        });

        // 攔截器測試
        findViewById(R.id.btn3).setOnClickListener(v -> {
            ARouter.getInstance()
                    .build(AROUTER_PATH_TEST3)
                    .navigation(this, new NavCallback() {
                        @Override
                        public void onArrival(Postcard postcard) {

                        }

                        @Override
                        public void onInterrupt(Postcard postcard) {
                            LogUtils.e("被攔截了");
                        }
                    });
        });
    }
}

1.2、Test1Activity

@Route(path = MainActivity.AROUTER_PATH_TEST1)
public class Test1Activity extends AppCompatActivity {

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

1.3、Test2Activity

@Route(path = MainActivity.AROUTER_PATH_TEST2)
public class Test2Activity extends AppCompatActivity {

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

    @Autowired(name = "age")
    int age;

    @Autowired(name = "test")
    Person person;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ARouter.getInstance().inject(this);

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test2);

        TextView tvShow = findViewById(R.id.tvShow);
        if (person != null){
            tvShow.setText(name + ", " + age + "," + person.toString());
        }

    }
}

1.4、TestInterceptorActivity

@Route(path = MainActivity.AROUTER_PATH_TEST3)
public class TestInterceptorActivity extends AppCompatActivity {

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

1.5、Test1Interceptor

@Interceptor(priority = 7)
public class Test1Interceptor implements IInterceptor {

    Context mContext;

    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
        if (MainActivity.AROUTER_PATH_TEST3.equals(postcard.getPath())){
            // 這裏的彈窗僅做舉例,代碼寫法不具有可參考價值
            final AlertDialog.Builder ab = new AlertDialog.Builder(MainActivity.getActivity());
            ab.setCancelable(false);
            ab.setTitle("溫馨提醒");
            ab.setMessage("想要跳轉到TestInterceptorActivity麼?(觸發了\"/inter/test1\"攔截器,攔截了本次跳轉)");
            ab.setNegativeButton("繼續", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    callback.onContinue(postcard);
                }
            });
            ab.setNeutralButton("算了", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    callback.onInterrupt(null);
                }
            });
            ab.setPositiveButton("加點料", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    postcard.withString("extra", "我是在攔截器中附加的參數");
                    callback.onContinue(postcard);
                }
            });

            MainLooper.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    ab.create().show();
                }
            });
        }else {
            callback.onContinue(postcard);
        }
    }

    @Override
    public void init(Context context) {
        mContext = context;
        LogUtils.e(Test1Interceptor.class.getName()+" has init.");
    }
}

1.6、其他

public class Person implements Parcelable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    protected Person(Parcel in) {
        name = in.readString();
        age = in.readInt();
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in);
        }

        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
    }
}
public class MainLooper extends Handler {
    private static MainLooper instance = new MainLooper(Looper.getMainLooper());

    protected MainLooper(Looper looper) {
        super(looper);
    }

    public static MainLooper getInstance() {
        return instance;
    }

    public static void runOnUiThread(Runnable runnable) {
        if(Looper.getMainLooper().equals(Looper.myLooper())) {
            runnable.run();
        } else {
            instance.post(runnable);
        }

    }
}

2、源碼分析

ARouter是通過APT生成代碼在框架內部進行操作,那麼,項目編譯生成的文件位置在那裏?

既然生成了這些源碼,我們就先隨便點點看看這些都是啥?

 

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$app implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/app/Test1Activity", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/app/test1activity", "app", null, -1, -2147483648));
    atlas.put("/app/Test2Activity", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/app/test2activity", "app", new java.util.HashMap<String, Integer>(){{put("test", 10); put("name", 8); put("age", 3); }}, -1, -2147483648));
    atlas.put("/app/Test3Activity", RouteMeta.build(RouteType.ACTIVITY, TestInterceptorActivity.class, "/app/test3activity", "app", null, -1, -2147483648));
  }
}

 

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("app", ARouter$$Group$$app.class);
  }
}

具體源碼大家可以自行查看,不過多粘貼了。

這裏簡簡單單隨便截圖了APT生成的部分源碼,是不是感覺跟上一篇文章使用到的代碼很多相似性吶~比如攔截器的優先級是1、跳轉匹配的路徑也是一樣的、跳轉傳遞的參數、定義的組名等等。既然這麼多一樣的那肯定是在內部某部分進行封裝使用,帶着這個問題我們開始逐步分析。

首先,我們從該框架使用到的註解開始分析(因爲註解是使用這個框架的起點)

2.1、註解分析

首先,我們知道要使用ARouter的首先需要在類的註釋上面寫上 @Route 這個註解,點進源碼看看

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {

    String path();
    String group() default "";
    String name() default "";  
    int extras() default Integer.MIN_VALUE;
    int priority() default -1;

}

使用該註解標註的類將被自動添加至路由表中。而且,ARouter 並非僅提供頁面(Activity)的路由功能,還可以用來路由模塊想要暴露給其他模塊調用的接口。也就是說 @Route 不僅可用於 Activity 類,還可用於模塊對外接口的實現類。那麼具體它可以實現那些類型?

從上面的源碼可以看到,除了攔截器,裏面通過實現接口重寫方法,方法裏面都有一個RouteMeta,那麼我們點進去RouteMeta這個類看看:

/**
 * It contains basic route information.
 *
 */
public class RouteMeta {
    private RouteType type;         // Type of route
    private Element rawType;        // Raw type of route
    private Class<?> destination;   // Destination
    private String path;            // Path of route
    private String group;           // Group of route
    private int priority = -1;      // The smaller the number, the higher the priority
    private int extra;              // Extra data
    private Map<String, Integer> paramsType;  // Param type
    private String name;

    private Map<String, Autowired> injectConfig;  // Cache inject config.

    public RouteMeta() {
    }
}

註釋的意思是:它包含基本路由信息。

RouteType,就是路由的類型。那麼,這款路由框架的具體路由類型又有那些?帶着這個疑問,點進RouteType 源碼看看。

public enum RouteType {
    ACTIVITY(0, "android.app.Activity"),
    SERVICE(1, "android.app.Service"),
    PROVIDER(2, "com.alibaba.android.arouter.facade.template.IProvider"),
    CONTENT_PROVIDER(-1, "android.app.ContentProvider"),
    BOARDCAST(-1, ""),
    METHOD(-1, ""),
    FRAGMENT(-1, "android.app.Fragment"),
    UNKNOWN(-1, "Unknown route type");
}

首先這是一個枚舉,這些就是框架可以具體使用到的路由類型

說完Route 這個註解,我們在來看看@Interceptor 攔截器註解,

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Interceptor {
    /**
     * The priority of interceptor, ARouter will be excute them follow the priority.
     */
    int priority();

    /**
     * The name of interceptor, may be used to generate javadoc.
     */
    String name() default "Default";
}

哦,我們發現攔截器的註解就2個方法,第一個是定義優先級的,第二個就是攔截器的名字。

接着,@Autowired 註解:

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface Autowired {

    // Mark param's name or service name.
    String name() default "";

    // If required, app will be crash when value is null.
    // Primitive type wont be check!
    boolean required() default false;

    // Description of the field
    String desc() default "";
}

我們知道這個註解是界面跳轉時參數傳遞用的。Activity 中使用該註解標誌的變量,會在頁面被路由打開的時候自動賦予傳遞的參數值。

2.2、初始化分析

我們知道,ARouter框架使用的第一個步驟,是要先初始化,也就是調用:ARouter.init( Application.this );這個API,那麼,它的初始化究竟是做了那些東西?我們先點進源碼看看:

/**
     * Init, it must be call before used router.
     */
    public static void init(Application application) {
        if (!hasInit) {
            logger = _ARouter.logger;
            _ARouter.logger.info(Consts.TAG, "ARouter init start.");
            hasInit = _ARouter.init(application);

            if (hasInit) {
                _ARouter.afterInit();
            }

            _ARouter.logger.info(Consts.TAG, "ARouter init over.");
        }
    }

 

那麼,實際上它調用的是綠色矩形內的代碼,綠色矩形內有兩個初始化,一個是 _ARouter.init(application),還有一個是 _ARouter.afterInit( )

我們首先點進 _ARouter . init()看看,

A:紅色箭頭的是類註釋,翻譯過來就是:ARouter核心( 外觀模式 )

B:綠色箭頭代表的是一個線程池,對線程池概念不是很清楚的朋友可以點進 必須要理清的Java線程池 先了解一下

C:藍色箭頭代表的是 這個_ARouter init()實際是調用的LogisticsCenter裏面的init方法。

首先,什麼是外觀模式?

簡單點理解就是,通過創建一個統一的類,用來包裝子系統中一個或多個複雜的類,客戶端可以通過調用外觀類的方法來調用內部子系統中所有方法。大概意思就是這樣,想深入理解的話可以自行查閱資料。

其次,這個線程池做了什麼功能?

點進去DefaultPoolExecutor這個類看看:

由源碼得知就是創建了一個數組阻塞隊列的線程池

最後,我們點進LogisticsCenter,看看裏面的init方法執行了什麼操作(因爲源碼太長,我就分別截圖)

圖一:

圖二:

通過LogisticsCenter - init(1)這幅源碼圖可以得知:如果是Debug模式,則執行從if代碼塊; 如果是Release模式:通過PackageUtils.isNewVersion(context)判斷當前應用是否是第一次啓動。接着,獲取arouter-compiler生成的文件,然後將該文件,存儲在sp中,下次啓動應用的時候,直接從sp緩存中讀取。

然後,在LogisticsCenter - init(2)紅色矩形的代碼塊可以得知:首先遍歷arouter-compiler生成的文件,將他們按照類型分別存儲到Warehouse的對應字段中。也就是,如果類名前綴符合文件拼接規則,比如爲com.alibaba.android.arouter.routes.ARouter$$Root的文件,就將其添加到具體的Warehouse裏面的集合中。也就是對應的這裏

 

那麼,這個文件拼接規則(也就是字符串拼接)是?

Warehouse又是什麼?點進源碼看看

其中,Warehouse的類註釋寫的非常好,翻譯過來就是:路由元數據和其他數據的存儲。這個類本質就是路由文件映射表。裏面提供了各種HashMap集合(Map不允許重複的key),去存儲SP存儲的值。

我們再看看這裏存儲攔截器的集合,UniqueKeyTreeMap,這個類是存儲攔截器的,我們看看它做了什麼?

原來,這個UniqueKeyTreeMap,就是繼承了TreeMap,哦,我們知道TreeMap是一種有序的集合(底層幫我們排序)所以數值越小,它就優先添加攔截器。這樣也就解釋了爲什麼攔截器屬性值設置的越低,優先級越高。

綜上,針對 _ARouter init( ) 這個初始化的源碼我們可以得知以下:

A:初始化這一操作,表面上是_ARouter ,實則是LogisticsCenter 在幫我們管理邏輯

B:LogisticsCenter 內部通過先存儲SP,然後遍歷、匹配(滿足條件則添加到具體的集合中,按照文件的前綴不同,將他們添加到路由映射表Warehouse的groupsIndex、interceptorsIndex、providersIndex 中)

C:具體的路由清單是Warehouse ,不僅保存實例,還給我們提供了緩存。也就是說 同一個目標(RouteMeta、IProvider、IInterceptor)僅會在第一次使用的時候創建一次,然後緩存起來。後面都是直接使用的緩存。

初始化還有一個API,_ARouter.afterInit( ) ; 這個API在上面的截圖也有,那麼這個API是用來幹什麼的?我們點進去看看。

首先,它實例化了一個InterceptorService。這裏的InterceptorService下面會說。 這裏的build方法,最終返回的是一個Postcard對象:

從源碼可以得知,實際上它返回的卻是,_ARouter.getInstance().build(path)這個方法,這個方法是一個方法重載(一般用的最多的就是這一個,也就是默認分組,不進行自定義分組),跟進去看看(源碼很長,分爲以下兩個截圖):

發現這裏有一個PathReplaceService(也就是紅色矩形),這個PathReplaceService又是什麼,點進去看看

這個類註釋翻譯過來就是:預處理路徑。這個接口是IProvider的子類

讓我們在看回綠色箭頭,其中,navigation(clazz)這種方式是屬於根據類型查找,而build(path)是根據名稱進行查找。如果應用中沒有實現PathReplaceService這個接口,則pService=null。PathReplaceService可以對所有的路徑進行預處理,然後返回一個新的值(返回一個新的String和Uri)。綠色箭頭有一個extractGroup()方法,點進去看看:

extractGroup(path)這個方法,核心邏輯是紅色矩形內的代碼,這個方法主要是獲取分組名稱。切割path字符串,默認爲path中第一部分爲組名。這就證明了如果我們不自定義分組,默認就是第一個分號的內容。

分析完了build,我們在看看navigation

繼續點進源碼看看,發現進入了_ARouter這個類裏面的navigation方法:

navigation1

其中,navigation - 1這幅圖中的紅色矩形代表的意思是,如果(兩個)路徑沒寫對,ARouter會Toast提示客戶端,路徑不對。

navigation - 2這幅圖中的紅色矩形代表的是忽略攔截器。interceptorService實例化對象的時機,是在_ARouter這類中的afterInit( )進行實例化的。這幅圖中的藍色矩形和箭頭的方法真實邏輯是調用了下圖的方法:

通過這段代碼我們可以得知:

1:如果navigation()不傳入Activity作爲context,則使用Application作爲context

2:內部使用了Intent來進行傳遞

3:如果在跳轉時,設置了flags,且沒有設置Activity作爲context,則下面的startActivity()方法會發生錯誤,因爲缺少Activity的Task棧;

4:Fragment的判斷根據版本不同進行了相應的判斷

看到了這裏,我們在看回navigation - 2這幅圖。如果(兩個)路徑匹配的話,會執行到截圖中的藍色矩形,點進藍色矩形裏面去看看(也就是 LogisticsCenter.completion( Postcard )):

首先,根據path在Warehouse.routes映射表中查找對應的RouteMeta。但是,第一次加載的時候,是沒有數據的。(而第一次加載是在LogisticsCenter.init()中,上面也說了。因此只有`Warehouse`的`groupsIndex、interceptorsIndex、providersIndex` 有數據),因此這個時候routeMeta=null。所以,這個時候會先從Warehouse.groupsIndex中取出類名前綴爲com.alibaba.android.arouter.routes.ARouter$$Group$$group的文件;接着,將我們添加@Route註解的類映射到Warehouse.routes中;然後將已經加載過的組從Warehouse.groupsIndex中移除,這樣也避免了重複添加進Warehouse.routes;注意,這個時候Warehouse.routes已經有值了,所以重新調用本方法(completion(postcard))執行了else代碼塊。

然後,LogisticsCenter.completion - 2 圖中就是具體的設置屬性值;然後在 LogisticsCenter.completion - 3 圖中,對 IProvider的子類進行初始化 provider.init(mContext); 初始化完畢以後,將IProvider的子類添加進Warehouse.providers中;最後將IProvider的實現類保存在postcard中,因此可以從postcard獲取IProvider的實例對象,其中,greenChannel()會忽略攔截器這個前面也說了。

小結:

在LogisticsCenter.completion這個方法裏面完成了對Warehouse.providers、Warehouse.routes的賦值。那麼Warehouse.interceptors又是在哪裏賦值的呢?

IProvider有個子類接口,InterceptorService(在文章的上面,也簡單提到過這個接口)

因此InterceptorServiceImpl也是IProvider的子類;在LogisticsCenter.completion()中,有provider.init(context)的初始化 ;那麼,我們看看InterceptorServiceImpl這個攔截器的實現類,

在InterceptorServiceImpl這個類裏面的紅色矩形中,完成了具體攔截器的初始化以及將攔截器添加到Warehouse.interceptors映射表中。

寫到這裏,ARouter框架的源碼基本上就分析完畢了。可能你說,這不是隻是初始化嘛,是的這只是初始化,但是界面跳轉的源碼都涵蓋在上面了,你可能不信,我們點擊跳轉的API去看看:

點擊build,又跳進熟悉的build裏面來了:

同樣,點擊navigation,也回到了上面的navigation中去了。

源碼基本上就寫到這裏,內容有點繞。個人建議自己對着源碼好好過一遍,這樣理解的效果可能會比較好。

 

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