原始碼分析 — ARouter

Elson_6發表於2018-03-09

目錄

  1. ARouter整體結構
  2. ARouter初始化流程的原始碼分析
  3. ARouter攔截器優先順序的問題

ARouter整體結構

這裡寫圖片描述

主要的類:

類名 類的描述
ARouter ARouter facade(門面模式);
_ARouter ARouter core (Facade patten)
LogisticsCenter LogisticsCenter contain all of the map.
Postcard A container that contains the roadmap.
Warehouse Storage of route meta and other data.
ClassUtils 獲取ARouter自動生成的程式碼檔案

ARouter初始化流程的原始碼分析

1. ARouter初始化時序圖

這裡寫圖片描述

2. ARouter初始化程式碼分析

在Application中初始化程式碼:ARouter.init(application)

// ARouter.class
public static void init(Application application) {
 if (!hasInit) {
        logger = _ARouter.logger;
        _ARouter.logger.info(Consts.TAG, "ARouter init start.");
        // 初始化init()
        hasInit = _ARouter.init(application);

        if (hasInit) {
	    // 啟動InterceptorService
            _ARouter.afterInit();
        }

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

// _ARouter.class
protected static synchronized boolean init(Application application) {
    mContext = application;
    // LogisticsCenter的初始化
    LogisticsCenter.init(mContext, executor);
    logger.info(Consts.TAG, "ARouter init success!");
    hasInit = true;
    return true;
}

// LogisticsCenter.class
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    mContext = context;
    executor = tpe;

    try {
        loadRouterMap();
        if (registerByPlugin) {
            logger.info(TAG, "Load router map by arouter-auto-register plugin.");
        } else {
            Set<String> routerMap;

            // 如果是Debug模式,則執行從if程式碼塊;
            // 如果是Release模式:通過PackageUtils.isNewVersion(context)判斷當前應用是否是第一次啟動;
            if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                // 獲取arouter-compiler生成的檔案
                routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                // 儲存在sp中,下次啟動應用的時候,直接從sp快取中讀取
                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 finish.
            } else {
	        // 從sp快取中讀取arouter-compiler生成的檔案
                routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
            }
			
	    // 遍歷arouter-compiler生成的檔案,將他們按照型別分別儲存到Warehouse的對應欄位中
            for (String className : routerMap) {
	        // 類名字首為com.alibaba.android.arouter.routes.ARouter$$Root的檔案
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    // 將類名字首為com.alibaba.android.arouter.routes.ARouter$$Group的檔案新增進Warehouse.groupsIndex中
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                    
                } 
                // 類名字首為com.alibaba.android.arouter.routes.ARouter$$Interceptors的檔案
                else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                }
                // 類名字首為com.alibaba.android.arouter.routes.ARouter$$Providers的檔案
                else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }
        }
	//...程式碼省略...
    } catch (Exception e) {
        throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
    }
}
複製程式碼

LogisticsCenter.init() 小結:

  1. 查詢指定包名(com.alibaba.android.arouter)下的檔案,並將之快取到SP中;
  2. 按照檔案的字首不同,將他們新增到路由對映表WarehousegroupsIndex、interceptorsIndex、providersIndex 中;

PackageUtils 類:用來管理應用的版本

// PackageUtils .class
// 判斷當前版本是否是新版本(第一次啟動)
public static boolean isNewVersion(Context context) {
    //...程式碼省略...
    SharedPreferences sp = context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE);
    // 從sp檔案中獲取之前儲存的VersionName和VersionCode
    if (!versionName.equals(sp.getString(LAST_VERSION_NAME, null)) || versionCode != sp.getInt(LAST_VERSION_CODE, -1)) {
        //...程式碼省略...
        return true;
    } else {
        return false;
    }
}

// 儲存當前版本的VersionName和VersionCode
public static void updateVersion(Context context) {
    if (TextUtils.isEmpty(NEW_VERSION_NAME) && NEW_VERSION_CODE != 0) {
        SharedPreferences sp = context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE);
        sp.edit()
          .putString(LAST_VERSION_NAME, NEW_VERSION_NAME)
          .putInt(LAST_VERSION_CODE, NEW_VERSION_CODE)
          .apply();
    }
}
複製程式碼

ClassUtils 類:獲取com.alibaba.android.arouter.routes包下 的檔案:

// ClassUtils.class
// 通過指定包名,掃描包下面包含的所有的ClassName
public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
    // Set型別的資料結構,防止重複
    final Set<String> classNames = new HashSet<>();

    List<String> paths = getSourcePaths(context);
    final CountDownLatch parserCtl = new CountDownLatch(paths.size());

    for (final String path : paths) {
        DefaultPoolExecutor.getInstance().execute(new Runnable() {
            @Override
            public void run() {
                DexFile dexfile = null;
                try {
                    if (path.endsWith(EXTRACTED_SUFFIX)) {
                        //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
                        dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                    } else {
                        dexfile = new DexFile(path);
                    }
		    // 從DexFile中獲取所有的檔案
                    Enumeration<String> dexEntries = dexfile.entries();
                    while (dexEntries.hasMoreElements()) {
                        String className = dexEntries.nextElement();
                        // 匹配所有包名路徑為com.alibaba.android.arouter.routes的檔案
                        if (className.startsWith(packageName)) {
                            classNames.add(className);
                        }
                    }
                } catch (Throwable ignore) {
                    Log.e("ARouter", "Scan map file in dex files made error.", ignore);
                } finally {
                    //...程式碼省略...
                }
            }
        });
    }
    //...程式碼省略...
    return classNames;// 返回所有包名路徑為com.alibaba.android.arouter.routes的檔案集合
}
複製程式碼

Warehouse類:路由檔案對映表

class Warehouse {
    // Cache route and metas
    // LogisticsCenter.init() 中被賦值
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    // LogisticsCenter.completion(postcard) 中被賦值
    static Map<String, RouteMeta> routes = new HashMap<>();

    // Cache provider
    // LogisticsCenter.completion(postcard) 中被賦值
    static Map<Class, IProvider> providers = new HashMap<>();
    // LogisticsCenter.init() 中被賦值
    static Map<String, RouteMeta> providersIndex = new HashMap<>();

    /* 
     * Cache interceptor
     * LogisticsCenter.init() 中被賦值;
     * 此處攔截器的儲存使用TreeMap,儲存的時候,已經對攔截器的優先順序進行排序
     */
    static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
    // InterceptorServiceImpl.init()中被賦值
    static List<IInterceptor> interceptors = new ArrayList<>();
}
複製程式碼

至此,_ARouter.init(application)的整個流程結束;
下面開始分析_ARouter.afterInit()程式碼:

// _ARouter.class
static void afterInit() {
    // Trigger interceptor init, use byName.
    interceptorService = (InterceptorService) 
		    ARouter.getInstance()
			.build("/arouter/service/interceptor") //生成一個Postcard物件
			//這個navigation()經過多次呼叫之後,
			//最終呼叫的是_ARouter.navigation(context, postcard, requestCode, navigationCallback)方法
			.navigation();
}
複製程式碼

build("/arouter/service/interceptor") 程式碼分析:

// _ARouter.class
protected Postcard build(String path) {
	// navigation(clazz)這種方式是屬於根據型別查詢,而build(path)是根據名稱進行查詢
	// 如果應用中沒有實現PathReplaceService這個介面,則pService=null
	PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
   if (null != pService) {
        // PathReplaceService可以對所有的路徑進行預處理,然後返回一個新的值
        path = pService.forString(path);
    }
    //extractGroup(path)是獲取分組名稱,預設為path中第一部分;
    return build(path, extractGroup(path));
}

// _ARouter.class
protected Postcard build(String path, String group) {
    PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
    if (null != pService) {
        path = pService.forString(path);
    }
    // 返回含有path和group組別的Postcard
    return new Postcard(path, group);
}

public interface PathReplaceService extends IProvider {
    String forString(String path);
    Uri forUri(Uri uri);
}
複製程式碼

navigation() 程式碼分析:

// _ARouter.class
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    try {
	// 重點:分析部分在下面
	// 1. 將IProvider對映到`Warehouse.providers`中;
	// 2. 將新增@Route註解的類對映到`Warehouse.routes`中
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        // ...程式碼省略...

        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);
    }
    // 忽略攔截器(在LogisticsCenter.completion(postcard)中,PROVIDER和FRAGMENT是忽略攔截器的)
    if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
    
	// interceptorService物件就是通過_ARouter.afterInit()例項化的;
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            @Override
            public void onContinue(Postcard postcard) {
                _navigation(context, postcard, requestCode, callback);
            }
            
            @Override
            public void onInterrupt(Throwable exception) {
                if (null != callback) {
                    callback.onInterrupt(postcard);
                }
            }
        });
    } else {
        return _navigation(context, postcard, requestCode, callback);
    }

    return null;
}

// _ARouter.class
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    // 這裡如果navigation()不傳入Activity作為context,則使用Application作為context
    final Context currentContext = null == context ? mContext : context;

    switch (postcard.getType()) {
        case ACTIVITY:
            // Build intent
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            // Activity的引數傳遞
            intent.putExtras(postcard.getExtras());

            // Set flags.
            int flags = postcard.getFlags();
            if (-1 != flags) {
                // 如果在跳轉時,設定了flags,且沒有設定Activity作為context,則下面的startActivity()方法會發生錯誤,因為缺少Activity的Task棧;
                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 {
	                // Activity跳轉
                        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:
	    //在LogisticsCenter.completion(postcard)中,我們將IProvider物件設定進postcard中,所以可以通過postcard.getProvider()獲取;
            return postcard.getProvider();
        case BOARDCAST:
        case CONTENT_PROVIDER:
        case FRAGMENT:
            Class fragmentMeta = postcard.getDestination();
            try {
                Object instance = fragmentMeta.getConstructor().newInstance();
                // Fragment的引數傳遞
                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;
}
複製程式碼

LogisticsCenter.completion() 小結:

  1. 將IProvider對映到Warehouse.providers中;
  2. 將新增@Route註解的類對映到Warehouse.routes
// LogisticsCenter.class
public synchronized static void completion(Postcard postcard) {
   if (null == postcard) {
        throw new NoRouteFoundException(TAG + "No postcard!");
    }
    
    /*
     * 根據path在Warehouse.routes對映表中查詢對應的RouteMeta;
     * 
     * 如果是第一次啟動,要查詢path = "/arouter/service/interceptor",
     * 而此時Warehouse.routes裡面是沒有元素的(在LogisticsCenter.init()中,
     * 只有`Warehouse`的`groupsIndex、interceptorsIndex、providersIndex` 有資料),因此routeMeta=null
     */
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {
	// 從Warehouse.groupsIndex中取出類名字首為com.alibaba.android.arouter.routes.ARouter$$Group$$group的檔案
        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 {
                IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                // 將我們新增@Route註解的類對映到Warehouse.routes中;
                iGroupInstance.loadInto(Warehouse.routes);
                // 將已經載入過的組從Warehouse.groupsIndex中移除,避免重複新增進Warehouse.routes
                Warehouse.groupsIndex.remove(postcard.getGroup());

            } catch (Exception e) {
                throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
            }
	    // 這個時候Warehouse.routes已經有值了,所以重新呼叫本方法執行else程式碼塊
            completion(postcard);   // Reload
        }
    } else {
        // 給postcard賦值
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());//設定routeMeta的Type
        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 auto inject.
                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();
                        // IProvider的子類進行初始化
                        provider.init(mContext);
                        // 將IProvider的子類新增進Warehouse.providers中
                        Warehouse.providers.put(providerMeta, provider);
                        instance = provider;
                    } catch (Exception e) {
                        throw new HandlerException("Init provider failed! " + e.getMessage());
                    }
                }
                // 將IProvider的實現類儲存在postcard中,因此可以從postcard獲取IProvider的例項物件;
                postcard.setProvider(instance);
                // greenChannel()會忽略攔截器
                postcard.greenChannel();    // Provider should skip all of interceptors
                break;
            case FRAGMENT:
	        // greenChannel()會忽略攔截器
                postcard.greenChannel();    // Fragment needn't interceptors
            default:
                break;
        }
    }
}
複製程式碼

到這裡,我們看到了Warehouse.providers、Warehouse.routes的賦值;那麼Warehouse.interceptors又是在哪裡賦值的呢?

InterceptorServiceImpl 的初始化:

// InterceptorService是IProvider的子類
public interface InterceptorService extends IProvider {
    void doInterceptions(Postcard postcard, InterceptorCallback callback);
}
/*
 * 因此InterceptorServiceImpl也是IProvider的子類;
 * 在LogisticsCenter.completion()中,有provider.init(context)的初始化;
 */
public class InterceptorServiceImpl implements InterceptorService {
	
    // ...程式碼省略...

    @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對映表中
                            Warehouse.interceptors.add(iInterceptor);
                        } catch (Exception ex) {
                            throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]");
                        }
                    }
		    // ...程式碼省略...
                }
            }
        });
    }
}
複製程式碼

至此,真個路由的初始化已經結束;


ARouter攔截器優先順序的問題


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

public class UniqueKeyTreeMap<K, V> extends TreeMap<K, V> {
    private String tipText;

    public UniqueKeyTreeMap(String exceptionText) {
        super();
        tipText = exceptionText;
    }

    @Override
    public V put(K key, V value) {
        /*
         * 將攔截器新增進UniqueKeyTreeMap時,以優先順序priority作為鍵,
         * 所以應用中攔截器的優先順序不能重複,否則會丟擲異常;
         */
        if (containsKey(key)) {
            throw new RuntimeException(String.format(tipText, key));
        } else {
            return super.put(key, value);
        }
    }
}
複製程式碼

小結:

將攔截器新增進UniqueKeyTreeMap時,以優先順序priority作為鍵,所以應用中攔截器的優先順序不能重複,否則會丟擲異常;

其他

Activity跳轉的原理:

  1. 從DexFile檔案中查詢到被@Route註解修飾的類;
  2. 儲存在Warehouse中;
  3. 在跳轉的時候,將所需查詢的Class賦值給Postcard物件;
  4. 最後使用的時候從postcard的Destination中獲取需跳轉的Class物件;
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
            // 這裡從postcard的Destination中獲取需跳轉的Class物件
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            intent.putExtras(postcard.getExtras());        
            
	    // ...程式碼省略...
	    ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
            
            //...程式碼省略...
    }
    return null;
}

// 另一種方式:通過類名直接跳轉
try {
    // GoodsQueryActivity.class為KitBundle元件中的頁面,當前元件為MainBundle元件;通過類名查詢到對應的Class,也能進行頁面跳轉;
    Class clazz = Class.forName("com.elson.kitbundle.ui.GoodsQueryActivity");
    Intent intent = new Intent(this, clazz);
    startActivity(intent);
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
複製程式碼

相關文章