1. 程式人生 > >阿里巴巴開源路由框架——ARouter原理分析

阿里巴巴開源路由框架——ARouter原理分析

背景

當專案的業務越來越複雜,業務線越來越多的時候,就需要按照業務線去分不同的模組去開發,這樣專門的人負責專門的業務模組,最終上線由殼工程去負責進行組合打包各個module,完成業務的快速迭代。整個過程會涉及到各個模組間進行通訊,比如訂單模組和個人中心模組,可能會需要頻繁的傳遞資料和頁面跳轉,這個時候怎麼去處理呢?我們能想到的方案就是採用類名反射,來動態建立需要跳轉和互動的類,這樣編譯時就不會報錯,執行時又可以完成模組間的互動。阿里巴巴推出的開源路由框架——ARouter就是基於反射和註解來解決這個問題的,本文不講基本使用(基本使用在專案的github主頁上已經將的非常詳細了),通過分析整個路由過程來講解它的基本原理。

說在前面

首先在我們需要用到的類的類名加上註解@Route(“/group/name”),注意這裡需要至少兩層路徑(第一個是分組,第二個一般是類名)。這個註解就是代表這個類可以被其他模組找到的一個路徑的註解,並且它是一個編譯時註解,這就意味著在編譯時就已經生成了相應的輔助類。ARouter把路由一共分為以下幾類:

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”);

其中我們常用的就是ACTIVITY,PROVIDER,FRAGMENT這三個了,也基本上滿足了我們模組化開發的需求。另外一點就是分組的概念,ARouter是按照組來進行整理的,也就是第一層的路徑,所以前面說必須要兩層路徑,否則不知道歸到哪裡去,一般一個module按照模組名採用統一的分組標識。我們來看看註解生成的類(這裡只包含了Activity,Fragment,Provider):

package com.alibaba.android.arouter.routes;
//。。。import省略
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$Personal implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/Personal/EARNING", RouteMeta.build(RouteType.ACTIVITY, PerEarningActivity.class, "/personal/earning", "personal", null, -1, -2147483648)); //...省略Activity,Fragment atlas.put("/Personal/main", RouteMeta.build(RouteType.FRAGMENT, PerMainFragment.class, "/personal/main", "personal", null, -1, -2147483648)); atlas.put("/Personal/service", RouteMeta.build(RouteType.PROVIDER, PerServiceImpl.class, "/personal/service", "personal", null, -1, -2147483648)); } }

以上就是所有註解的路徑的資訊集合,包含了所有的Activity,Fragment,Provider(一般一個module一個Provider就夠用了,專門用來跟其他模組互動),並都以路徑為key放到這個map中。

package com.alibaba.android.arouter.routes;
//。。。import省略
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Providers$$modlue_personal implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    providers.put("com.tb.test.service.ModulePersonalService", RouteMeta.build(RouteType.PROVIDER, PerServiceImpl.class, "/Personal/service", "personal", null, -1, -2147483648));
  }
}

這個類是專門的Provider的索引的集合,所有的provider都被以全類名為索引放到一個map中。

package com.alibaba.android.arouter.routes;
//。。。import省略

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

這個類是所有的group的資訊收集,全部都以group的名字為key,以註解生成的不同的group的類的class物件為value放入到一個map中。

總共就生成這三種類型的類,當然,如果你有不同的分組還會生成其他的類,不過都是這三種裡面的一種。

完成了這些註解資訊的收集,下面就會去使用這些資訊來完成我們的跨模組互動了。

初始化過程

使用ARouter必須先要進行初始化:

if (isDebug()) {
// These two lines must be written before init, otherwise these configurations will be invalid in the init process
ARouter.openLog(); // Print log
ARouter.openDebug(); // Turn on debugging mode (If you are running in InstantRun mode, you must turn on debug mode! Online version needs to be closed, otherwise there is a security risk)
}
ARouter.init(mApplication); // As early as possible, it is recommended to initialize in the Application

上面這段話就是去初始化Arouter,我們來看看init裡面到底做了什麼事。。。

/**
     * 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這個類裡面,跟進去:

protected static synchronized boolean init(Application application) {
        mContext = application;
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;

        // It's not a good idea.
        // if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
        //     application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
        // }
        return true;
    }

程式碼也很簡單,核心就是LogisticsCenter.init這句話,跟進去看看,核心程式碼如下:

List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

            //
            for (String className : classFileNames) {
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    // This one of root elements, load root.
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    // Load interceptorMeta
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    // Load providerIndex
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }

我們可以看到,首先是去獲取到所有的app裡的由ARouter註解生成的類的類名,他們的統一特點就是在同一個包下,包名為:com.alibaba.android.arouter.routes
然後就是迴圈遍歷這些類,也就是剛才我們說的那三種類。在這裡,有一個Warehouse類,看下程式碼:

/**
 * Storage of route meta and other data.
 *
 * @author zhilong <a href="mailto:[email protected]">Contact me.</a>
 * @version 1.0
 * @since 2017/2/23 下午1:39
 */
class Warehouse {
    // Cache route and metas
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    static Map<String, RouteMeta> routes = new HashMap<>();

    // Cache provider
    static Map<Class, IProvider> providers = new HashMap<>();
    static Map<String, RouteMeta> providersIndex = new HashMap<>();

    // Cache interceptor
    static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
    static List<IInterceptor> interceptors = new ArrayList<>();

    static void clear() {
        routes.clear();
        groupsIndex.clear();
        providers.clear();
        providersIndex.clear();
        interceptors.clear();
        interceptorsIndex.clear();
    }
}

很簡單,定義了幾個靜態map,在初始化的時候來存放之前的註解生成的那些相關資訊。初始化裡面存的就是所有group索引的map,所有攔截器(本文不講)索引的map,所有provider索引的map。至此,之前的那些註解類裡面的資訊都被儲存起來了,這樣後續在查詢的時候就很方便可以找到對應的類,我們繼續看初始化之後的afterInit方法:

static void afterInit() {
        // Trigger interceptor init, use byName.
        interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
    }

我們跟蹤之後,發現最終會呼叫LogisticsCenter中的completion方法:

/**
     * Completion the postcard by route metas
     *
     * @param postcard Incomplete postcard, should completion by this method.
     */
    public synchronized static void completion(Postcard postcard) {
        if (null == postcard) {
            throw new NoRouteFoundException(TAG + "No postcard!");
        }

        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) {    // Maybe its does't exist, or didn't load.
            Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
            if (null == groupMeta) {
                throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
            } else {
                // Load route and cache it into memory, then delete from metas.
                try {
                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }

                    IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                    iGroupInstance.loadInto(Warehouse.routes);
                    Warehouse.groupsIndex.remove(postcard.getGroup());

                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                } catch (Exception e) {
                    throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                }

                completion(postcard);   // Reload
            }
        } else {
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());

            Uri rawUri = postcard.getUri();
            if (null != rawUri) {   // Try to set params into bundle.
                Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
                Map<String, Integer> paramsType = routeMeta.getParamsType();

                if (MapUtils.isNotEmpty(paramsType)) {
                    // Set value by its type, just for params which annotation by @Param
                    for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                        setValue(postcard,
                                params.getValue(),
                                params.getKey(),
                                resultMap.get(params.getKey()));
                    }

                    // Save params name which need autoinject.
                    postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
                }

                // Save raw uri
                postcard.withString(ARouter.RAW_URI, rawUri.toString());
            }

            switch (routeMeta.getType()) {
                case PROVIDER:  // if the route is provider, should find its instance
                    // Its provider, so it must be implememt IProvider
                    Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                    IProvider instance = Warehouse.providers.get(providerMeta);
                    if (null == instance) { // There's no instance of this provider
                        IProvider provider;
                        try {
                            provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                        } catch (Exception e) {
                            throw new HandlerException("Init provider failed! " + e.getMessage());
                        }
                    }
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
            }
        }
    }

這個方法有點長,不過我們可以看到,核心功能就是postcard的資訊完善。postcard就是整個路由過程中的信使,類似於生活中的明信片功能,包含了路由所有需要的資訊。通過第34行的遞迴呼叫,根據groupsIndex和providersIndex保證了Warehouse裡面的另外兩個靜態map(routes,providers)的賦值,這樣最終都會走到36行else分支,去保證所有路由資訊的完整性,另外swtich…case裡面的postcard.greenChannel()其實是activity跳轉專用的,目的是用來攔截activity跳轉,來對跳轉過程進行干預,在之前或者之後做一些自己的處理,所以greenChannel就是綠色通道,不進行攔截。另外程式碼裡面也可以看到,類的生成都是採用getConstructor().newInstance()這種反射來進行的,最終呼叫:

ModulePersonalService service = (ModulePersonalService) ARouter.getInstance().build("/Personal/service").navigation();

得到這個跨模組服務之後,裡面的所有方法都可以去呼叫來實現功能需求了。

呼叫過程

Activity的跳轉如下:

            ARouter.getInstance().build("/Personal/main").navigation(activity);

最終呼叫程式碼則是_ARouter類裡面的_navigation方法:

private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = null == context ? mContext : context;

        switch (postcard.getType()) {
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());

                // Set flags.
                int flags = postcard.getFlags();
                if (-1 != flags) {
                    intent.setFlags(flags);
                } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

                // Navigation in main looper.
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        if (requestCode > 0) {  // Need start for result
                            ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
                        } else {
                            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
                        }

                        if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
                            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
                        }

                        if (null != callback) { // Navigation over.
                            callback.onArrival(postcard);
                        }
                    }
                });

                break;
            case PROVIDER:
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
                Class fragmentMeta = postcard.getDestination();
                try {
                    Object instance = fragmentMeta.getConstructor().newInstance();
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }

                    return instance;
                } catch (Exception ex) {
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }

        return null;
    }

可以看到對Activity的處理,最終就是呼叫startActivity方法,對provider就是返回一個類的例項,而BOARDCAST、CONTENT_PROVIDER、FRAGMENT也都是生成一個例項返回,對於METHOD、SERVICE暫時是沒有處理的。

攔截器和自動注入的功能,本文沒有去分析,一般跳到某一個頁面需要判斷是否登陸的時候,可以使用攔截器,自動注入可以在頁面間傳遞資料,非常方便。

好了,今天的講解就結束了,有疑問的同學可以在下方留言~

相關推薦

阿里巴巴開源路由框架——ARouter原理分析

背景 當專案的業務越來越複雜,業務線越來越多的時候,就需要按照業務線去分不同的模組去開發,這樣專門的人負責專門的業務模組,最終上線由殼工程去負責進行組合打包各個module,完成業務的快速迭代。整個過程會涉及到各個模組間進行通訊,比如訂單模組和個人中心

阿里巴巴的Vlayout框架原始碼原理詳解(第一篇流程分析

先看一下阿里對這個框架留下的Demo的效果: 看效果大體的可以猜測這個框架給我們提供了很多佈局規則,據說淘寶首頁就是用這個框架做的。原始碼地址 好接下來我們就沿著這個Demo這條線開始分析實現原理,從而學習人家的架構搭建方式 先看佈局程式碼 <FrameLayo

滴滴開源Android外掛化框架VirtualAPK原理分析

概述 Activity 支援 Hook ActivityManagerService Hook Instrumentation 啟動外掛Acti

阿里巴巴開源框架JarsLink

JarsLink (原名Titan) 是一個基於JAVA的模組化開發框架,它提供在執行時動態載入模組(一個JAR包)、解除安裝模組和模組間呼叫的API。也是阿里巴巴的開源專案之一 https://github.com/alibaba/jarslink,目前在微貸事業群廣泛使用。 需求背景

阿里巴巴開源框架-通用快取訪問JetCache介紹

JetCache是由阿里巴巴開源的通用快取訪問框架,如果你對Spring Cache很熟悉的話,請一定花一點時間瞭解一下JetCache,它更好用。 JetCache提供的核心能力包括: 提供統一的,類似jsr-107風格的API訪問Cache,並可通過註解建立並配置Cache例項 通過註解

阿里巴巴開源框架Weex學習之 weex pack與weex toolkit的區別

Weex pack與Weex toolkit的區別: weex pack:       weexpack 是基於 Weex 快速搭建應用原型的利器。它能夠幫助開發者通過命令列建立 Weex 工程,新

阿里路由框架--ARouter最簡單例子

這是最簡單的案例 專案結果: 一個是專案的app,然後我新建了一個module:libraryone 首先處理app MainActivity.java package com.exampl

阿里巴巴開源的通用快取訪問框架JetCache介紹

GenericObjectPoolConfig pc = new GenericObjectPoolConfig();pc.setMinIdle(2);pc.setMaxIdle(10);pc.setMaxTotal(10);JedisPool pool = new JedisPool(pc, "localh

【Canal】資料同步的終極解決方案,阿里巴巴開源的Canal框架當之無愧!!

## 寫在前面 > 在當今網際網路行業,尤其是現在分散式、微服務開發環境下,為了提高搜尋效率,以及搜尋的精準度,會大量使用Redis、Memcached等NoSQL資料庫,也會使用大量的Solr、Elasticsearch等全文檢索服務。那麼,這個時候,就會有一個問題需要我們來思考和解決:那就是資料同

阿里巴巴分散式服務框架dubbo學習筆記

Dubbo是什麼? Dubbo是一個分散式服務框架,致力於提供高效能和透明化的RPC遠端服務呼叫方案,以及SOA服務治理方案。簡單的說,dubbo就是個服務框架,如果沒有分散式的需求,其實是不需要用的,只有在分散式的時候,才有dubbo這樣的分散式服務框架的需求,並且本質上是個服務呼叫的東東,說

阿里巴巴開源 Spring Cloud Alibaba,加碼微服務生態建設

本週,Spring Cloud聯合創始人Spencer Gibb在Spring官網的部落格頁面宣佈:阿里巴巴開源 Spring Cloud Alibaba,併發布了首個預覽版本。隨後,Spring Cloud 官方Twitter也釋出了此訊息,以下翻譯自部落格原文。 大家好,很高興地告訴大家,今天 Spri

高效能日誌框架 Log4a 原理分析

前言 對於移動開發者來說,針對一些使用者反饋難以復現的線上問題,分析日誌有時候是解決問題的必要手段。 但是日誌的收集一直有個痛點,就是效能與日誌完整性無法兼得。 要實現高效能的日誌收集,勢必要使用大量記憶體,先將日誌寫入記憶體中,然後在合適的時機將記憶體裡的日誌寫入到檔案系

這就是巨人的肩膀!2018年阿里巴巴開源的那些牛逼專案彙總

導讀: 開源展示了人類共同協作,成果分享的魅力,每一次技術發展都是站在巨人的肩膀上,技術諸多創新和發展往往就是基於開源發展起來的,沒有任何一家網路公司可以不使用開源技術,僅靠自身技術而發展起來。阿里巴巴各個團隊都是發自內心地將踩過的坑和總結的經驗融入到開源專案中,供業界所有人使用,希望幫助他人解決問題。

Google、亞馬遜、微軟 、阿里巴巴開源軟體一覽

作者:InfoQ 編輯:小智 為什麼要釋出開源專案?一是開源能夠幫助他人更快地開發軟體,促進世界創新,主要是社會價值層面的考慮。二是開源能夠倒逼工程師寫出更好的程式碼。三是開源能夠更有效利用社群的力量,幫助企業一起解決難題。開源,讓世界更美好,不是嗎? 寫在前面 Google,亞馬遜,微軟,和阿里

MyBatis框架原理分析(轉載)

MyBatis 是支援定製化 SQL、儲存過程以及高階對映的優秀的持久層框架,其主要就完成2件事情: 封裝JDBC操作 利用反射打通Java類與SQL語句之間的相互轉換 MyBatis的主要設計目的就是讓我們對執行SQL語句時對輸入輸出的資料管理更加方便,所以方便地寫出SQL

阿里巴巴開源限流系統 Sentinel 全解析

今年下半年阿里開源了自研的限流系統 Sentinel,官方對 Sentinel 的介紹中用到了一系列高大山的名詞諸如 限流、熔斷降級、流量塑形、系統負載保護等,還有漂亮的形容詞諸如 輕巧、專業、實時等。作為技術消費者看到這樣的廣告詞之後禁不住要大聲感嘆 —— NiuB!更要不得的是 Sentin

分散式開源排程框架TBSchedule原理與應用

主要內容: 第一部分 TBSchedule基本概念及原理 1. 概念介紹 2. 工作原理 3. 原始碼分析 4. 與其他開源排程框架對比 第二部分 TBSchedule分散式排程示例 1. TBSchedu

Mybatis框架原理分析

框架主要類層次結構 1、Mybatis主要做的工作: 1、根據JDBC規範建立與資料庫的連線 2、通過反射打通Java物件與資料庫引數互動之間相互轉化關係 2、主要類的層次關係 SqlSessionFactoryBuilder類通過各種構造器方法,

阿里巴巴開源技術彙總:115個軟體(四)

TaobaoJVM 基於 OpenJDK HotSpot VM,是國內第一個優化、定製且開源的伺服器版Java虛擬機器。目前已經在淘寶、天貓上線,全部替換了Oracle官方JVM版本,在效能,功能上都初步體現了它的價值。當前淘寶有幾萬臺Java應用伺服器,上千名Java工程師、及上百個Java應用。為此,核心

阿里巴巴開源分散式事務解決方案 Fescar

Fescar 是 阿里巴巴 開源的 分散式事務中介軟體,以 高效 並且對業務 0 侵入 的方式,解決 微服務 場景下面臨的分散式事務問題。 1. 什麼是微服務化帶來的分散式事務問題? 首先,設想一個傳統