ARouter源碼分析(一)—— 路由跳轉分析

相關工程githubhttps://github.com/AlexMahao/ARouter

邏輯分析

Arouter的路由跳轉整體可分爲三個步驟:

  • 編譯時期利用Processor生成路由清單文件。
  • 運行時期加載路由清單文件。
  • 跳轉時期根據標識符查詢路由清單,完成路由地址跳轉。

編譯時期 arouter-compiler

RouteProcessor是處理路由清單生成的類。其初始化方法如下:

@Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // 判斷是否需要生成文檔,以json的形式生成路由文檔
        if (generateDoc) {
            try {
                // ARouter/app/build/generated/source/apt/debug/com/alibaba/android/arouter/docs
                docWriter = mFiler.createResource(
                        StandardLocation.SOURCE_OUTPUT,
                        PACKAGE_OF_GENERATE_DOCS,
                        "arouter-map-of-" + moduleName + ".json"
                ).openWriter();
            } catch (IOException e) {
                logger.error("Create doc writer failed, because " + e.getMessage());
            }
        }

        iProvider = elementUtils.getTypeElement(Consts.IPROVIDER).asType();

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

主要是判斷是否生成輔助查看的文檔。

然後在parseRoutes()中獲取@Route註解,解析並生成輔助類。該方法的流程不在深入研究。看一下生成的輔助類結構。

ARouter$$Group$$test.java
ARouter$$Group$$yourservicegroupname.java                               
ARouter$$Root$$app.java
ARouter$$Providers$$app.java

總共生成了以上幾個文件,可以分爲三類:

  • Root根節點類: 包含多個分組。
  • Group: 包含該組下面的詳細的路由地址。
  • Providers : 暫時忽略。

Arouter中有分組的概念。官方文檔解釋如下:

SDK中針對所有的路徑(/test/1 /test/2)進行分組,分組只有在分組中的某一個路徑第一次被訪問的時候,該分組纔會被初始化。可以通過@Route 註解主動指定分組,否則使用路徑中第一段字符串(/*/)作爲分組。注意:一旦主動指定分組之後,應用內路由需要使用 ARouter.getInstance().build(path, group) 進行跳轉,手動指定分組,否則無法找到@Route(path = "/test/1", group = "app")

根據描述,可以看出ARouter$$Root$$app.java就是對分組的統計。該類內容如下:

public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("test", ARouter$$Group$$test.class);
    routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);
  }
}

每一個分組的標識又指向了內部路由清單明細的輔助類。

ARouter$$Group$$test.java如下:

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("ch", 5); put("fl", 6); put("dou", 7); put("boy", 0); put("url", 8); put("pac", 10); put("obj", 11); put("name", 8); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
    atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
    atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
    atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", null, -1, -2147483648));
    atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
  }
}

從省城的路由清單裏可以看到,最終的路由清單裏面,包含了路徑已經對應的跳轉信息。

跳轉信息的類爲RouteMeta, 該類結構如下:

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;
}

由此看出,上面的路由清單中聲明瞭當前路由是activity,跳轉的class等。

以上就是對編譯時期的分析。

運行時期 arouter-api

應用運行時需要調用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.init()_Arouter.afterInit(),afterInit()方法主要用於加載攔截器,不在此次分析的範疇。

 protected static synchronized boolean init(Application application) {
        mContext = application;
        // Logistics 後勤中心
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;
        mHandler = new Handler(Looper.getMainLooper());
        return true;
    }

繼續深入LogisticsCenter.init(mContext, executor);

 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;

                // It will rebuild router map every times when debuggable.
                // 如果是debug版本或者是新版本,就會重新加載路由表
                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.
                    // 獲取com.alibaba.android.arouter.routes的類列表,及Processor生成的輔助類
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                    if (!routerMap.isEmpty()) {
                        // 保存路由表緩存
                        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                    }
                    // 修改新的路由表版本
                    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();

                // 把上面加載得到的路由映射根據ClassName分爲三種,分別進行註冊
                // IRouteRoot 路由的分組輔助類
                // IInterceptorGroup 攔截器
                // IProviderGroup 服務組件
                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);
                    }
                }
            }
    }
         

該方法可以分爲兩個部分。第一個部分是獲取編譯時期獲取的類的全路徑列表。第二個部分是分別調用輔助類的loadInto()將路由表信息保存到Warehouse中。

該方法內部流程如下:

  • loadRouterMap()中將registerByPlugin置爲false,所以邏輯直接進入到else中。
  • 判斷是否是新版本或者是debug,如果是則重新獲取輔助類的全路徑列表,否則就從緩存裏面取。
  • 調用輔助類,將路由信息存儲到Warehouse中。

跳轉時期 arouter-api

ARouter.getInstance()
                        .build("/test/activity2")
                        .navigation();

跳轉的使用方式如上,看一下build方法,該方法最終調用_Arouter.build()方法

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

group如果沒有傳入,就是默認組名。

PathReplaceServiceArouter提供的一個路徑替換的服務,默認是null

最終build()返回了Postcard對象。該對象繼承RouteMeta。結構如下。

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;

    // Animation
    private Bundle optionsCompat;    // The transition animation of activity
    private int enterAnim = -1;
    private int exitAnim = -1;
}

可見該類包含了一些跳轉所需要的參數。但是當前只是包含了path等地址標識。然後調用navigation()

該方法最終調用到_Arouter_navigation()方法


    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        try {
            // 根據地址信息匹配出跳轉信息
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            // 降級處理等 ...
        }

        if (null != callback) {
            callback.onFound(postcard);
        }
        // 如果不是綠色通多,攔截器做攔截
        if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            // 攔截器處理等 ...
            
        } else {
            return _navigation(context, postcard, requestCode, callback);
        }

        return null;
    }

該方法首先調用LogisticsCenter.completion(postcard);Warehouse中查詢跳轉所需要的關鍵信息。比如目標class等。

然後判斷是否是綠色通道,最終都調用了_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:
                // 執行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);
                }

                // 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;
            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;
    }

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