三方庫源碼筆記(3)-ARouter 源碼詳解

公衆號:字節數組,但願對你有所幫助 🤣🤣java

對於 Android Developer 來講,不少開源庫都是屬於開發必備的知識點,從使用方式到實現原理再到源碼解析,這些都須要咱們有必定程度的瞭解和運用能力。因此我打算來寫一系列關於開源庫源碼解析實戰演練的文章,初定的目標是 EventBus、ARouter、LeakCanary、Retrofit、Glide、OkHttp、Coil 等七個知名開源庫,但願對你有所幫助 🤣🤣android

系列文章導航:git

1、ARouter

路由框架在大型項目中比較常見,特別是在項目中擁有多個 module 的時候。爲了實現組件化,多個 module 間的通訊就不能直接以模塊間的引用來實現,此時就須要依賴路由框架來實現模塊間的通訊和解耦github

而 ARouter 就是一個用於幫助 Android App 進行組件化改造的框架,支持模塊間的路由、通訊、解耦api

一、支持的功能

  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 插件便捷的關聯路徑和目標類
  16. 支持增量編譯(開啓文檔生成後沒法增量編譯)

二、典型應用

  1. 從外部URL映射到內部頁面,以及參數傳遞與解析
  2. 跨模塊頁面跳轉,模塊間解耦
  3. 攔截跳轉過程,處理登錄、埋點等邏輯
  4. 跨模塊API調用,經過控制反轉來作組件解耦

以上介紹來自於 ARouter 的 Github 官網:README_CN數組

本文就基於其當前(2020/10/04)ARouter 的最新版本,對 ARouter 進行一次全面的源碼解析和原理介紹,作到知其然也知其因此然,但願對你有所幫助 😂😂緩存

dependencies {
    implementation 'com.alibaba:arouter-api:1.5.0'
    kapt 'com.alibaba:arouter-compiler:1.2.2'
}
複製代碼

2、前言

假設存在一個包含多個 module 的項目,在名爲 user 的 module 中存在一個 UserHomeActivity,其對應的路由路徑/account/userHome。那麼,當咱們要從其它 module 跳轉到該頁面時,只須要指定 path 來跳轉便可markdown

package github.leavesc.user

/** * 做者:leavesC * 時間:2020/10/3 18:05 * 描述: * GitHub:https://github.com/leavesC */
@Route(path = RoutePath.USER_HOME)
class UserHomeActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_home)
    }

}

//其它頁面使用以下代碼來跳轉到 UserHomeActivity
ARouter.getInstance().build(RoutePath.USER_HOME).navigation()
複製代碼

只根據一個 path,ARouter 是如何定位到特定的 Activity 的呢?這就須要經過在編譯階段生成輔助代碼來實現了。咱們知道,想要跳轉到某個 Activity,那麼就須要拿到該 Activity 的 Class 對象才行。在編譯階段,ARouter 會根據咱們設定的路由跳轉規則來自動生成映射文件,映射文件中就包含了 path 和 ActivityClass 之間的對應關係app

例如,對於 UserHomeActivity,在編譯階段就會自動生成如下輔助文件。能夠看到,ARouter$$Group$$account 類中就將 path 和 ActivityClass 做爲鍵值對保存到了 Map 中。ARouter 就是依靠此來進行跳轉的框架

package com.alibaba.android.arouter.routes;

/** * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$account implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/account/userHome", RouteMeta.build(RouteType.ACTIVITY, UserHomeActivity.class, "/account/userhome", "account", null, -1, -2147483648));
  }
}
複製代碼

還有一個重點須要注意,就是這類自動生成的文件的包名路徑都是 com.alibaba.android.arouter.routes,且類名前綴也是有特定規則的。雖然 ARouter$$Group$$account 類實現了將對應關係保存到 Map 的邏輯,可是 loadInto 方法仍是須要由 ARouter 在運行時來調用,那麼 ARouter 就須要拿到 ARouter$$Group$$account 這個類才行,而 ARouter 就是經過掃描 com.alibaba.android.arouter.routes這個包名路徑來獲取全部輔助文件的

ARouter 的基本實現思路就是:

  1. 開發者本身維護特定 path特定的目標類之間的對應業務關係,ARouter 只要求開發者使用包含了 path 的 @Route 註解修飾目標類
  2. ARouter 在編譯階段經過註解處理器來自動生成 path 和特定的目標類之間的對應關係,即將 path 做爲 key,將目標類的 Class 對象做爲 value 之一存到 Map 之中
  3. 在運行階段,應用經過 path 來發起請求,ARouter 根據 path 從 Map 中取值,從而拿到目標類,以此來完成跳轉

3、初始化

ARouter 通常是經過在 Application 中調用 init 方法來完成初始化的,這裏先來看下其初始化流程

/** * 做者:leavesC * 時間:2020/10/4 18:05 * 描述: * GitHub:https://github.com/leavesC */
class MyApp : Application() {

    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) {
            ARouter.openDebug()
            ARouter.openLog()
        }
        ARouter.init(this)
    }

}
複製代碼

ARouter 類使用了單例模式,ARouter 類只是負責對外暴露能夠由外部調用的 API,大部分的實現邏輯仍是轉交由 _ARouter 類來完成

public final class ARouter {
    
    private volatile static ARouter instance = null;
    
    private ARouter() {
    }
    
    /** * Get instance of router. A * All feature U use, will be starts here. */
    public static ARouter getInstance() {
        if (!hasInit) {
            throw new InitException("ARouter::Init::Invoke init(context) first!");
        } else {
            if (instance == null) {
                synchronized (ARouter.class) {
                    if (instance == null) {
                        instance = new ARouter();
                    }
                }
            }
            return instance;
        }
    }

    /** * 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.");
            //經過 _ARouter 來完成初始化
            hasInit = _ARouter.init(application);
            if (hasInit) {
                _ARouter.afterInit();
            }
            _ARouter.logger.info(Consts.TAG, "ARouter init over.");
        }
    }
    
    ···
    
}
複製代碼

_ARouter 類是包私有權限,也使用了單例模式,其 init(Application) 方法的重點就在於 LogisticsCenter.init(mContext, executor)

final class _ARouter {
    
    private volatile static _ARouter instance = null;
    
    private _ARouter() {
    }

    protected static _ARouter getInstance() {
        if (!hasInit) {
            throw new InitException("ARouterCore::Init::Invoke init(context) first!");
        } else {
            if (instance == null) {
                synchronized (_ARouter.class) {
                    if (instance == null) {
                        instance = new _ARouter();
                    }
                }
            }
            return instance;
        }
    }

    protected static synchronized boolean init(Application application) {
        mContext = application;
        //重點
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;
        mHandler = new Handler(Looper.getMainLooper());
        return true;
    }
    
    ···
    
}
複製代碼

LogisticsCenter 就實現了前文說的掃描特定包名路徑拿到全部自動生成的輔助文件的邏輯,即在進行初始化的時候,咱們就須要加載到當前項目一共包含的全部 group,以及每一個 group 對應的路由信息表,其主要邏輯是:

  1. 若是當前開啓了 debug 模式或者經過本地 SP 緩存判斷出 app 的版本先後發生了變化,那麼就從新獲取全局路由信息,不然就仍是使用以前緩存到 SP 中的數據
  2. 獲取全局路由信息是一個比較耗時的操做,因此 ARouter 就經過將全局路由信息緩存到 SP 中來實現複用。但因爲在開發階段開發者可能隨時就會添加新的路由表,而每次發佈新版本正常來講都是會加大應用的版本號的,因此 ARouter 就只在開啓了 debug 模式或者是版本號發生了變化的時候纔會從新獲取路由信息
  3. 獲取到的路由信息中包含了在 com.alibaba.android.arouter.routes 這個包下自動生成的輔助文件的全路徑,經過判斷路徑名的前綴字符串,就能夠知道該類文件對應什麼類型,而後經過反射構建不一樣類型的對象,經過調用對象的方法將路由信息存到 Warehouse 的 Map 中。至此,整個初始化流程就結束了
public class LogisticsCenter {
    
    /** * LogisticsCenter init, load all metas in memory. Demand initialization */
    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;

        try {
            long startInit = System.currentTimeMillis();
            //billy.qi modified at 2017-12-06
            //load by plugin first
            loadRouterMap();
            if (registerByPlugin) {
                logger.info(TAG, "Load router map by arouter-auto-register plugin.");
            } else {
                Set<String> routerMap;

                //若是當前開啓了 debug 模式或者經過本地 SP 緩存判斷出 app 的版本先後發生了變化
                //那麼就從新獲取路由信息,不然就從使用以前緩存到 SP 中的數據
                // It will rebuild router map every times when debuggable.
                if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                    logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                    // These class was generated by arouter-compiler.
                    //獲取 ROUTE_ROOT_PAKCAGE 包名路徑下包含的全部的 ClassName
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                    if (!routerMap.isEmpty()) {
                        //緩存到 SP 中
                        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                    }
                    //更新 App 的版本信息
                    PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
                } else {
                    logger.info(TAG, "Load router map from cache.");
                    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                }

                logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
                startInit = System.currentTimeMillis();

                for (String className : routerMap) {
                    //經過 className 的前綴來判斷該 class 對應的什麼類型,並同時緩存到 Warehouse 中
                    //1.IRouteRoot
                    //2.IInterceptorGroup
                    //3.IProviderGroup
                    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);
                    }
                }
            }

           ···
               
        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }
    
}
複製代碼

對於第三步,能夠舉個例子來增強理解

對於上文所講的 UserHomeActivity,放在名爲 user 的 module 中,按照 ARouter 的要求,在 gradle 文件中爲該 module 聲明瞭如下配置,將 projectName 做爲參數值。UserHomeActivity 對應的 path 是 /account/userHome,ARouter 默認會將 path 的第一個單詞即 account 做爲其 group

javaCompileOptions {
    kapt {
        arguments {
            arg("AROUTER_MODULE_NAME", project.getName())
        }
    }
}
複製代碼

ARouter 在經過註解處理器生成輔助文件的時候,類名就會根據以上信息來生成,因此最終就會生成如下兩個文件:

package com.alibaba.android.arouter.routes;

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

package com.alibaba.android.arouter.routes;

/** * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$account implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/account/userHome", RouteMeta.build(RouteType.ACTIVITY, UserHomeActivity.class, "/account/userhome", "account", null, -1, -2147483648));
  }
}
複製代碼

LogisticsCenter 的 init 方法就會根據文件名的固定前綴 ARouter$$Root$$ 掃描到 ARouter$$Root$$user 這個類,而後經過反射構建出該對象,而後經過調用其 loadInto 方法將鍵值對保存到 Warehouse.groupsIndex 中。等到後續須要跳轉到 groupaccount 的頁面時,就會再來反射調用 ARouter$$Group$$accountloadInto 方法,即按需加載,等到須要用到的時候再來獲取詳細的路由對應信息

由於對於一個大型的 App 來講,可能包含幾十甚至幾百個頁面,若是一次性將全部路由信息都加載到內存中,對於內存的壓力是比較大的,而用戶每次使用可能也只會打開十幾個頁面,因此這是就實現了按需加載

4、跳轉到 Activity

講完初始化流程,再來看下 ARouter 實現 Activity 跳轉的流程

跳轉到 Activity 最簡單的方式就是隻指定 path:

ARouter.getInstance().build(RoutePath.USER_HOME).navigation()
複製代碼

build() 方法會經過 ARouter 中轉調用到 _ARouterbuild() 方法,最終返回一個 Postcard 對象

/** * Build postcard by path and default group */
    protected Postcard build(String path) {
        if (TextUtils.isEmpty(path)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                //用於路徑替換,這對於某些須要控制頁面跳轉流程的場景比較有用
                //例如,若是某個頁面須要登陸才能夠展現的話
                //就能夠經過 PathReplaceService 將 path 替換 loginPagePath
                path = pService.forString(path);
            }
            //使用字符串 path 包含的第一個單詞做爲 group
            return build(path, extractGroup(path));
        }
    }

    /** * Build postcard by path and group */
    protected Postcard build(String path, String group) {
        if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            return new Postcard(path, group);
        }
    }
複製代碼

返回的 Postcard 對象能夠用於傳入一些跳轉配置參數,例如:攜帶參數 mBundle、開啓綠色通道 greenChannel 、跳轉動畫 optionsCompat

public final class Postcard extends RouteMeta {
    // Base
    private Uri uri;
    private Object tag;             // A tag prepare for some thing wrong.
    private Bundle mBundle;         // Data to transform
    private int flags = -1;         // Flags of route
    private int timeout = 300;      // Navigation timeout, TimeUnit.Second
    private IProvider provider;     // It will be set value, if this postcard was provider.
    private boolean greenChannel;
    private SerializationService serializationService;
    
}
複製代碼

Postcard 的 navigation() 方法又會調用到 _ARouter 的如下方法來完成 Activity 的跳轉。該方法邏輯上並不複雜,註釋也寫得很清楚了

final class _ARouter {

    /** * Use router navigation. * * @param context Activity or null. * @param postcard Route metas * @param requestCode RequestCode * @param callback cb */
    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
        if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
            // Pretreatment failed, navigation canceled.
            //用於執行跳轉前的預處理操做,能夠經過 onPretreatment 方法的返回值決定是否取消跳轉
            return null;
        }

        try {
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            //沒有找到匹配的目標類
            //下面就執行一些提示操做和事件回調通知
            
            logger.warning(Consts.TAG, ex.getMessage());
            if (debuggable()) {
                // Show friendly tips for user.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(mContext, "There's no route matched!\n" +
                                " Path = [" + postcard.getPath() + "]\n" +
                                " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
                    }
                });
            }
            if (null != callback) {
                callback.onLost(postcard);
            } else {
                // No callback for this invoke, then we use the global degrade service.
                DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
                if (null != degradeService) {
                    degradeService.onLost(context, postcard);
                }
            }
            return null;
        }

        if (null != callback) {
            //找到了匹配的目標類
            callback.onFound(postcard);
        }

        if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            //沒有開啓綠色通道,那麼就還須要執行全部攔截器
            //外部能夠經過攔截器實現:控制是否容許跳轉、更改跳轉參數等邏輯
            
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /** * Continue process * * @param postcard route meta */
                @Override
                public void onContinue(Postcard postcard) {
                    //攔截器容許跳轉
                    _navigation(context, postcard, requestCode, callback);
                }

                /** * Interrupt process, pipeline will be destory when this method called. * * @param exception Reson of interrupt. */
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }

                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {
            //開啓了綠色通道,直接跳轉,不須要遍歷攔截器
            return _navigation(context, postcard, requestCode, callback);
        }

        return null;
    }
    
    //因爲本例子的目標頁面是 Activity,因此只看 ACTIVITY 便可
    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
                //Destination 就是指向目標 Activity 的 class 對象
                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);
                }

                // Set Actions
                String action = postcard.getAction();
                if (!TextUtils.isEmpty(action)) {
                    intent.setAction(action);
                }

                // Navigation in main looper.
                //最終在主線程完成跳轉
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });

                break;
            ··· //省略其它類型判斷
        }

        return null;
    }

}
複製代碼

navigation 方法的重點在於 LogisticsCenter.completion(postcard) 這一句代碼。在講 ARouter 初始化流程的時候有講到:等到後續須要跳轉到 groupaccount 的頁面時,就會再來反射調用 ARouter$$Group$$accountloadInto 方法,即按需加載,等到須要的時候再來獲取詳細的路由對應信息

completion 方法就是用來獲取詳細的路由對應信息的。該方法會經過 postcard 攜帶的 path 和 group 信息從 Warehouse 取值,若是值不爲 null 的話就將信息保存到 postcard 中,若是值爲 null 的話就拋出 NoRouteFoundException

/** * Completion the postcard by route metas * * @param postcard Incomplete postcard, should complete 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) {    //爲 null 說明目標類不存在或者是該 group 還未加載過
            Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
            if (null == groupMeta) {
                //groupMeta 爲 null,說明 postcard 的 path 對應的 group 不存在,拋出異常
                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()));
                    }
				  //會執行到這裏,說明此 group 還未加載過,那麼就來反射加載 group 對應的全部 path 信息
                   //獲取後就保存到 Warehouse.routes
                    IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                    iGroupInstance.loadInto(Warehouse.routes);
                    
                    //移除此 group
                    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 中
            
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());

            //省略一些和本例子無關的代碼
            ···
        }
    }
複製代碼

5、跳轉到 Activity 並注入參數

ARouter 也支持在跳轉到 Activity 的同時向目標頁面自動注入參數

在跳轉的時候指定要攜帶的鍵值對參數:

ARouter.getInstance().build(RoutePath.USER_HOME)
        .withLong(RoutePath.USER_HOME_PARAMETER_ID, 20)
        .withString(RoutePath.USER_HOME_PARAMETER_NAME, "leavesC")
        .navigation()

object RoutePath {

    const val USER_HOME = "/account/userHome"

    const val USER_HOME_PARAMETER_ID = "userHomeId"

    const val USER_HOME_PARAMETER_NAME = "userName"

}
複製代碼

在目標頁面經過 @Autowired 註解修飾變量。註解能夠同時聲明其 name 參數,用於和傳遞的鍵值對中的 key 對應上,這樣 ARouter 才知道應該向哪一個變量賦值。若是沒有聲明 name 參數,那麼 name 參數就默認和變量名相等

這樣,在咱們調用 ARouter.getInstance().inject(this) 後,ARouter 就會自動完成參數的賦值

package github.leavesc.user

/** * 做者:leavesC * 時間:2020/10/4 14:05 * 描述: * GitHub:https://github.com/leavesC */
@Route(path = RoutePath.USER_HOME)
class UserHomeActivity : AppCompatActivity() {

    @Autowired(name = RoutePath.USER_HOME_PARAMETER_ID)
    @JvmField
    var userId: Long = 0

    @Autowired
    @JvmField
    var userName = ""

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_home)
        ARouter.getInstance().inject(this)
        tv_hint.text = "$userId $userName"
    }

}
複製代碼

ARouter 實現參數自動注入也須要依靠註解處理器生成的輔助文件來實現,即會生成如下的輔助代碼:

package github.leavesc.user;

/** * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class UserHomeActivity$$ARouter$$Autowired implements ISyringe {
    
  //用於實現序列化和反序列化
  private SerializationService serializationService;

  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);
    UserHomeActivity substitute = (UserHomeActivity)target;
    substitute.userId = substitute.getIntent().getLongExtra("userHomeId", substitute.userId);
    substitute.userName = substitute.getIntent().getStringExtra("userName");
  }
}

複製代碼

由於在跳轉到 Activity 時攜帶的參數也是須要放到 Intent 裏的,因此 inject 方法也只是幫咱們實現了從 Intent 取值而後向變量賦值的邏輯而已,這就要求相應的變量必須是 public 的,這就是在 Kotlin 代碼中須要同時向變量加上 @JvmField註解的緣由

如今來看下 ARouter 是如何實現參數自動注入的,其起始方法就是:ARouter.getInstance().inject(this),其最終會調用到如下方法

final class _ARouter {
   
    static void inject(Object thiz) {
        AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());
        if (null != autowiredService) {
            autowiredService.autowire(thiz);
        }
    }
    
}
複製代碼

ARouter 經過控制反轉的方式拿到 AutowiredService 對應的實現類 AutowiredServiceImpl 的實例對象,而後調用其 autowire 方法完成參數注入

因爲生成的參數注入輔助類的類名具備固定的包名和類名,即包名和目標類所在包名一致,類名是目標類類名+ $$ARouter$$Autowired,因此在 AutowiredServiceImpl 中就能夠根據傳入的 instance 參數和反射來生成輔助類對象,最終調用其 inject 方法完成參數注入

@Route(path = "/arouter/service/autowired")
public class AutowiredServiceImpl implements AutowiredService {
    private LruCache<String, ISyringe> classCache;
    private List<String> blackList;

    @Override
    public void init(Context context) {
        classCache = new LruCache<>(66);
        blackList = new ArrayList<>();
    }

    @Override
    public void autowire(Object instance) {
        String className = instance.getClass().getName();
        try {
            //若是在白名單中了的話,那麼就再也不執行參數注入
            if (!blackList.contains(className)) {
                ISyringe autowiredHelper = classCache.get(className);
                if (null == autowiredHelper) {  // No cache.
                    autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();
                }
                //完成參數注入
                autowiredHelper.inject(instance);
                //緩存起來,避免重複反射
                classCache.put(className, autowiredHelper);
            }
        } catch (Exception ex) {
            //若是參數注入過程拋出異常,那麼就將其加入白名單中
            blackList.add(className);    // This instance need not autowired.
        }
    }
}
複製代碼

6、控制反轉

上一節所講的跳轉到 Activity 並自動注入參數屬於依賴注入的一種,ARouter 同時也支持控制反轉:經過接口來獲取其實現類實例

例如,假設存在一個 ISayHelloService 接口,咱們須要拿到其實現類實例,可是不但願在使用的時候和特定的實現類 SayHelloService 綁定在一塊兒從而形成強耦合,此時就可使用 ARouter 的控制反轉功能,但這也要求 ISayHelloService 接口繼承了 IProvider 接口才行

/** * 做者:leavesC * 時間:2020/10/4 13:49 * 描述: * GitHub:https://github.com/leavesC */
interface ISayHelloService : IProvider {

    fun sayHello()

}

@Route(path = RoutePath.SERVICE_SAY_HELLO)
class SayHelloService : ISayHelloService {

    override fun init(context: Context) {

    }

    override fun sayHello() {
        Log.e("SayHelloService", "$this sayHello")
    }

}
複製代碼

在使用的時候直接傳遞 ISayHelloService 的 Class 對象便可,ARouter 會將 SayHelloService 以單例模式的形式返回,無需開發者手動去構建 SayHelloService 對象,從而達到解耦的目的

ARouter.getInstance().navigation(ISayHelloService::class.java).sayHello()
複製代碼

和實現 Activity 跳轉的時候同樣,ARouter 也會自動生成如下幾個文件,包含了路由表的映射關係

package com.alibaba.android.arouter.routes;

/** * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$account implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/account/sayHelloService", RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayhelloservice", "account", null, -1, -2147483648));
  }
}

/** * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Providers$$user implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    providers.put("github.leavesc.user.ISayHelloService", RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayHelloService", "account", null, -1, -2147483648));
  }
}

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

這裏再來看下其具體的實現原理

在講初始化流程的時候有講到,LogisticsCenter 實現了掃描特定包名路徑拿到全部自動生成的輔助文件的邏輯。因此,最終 Warehouse 中就會在初始化的時候拿到如下數據

Warehouse.groupsIndex:

  • account -> class com.alibaba.android.arouter.routes.ARouter$$Group$$account

Warehouse.providersIndex:

  • github.leavesc.user.ISayHelloService -> RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayHelloService", "account", null, -1, -2147483648)

ARouter.getInstance().navigation(ISayHelloService::class.java) 最終會中轉調用到 _ARouter 的如下方法

protected <T> T navigation(Class<? extends T> service) {
        try {
            //從 Warehouse.providersIndex 取值拿到 RouteMeta 中存儲的 path 和 group
            Postcard postcard = LogisticsCenter.buildProvider(service.getName());

            // Compatible 1.0.5 compiler sdk.
            // Earlier versions did not use the fully qualified name to get the service
            if (null == postcard) {
                // No service, or this service in old version.
                postcard = LogisticsCenter.buildProvider(service.getSimpleName());
            }

            if (null == postcard) {
                return null;
            }
		   //重點
            LogisticsCenter.completion(postcard);
            return (T) postcard.getProvider();
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());
            return null;
        }
    }
複製代碼

LogisticsCenter.completion(postcard) 方法的流程和以前講解的差很少,只是在獲取對象實例的時候同時將實例緩存起來,留待以後複用,至此就完成了控制反轉的流程了

/** * Completion the postcard by route metas * * @param postcard Incomplete postcard, should complete by this method. */
    public synchronized static void completion(Postcard postcard) {
	   ... //省略以前已經講解過的代碼 
  
	   RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        
        switch (routeMeta.getType()) {
                case PROVIDER:  // if the route is provider, should find its instance
                    // Its provider, so it must implement IProvider
                	//拿到 SayHelloService Class 對象
                    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
                        //instance 等於 null 說明是第一次取值
                        //那麼就經過反射構建 SayHelloService 對象,而後將之緩存到 Warehouse.providers 中
                        //因此經過控制反轉獲取的對象在應用的整個生命週期內只會有一個實例
                        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;
            }
        
    }
複製代碼

7、攔截器

ARouter 的攔截器對於某些須要控制頁面跳轉流程的業務邏輯來講是十分有用的功能。例如,用戶若是要跳轉到我的資料頁面時,咱們就能夠經過攔截器來判斷用戶是否處於已登陸狀態,還未登陸的話就能夠攔截該請求,而後自動爲用戶打開登陸頁面

咱們能夠同時設定多個攔截器,每一個攔截器設定不一樣的優先級

/** * 做者:leavesC * 時間:2020/10/5 11:49 * 描述: * GitHub:https://github.com/leavesC */
@Interceptor(priority = 100, name = "啥也不作的攔截器")
class NothingInterceptor : IInterceptor {

    override fun init(context: Context) {

    }

    override fun process(postcard: Postcard, callback: InterceptorCallback) {
        //不攔截,任其跳轉
        callback.onContinue(postcard)
    }

}

@Interceptor(priority = 200, name = "登錄攔截器")
class LoginInterceptor : IInterceptor {

    override fun init(context: Context) {

    }

    override fun process(postcard: Postcard, callback: InterceptorCallback) {
        if (postcard.path == RoutePath.USER_HOME) {
            //攔截
            callback.onInterrupt(null)
            //跳轉到登錄頁
            ARouter.getInstance().build(RoutePath.USER_LOGIN).navigation()
        } else {
            //不攔截,任其跳轉
            callback.onContinue(postcard)
        }
    }

}
複製代碼

這樣,當咱們執行 ARouter.getInstance().build(RoutePath.USER_HOME).navigation() 想要跳轉的時候,就會發現打開的實際上是登陸頁 RoutePath.USER_LOGIN

來看下攔截器是如何實現的

對於以上的兩個攔截器,會生成如下的輔助文件。輔助文件會拿到全部咱們自定義的攔截器實現類並根據優先級高低存到 Map 中

package com.alibaba.android.arouter.routes;

/** * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Interceptors$$user implements IInterceptorGroup {
  @Override
  public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
    interceptors.put(100, NothingInterceptor.class);
    interceptors.put(200, LoginInterceptor.class);
  }
}
複製代碼

而這些攔截器同樣是會在初始化的時候,經過LogisticsCenter.init方法存到 Warehouse.interceptorsIndex

/** * LogisticsCenter init, load all metas in memory. Demand initialization */
    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        
        ···
            
        for (String className : routerMap) {
                    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);
                    }
                }
        
          ···
        
    }
複製代碼

而後,在 _ARouternavigation 方法中,如何判斷到這次路由請求沒有開啓綠色通道模式的話,那麼就會將這次請求轉交給 interceptorService,讓其去遍歷每一個攔截器

final class _ARouter {
 
    /** * Use router navigation. * * @param context Activity or null. * @param postcard Route metas * @param requestCode RequestCode * @param callback cb */
    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        
        ···
            
        if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            
            //遍歷攔截器
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /** * Continue process * * @param postcard route meta */
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, postcard, requestCode, callback);
                }

                /** * Interrupt process, pipeline will be destory when this method called. * * @param exception Reson of interrupt. */
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }

                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {
            return _navigation(context, postcard, requestCode, callback);
        }

        return null;
    }
    
}
複製代碼

interceptorService 變量屬於 InterceptorService 接口類型,該接口的實現類是 InterceptorServiceImpl,ARouter內部在初始化的過程當中也是根據控制反轉的方式來拿到 interceptorService 這個實例的

InterceptorServiceImpl 的主要邏輯是:

  1. 在第一次獲取 InterceptorServiceImpl 實例的時候,其 init 方法會立刻被調用,該方法內部會交由線程池來執行,經過反射生成每一個攔截器對象,並調用每一個攔截器的 init 方法來完成攔截器的初始化,並將每一個攔截器對象都存到 Warehouse.interceptors 中。若是初始化完成了,則喚醒等待在 interceptorInitLock 上的線程
  2. 當攔截器邏輯被觸發,即 doInterceptions 方法被調用時,若是此時第一個步驟還未執行完的話,則會經過 checkInterceptorsInitStatus()方法等待第一個步驟執行完成。若是十秒內都未完成的話,則走失敗流程直接返回
  3. 在線程池中遍歷攔截器列表,若是有某個攔截器攔截了請求的話則調用 callback.onInterrupt方法通知外部,不然的話則調用 callback.onContinue() 方法繼續跳轉邏輯
@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {
    private static boolean interceptorHasInit;
    private static final Object interceptorInitLock = new Object();
    
    @Override
    public void init(final Context context) {
        LogisticsCenter.executor.execute(new Runnable() {
            @Override
            public void run() {
                if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
                    //遍歷攔截器列表,經過反射構建對象並初始化
                    for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
                        Class<? extends IInterceptor> interceptorClass = entry.getValue();
                        try {
                            IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
                            iInterceptor.init(context);
                            //存起來
                            Warehouse.interceptors.add(iInterceptor);
                        } catch (Exception ex) {
                            throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]");
                        }
                    }

                    interceptorHasInit = true;

                    logger.info(TAG, "ARouter interceptors init over.");

                    synchronized (interceptorInitLock) {
                        interceptorInitLock.notifyAll();
                    }
                }
            }
        });
    }


    @Override
    public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
        if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {

            checkInterceptorsInitStatus();

            if (!interceptorHasInit) {
                //初始化過久,不等了,直接走失敗流程
                callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
                return;
            }

            LogisticsCenter.executor.execute(new Runnable() {
                @Override
                public void run() {
                    CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                    try {
                        _excute(0, interceptorCounter, postcard);
                        interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                        if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn't return anythings.
                            //大於 0 說明這次請求被某個攔截器攔截了,走失敗流程
                            callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                        } else if (null != postcard.getTag()) {    // Maybe some exception in the tag.
                            callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
                        } else {
                            callback.onContinue(postcard);
                        }
                    } catch (Exception e) {
                        callback.onInterrupt(e);
                    }
                }
            });
        } else {
            callback.onContinue(postcard);
        }
    }

    /** * Excute interceptor * * @param index current interceptor index * @param counter interceptor counter * @param postcard routeMeta */
    private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
        if (index < Warehouse.interceptors.size()) {
            IInterceptor iInterceptor = Warehouse.interceptors.get(index);
            iInterceptor.process(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    // Last interceptor excute over with no exception.
                    counter.countDown();
                    _excute(index + 1, counter, postcard);  // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
                }

                @Override
                public void onInterrupt(Throwable exception) {
                    // Last interceptor excute over with fatal exception.

                    postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage());    // save the exception message for backup.
                    counter.cancel();
                    // Be attention, maybe the thread in callback has been changed,
                    // then the catch block(L207) will be invalid.
                    // The worst is the thread changed to main thread, then the app will be crash, if you throw this exception!
// if (!Looper.getMainLooper().equals(Looper.myLooper())) { // You shouldn't throw the exception if the thread is main thread.
// throw new HandlerException(exception.getMessage());
// }
                }
            });
        }
    }

    private static void checkInterceptorsInitStatus() {
        synchronized (interceptorInitLock) {
            while (!interceptorHasInit) {
                try {
                    interceptorInitLock.wait(10 * 1000);
                } catch (InterruptedException e) {
                    throw new HandlerException(TAG + "Interceptor init cost too much time error! reason = [" + e.getMessage() + "]");
                }
            }
        }
    }
    
}
複製代碼

8、註解處理器

通篇讀下來,讀者應該可以感覺到註解處理器在 ARouter 中起到了很大的做用,依靠註解處理器生成的輔助文件,ARouter 才能完成參數自動注入等功能。這裏就再來介紹下 ARouter 關於註解處理器的實現原理

註解處理器(Annotation Processing Tool)是一種註解處理工具,用來在編譯期掃描和處理註解,經過註解來生成 Java 文件。即以註解做爲橋樑,經過預先規定好的代碼生成規則來自動生成 Java 文件。此類註解框架的表明有 ButterKnife、Dragger二、EventBus 等

Java API 已經提供了掃描源碼並解析註解的框架,開發者能夠經過繼承 AbstractProcessor 類來實現本身的註解解析邏輯。APT 的原理就是在註解了某些代碼元素(如字段、函數、類等)後,在編譯時編譯器會檢查 AbstractProcessor 的子類,而且自動調用其 process() 方法,而後將添加了指定註解的全部代碼元素做爲參數傳遞給該方法,開發者再根據註解元素在編譯期輸出對應的 Java 代碼

關於 APT 技術的原理和應用能夠看這篇文章:Android APT 實例講解

ARouter 源碼中和註解處理器相關的 module 有兩個:

  • arouter-annotation。Java Module,包含了像 Autowired、Interceptor 這些註解以及 RouteMeta 等 JavaBean
  • arouter-compiler。Android Module,包含了多個 AbstractProcessor 的實現類用於生成代碼

這裏主要來看 arouter-compiler,這裏以自定義的攔截器 NothingInterceptor 做爲例子

package github.leavesc.user

/** * 做者:leavesC * 時間:2020/10/5 11:49 * 描述: * GitHub:https://github.com/leavesC */
@Interceptor(priority = 100, name = "啥也不作的攔截器")
class NothingInterceptor : IInterceptor {

    override fun init(context: Context) {

    }

    override fun process(postcard: Postcard, callback: InterceptorCallback) {
        //不攔截,任其跳轉
        callback.onContinue(postcard)
    }

}
複製代碼

生成的輔助文件:

package com.alibaba.android.arouter.routes;

import com.alibaba.android.arouter.facade.template.IInterceptor;
import com.alibaba.android.arouter.facade.template.IInterceptorGroup;
import github.leavesc.user.NothingInterceptor;
import java.lang.Class;
import java.lang.Integer;
import java.lang.Override;
import java.util.Map;

/** * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Interceptors$$user implements IInterceptorGroup {
  @Override
  public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
    interceptors.put(100, NothingInterceptor.class);
  }
}
複製代碼

那麼,生成的輔助文件咱們就要包含如下幾個元素:

  1. 包名
  2. 導包
  3. 註釋
  4. 實現類及繼承的接口
  5. 包含的方法及方法參數
  6. 方法體
  7. 修飾符

若是經過硬編碼的形式,即經過拼接字符串的方式來生成以上代碼也是能夠的,可是這樣會使得代碼很差維護且可讀性很低,因此 ARouter 是經過 JavaPoet 這個開源庫來生成代碼的。JavaPoet 是 square 公司開源的 Java 代碼生成框架,能夠很方便地經過其提供的 API 來生成指定格式(修飾符、返回值、參數、函數體等)的代碼

攔截器對應的 AbstractProcessor 子類就是 InterceptorProcessor,其主要邏輯是:

  1. 在 process 方法中經過 RoundEnvironment 拿到全部使用了 @Interceptor 註解進行修飾的代碼元素 elements,而後遍歷全部 item
  2. 判斷每一個 item 是否繼承了 IInterceptor 接口,是的話則說明該 item 就是咱們要找的攔截器實現類
  3. 獲取每一個 item 包含的 @Interceptor 註解對象,根據咱們爲之設定的優先級 priority,將每一個 item 按順序存到 interceptors 中
  4. 若是存在兩個攔截器的優先級相同,那麼就拋出異常
  5. 將全部攔截器按順序存入 interceptors 後,經過 JavaPoet 提供的 API 來生成包名、導包、註釋、實現類等多個代碼元素,並最終生成一個完整的類文件
@AutoService(Processor.class)
@SupportedAnnotationTypes(ANNOTATION_TYPE_INTECEPTOR)
public class InterceptorProcessor extends BaseProcessor {

    //用於保存攔截器,按照優先級高低進行排序
    private Map<Integer, Element> interceptors = new TreeMap<>();

    private TypeMirror iInterceptor = null;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        iInterceptor = elementUtils.getTypeElement(Consts.IINTERCEPTOR).asType();

        logger.info(">>> InterceptorProcessor init. <<<");
    }

    /** * {@inheritDoc} * * @param annotations * @param roundEnv */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (CollectionUtils.isNotEmpty(annotations)) {
            //拿到全部使用了 @Interceptor 進行修飾的代碼元素
            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Interceptor.class);
            try {
                parseInterceptors(elements);
            } catch (Exception e) {
                logger.error(e);
            }
            return true;
        }

        return false;
    }

    /** * Parse tollgate. * * @param elements elements of tollgate. */
    private void parseInterceptors(Set<? extends Element> elements) throws IOException {
        if (CollectionUtils.isNotEmpty(elements)) {
            logger.info(">>> Found interceptors, size is " + elements.size() + " <<<");

            // Verify and cache, sort incidentally.
            for (Element element : elements) {
                //判斷使用了 @Interceptor 進行修飾的代碼元素是否同時實現了 com.alibaba.android.arouter.facade.template.IInterceptor 這個接口
                //二者缺一不可
                if (verify(element)) {  // Check the interceptor meta
                    logger.info("A interceptor verify over, its " + element.asType());
                    Interceptor interceptor = element.getAnnotation(Interceptor.class);

                    Element lastInterceptor = interceptors.get(interceptor.priority());
                    if (null != lastInterceptor) { // Added, throw exceptions
                        //不爲 null 說明存在兩個攔截器其優先級相等,這是不容許的,直接拋出異常
                        throw new IllegalArgumentException(
                                String.format(Locale.getDefault(), "More than one interceptors use same priority [%d], They are [%s] and [%s].",
                                        interceptor.priority(),
                                        lastInterceptor.getSimpleName(),
                                        element.getSimpleName())
                        );
                    }
                    //將攔截器按照優先級高低進行排序保存
                    interceptors.put(interceptor.priority(), element);
                } else {
                    logger.error("A interceptor verify failed, its " + element.asType());
                }
            }

            // Interface of ARouter.
            //拿到 com.alibaba.android.arouter.facade.template.IInterceptor 這個接口的類型抽象
            TypeElement type_ITollgate = elementUtils.getTypeElement(IINTERCEPTOR);
            //拿到 com.alibaba.android.arouter.facade.template.IInterceptorGroup 這個接口的類型抽象
            TypeElement type_ITollgateGroup = elementUtils.getTypeElement(IINTERCEPTOR_GROUP);

            /** * Build input type, format as : * * ```Map<Integer, Class<? extends ITollgate>>``` */
            //生成對 Map<Integer, Class<? extends IInterceptor>> 這段代碼的抽象封裝
            ParameterizedTypeName inputMapTypeOfTollgate = ParameterizedTypeName.get(
                    ClassName.get(Map.class),
                    ClassName.get(Integer.class),
                    ParameterizedTypeName.get(
                            ClassName.get(Class.class),
                            WildcardTypeName.subtypeOf(ClassName.get(type_ITollgate))
                    )
            );

            // Build input param name.
            //生成 loadInto 方法的入參參數 interceptors
            ParameterSpec tollgateParamSpec = ParameterSpec.builder(inputMapTypeOfTollgate, "interceptors").build();

            // Build method : 'loadInto'
            //生成 loadInto 方法
            MethodSpec.Builder loadIntoMethodOfTollgateBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(tollgateParamSpec);

            // Generate
            if (null != interceptors && interceptors.size() > 0) {
                // Build method body
                for (Map.Entry<Integer, Element> entry : interceptors.entrySet()) {
                    //遍歷每一個攔截器,生成 interceptors.put(100, NothingInterceptor.class); 這類型的代碼
                    loadIntoMethodOfTollgateBuilder.addStatement("interceptors.put(" + entry.getKey() + ", $T.class)", ClassName.get((TypeElement) entry.getValue()));
                }
            }

            // Write to disk(Write file even interceptors is empty.)
            //包名固定是 PACKAGE_OF_GENERATE_FILE,即 com.alibaba.android.arouter.routes
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(NAME_OF_INTERCEPTOR + SEPARATOR + moduleName) //設置類名
                            .addModifiers(PUBLIC) //添加 public 修飾符
                            .addJavadoc(WARNING_TIPS) //添加註釋
                            .addMethod(loadIntoMethodOfTollgateBuilder.build()) //添加 loadInto 方法
                            .addSuperinterface(ClassName.get(type_ITollgateGroup)) //最後生成的類同時實現了 IInterceptorGroup 接口
                            .build()
            ).build().writeTo(mFiler);

            logger.info(">>> Interceptor group write over. <<<");
        }
    }

    /** * Verify inteceptor meta * * @param element Interceptor taw type * @return verify result */
    private boolean verify(Element element) {
        Interceptor interceptor = element.getAnnotation(Interceptor.class);
        // It must be implement the interface IInterceptor and marked with annotation Interceptor.
        return null != interceptor && ((TypeElement) element).getInterfaces().contains(iInterceptor);
    }
}
複製代碼

9、結尾

ARouter 的實現原理和源碼解析都講得差很少了,自認仍是講得挺全面的,那麼下一篇就再來進入實戰篇吧,本身來動手實現一個簡易版本的 ARouter 😂😂

相關文章
相關標籤/搜索