Android组件化帮手——ARouter原理

1.编译

Arouter在编译器的主要工作就是生成中间件的代码,在gradle中加入Arouter的依赖后在编译的时候就会在对应的module下添加com.alibaba.android.arouter.routes目录,这个目录中主要存放Arouter生成的文件,比如

public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    //routes是一个map类型,key值就是path路径中的group,value值就是下面的那个文件
    routes.put("app", ARouter$$Group$$app.class);
  }
}

public class ARouter$$Group$$app implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    //key是通过@Route配置的路径,value是保存了启动的组件类型以及对应的文件
    atlas.put("/app/LoginActivity", RouteMeta.build(RouteType.ACTIVITY, LoginActivity.class, "/app/loginactivity", "app", new java.util.HashMap<String, Integer>(){{put("path", 8); }}, -1, -2147483648));
    atlas.put("/app/MainActivity", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/app/mainactivity", "app", null, -1, -2147483648));
    atlas.put("/app/SecondActivity", RouteMeta.build(RouteType.ACTIVITY, SecondActivity.class, "/app/secondactivity", "app", null, -1, -2147483648));
    atlas.put("/app/ThirdActivity", RouteMeta.build(RouteType.ACTIVITY, ThirdActivity.class, "/app/thirdactivity", "app", new java.util.HashMap<String, Integer>(){{put("name", 8); put("hero", 0); put("age", 3); }}, -1, 0));
  }
}

/**
 * For versions of 'compiler' greater than 1.0.7
 *
 * @param type        type				类型(Activity、Service等)
 * @param destination destination		具体类(MainActivity.class)
 * @param path        path				路径(app/MainActivity)
 * @param group       group				app
 * @param paramsType  paramsType		null
 * @param priority    priority
 * @param extra       extra
 * @return this
 */
public static RouteMeta build(RouteType type, Class<?> destination, String path, String group, Map<String, Integer> paramsType, int priority, int extra) {
    return new RouteMeta(type, null, destination, path, group, paramsType, priority, extra);
}

因为编译生成的类的前缀都是 Arouter,所以moudle下的完整包名就是com.alibaba.android.arouter.routes.Arouter

2.运行

  • 运行时主要工作就是注入,从Application中的Arouter初始化看起
ARouter.init(this);

  • init最终是调用到了这里
//_Arouter#init
protected static synchronized boolean init(Application application) {
    mContext = application;
    LogisticsCenter.init(mContext, executor);
    logger.info(Consts.TAG, "ARouter init success!");
    hasInit = true;
    return true;
}

  • 初始化的init方法中又调用了LogisticsCenter
//LogisticsCenter#init
//第一个参数context是Application它来自Arouter.init
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或者是否是新版本的判断,如果结果为true就需要重新获取包下面所有的class
            if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                // 这里会获取com.alibaba.android.arouter.routes目录下的所有class文件
                routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                if (!routerMap.isEmpty()) {
                    //获取到class后将其存储到SharedPreferences中
                    context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                }

                //router保存完成时还要保存最新的版本
                PackageUtils.updateVersion(context);    
            } else {
                logger.info(TAG, "Load router map from cache.");
                //不是新的版本或者没有debug时可直接从SharedPreference中获取所有的class
                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();

            //遍历获取到的class文件名,然后对不同类名的前缀做对比后通过loadInto注入到Warehouse
            for (String className : routerMap) {
                //com.alibaba.android.arouter.routes.Arouter$$Root
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    //com.alibaba.android.arouter.routes.Arouter$$Interceptors
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    //com.alibaba.android.arouter.routes.Arouter$$Provider
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }
        }

        logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");

        if (Warehouse.groupsIndex.size() == 0) {
            logger.error(TAG, "No mapping files were found, check your configuration please!");
        }

        if (ARouter.debuggable()) {
            logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
        }
    } catch (Exception e) {
        throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
    }
}

  • LogisticsCenter方法的流程如下:

    • 判断是否是新版本或者是debug模式
    • 如果判断的条件成立就调用ClassUtils.getFileNameByPackageName方法重新获取className然后加入到SharedPreference中缓存
    • 如果条件不成立就直接从SharedPreference获取
    • 遍历获取到的ClassName,然后根据不同类名的前缀做对比后通过loadinto方法注入到Warehouse
//ClassUtils#getFileNameByPackageName
public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
    final Set<String> classNames = new HashSet<>();
	
    //获取所有dex文件的路径
    List<String> paths = getSourcePaths(context);
    final CountDownLatch parserCtl = new CountDownLatch(paths.size());
	
    //遍历获取到的dex文件的路径
    for (final String path : paths) {
        DefaultPoolExecutor.getInstance().execute(new Runnable() {
            @Override
            public void run() {
                DexFile dexfile = null;

                try {
                    if (path.endsWith(EXTRACTED_SUFFIX)) {
                        //如果是.zip结尾的路径就用loadDex加载,如果创建新的DexFile就会报出权限错误
                        dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                    } else {
                        //创建新的DexFile
                        dexfile = new DexFile(path);
                    }

                    Enumeration<String> dexEntries = dexfile.entries();
                    //遍历dexFile下面的元素
                    while (dexEntries.hasMoreElements()) {
                        String className = dexEntries.nextElement();
                        if (className.startsWith(packageName)) {
                            //将开头为"com.alibaba.android.arouter.routes"的className添加到classNames集合中
                            classNames.add(className);
                        }
                    }
                } catch (Throwable ignore) {
                    Log.e("ARouter", "Scan map file in dex files made error.", ignore);
                } finally {
                    if (null != dexfile) {
                        try {
                            dexfile.close();
                        } catch (Throwable ignore) {
                        }
                    }

                    parserCtl.countDown();
                }
            }
        });
    }

    parserCtl.await();

    Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
    return classNames;
}

  • 获取classNames的流程如下:

    • 获取dexFile路径
    • 遍历dexFile路径以及下面的元素
    • 如果是以com.alibaba.android.arouter.routes开头则添加到ClassNames集合中
//ClassUtils#getSourcePaths
public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
    ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
    File sourceApk = new File(applicationInfo.sourceDir);

    List<String> sourcePaths = new ArrayList<>();
    //添加默认的apk路径
    sourcePaths.add(applicationInfo.sourceDir); 

    //the prefix of extracted file, ie: test.classes
    String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;

    //如果VM已经支持了MultiDex,就不要去Secondary Folder加载 Classesx.zip了,那里已经么有了
    //通过是否存在sp中的multidex.version是不准确的,因为从低版本升级上来的用户,是包含这个sp配置的
    //如果已经支持了MutilDex
    if (!isVMMultidexCapable()) {
        //获取所有dex文件的数量
        int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);
        File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
		
        //遍历获取路径
        for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
            //for each dex file, ie: test.classes2.zip, test.classes3.zip...
            String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
            File extractedFile = new File(dexDir, fileName);
            if (extractedFile.isFile()) {
                //添加路径
                sourcePaths.add(extractedFile.getAbsolutePath());
                //we ignore the verify zip part
            } else {
                throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
            }
        }
    }

    if (ARouter.debuggable()) { // Search instant run support only debuggable
        sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));
    }
    return sourcePaths;
}

  • 获取dex文件路径的流程如下:

    • 添加apk默认路径
    • 是否支持MutilDex
      • 如果支持则先获取dex文件的总数
      • 然后开始遍历获取路径
      • 添加到sourcePaths集合中
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();
    }
}

  • Warehouse中包含三种缓存类型

  • Arouter在运行时的完整工作流程如下:

    • Application中初始化
    • 初始化时中先调用ClassUtils.getFileNameByPackageNam()获取所有com.alibaba.android.arouter.route目录下的dex文件路径
    • 遍历dex文件路径获取所有的class文件的完整类名
    • 遍历获取到的class,获取指定的前缀,调用loadinto方法根据不同前缀注入到Warehouse对应的成员变量中
    • 这其中就包含Arouter在编译时生成的com.alibaba.android.arouter.routes.Arouter$$Root.Arouter$$Root$$app

3.调用

  • 调用时的工作主要是获取,先看源码
 ARouter.getInstance().build(ActivityConsts.ACTIVITY_URL_THIRD)
                        .navigation(this, new CustomNavigationCallback()));

  • 直接从build看起
//_Arouter#build
public Postcard build(String path) {
    return _ARouter.getInstance().build(path);
}

//_Arouter#build
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) {
            path = pService.forString(path);
        }
        //extractGroup获取到group,就是代码中的【app】
        return build(path, extractGroup(path));
    }
}

//_Arouter#extractGroup
private String extractGroup(String path) {
    //校验path格式是否正确
    if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
        throw new HandlerException(Consts.TAG + "Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!");
    }

    try {
        //通过截取path获取到group
        String defaultGroup = path.substring(1, path.indexOf("/", 1));
        if (TextUtils.isEmpty(defaultGroup)) {
            throw new HandlerException(Consts.TAG + "Extract the default group failed! There's nothing between 2 '/'!");
        } else {
            return defaultGroup;
        }
    } catch (Exception e) {
        logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage());
        return null;
    }
}

//_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);
        }
        //创建一个Postcard
        return new Postcard(path, group);
    }
}

public Postcard(String path, String group) {
    this(path, group, null, null);
}

public Postcard(String path, String group, Uri uri, Bundle bundle) {
    //保存路径
    setPath(path);
    //保存group,就是【app】
    setGroup(group);
    setUri(uri);
    //创建了个Bundle()
    this.mBundle = (null == bundle ? new Bundle() : bundle);
}

  • Arouter.getInstance().build()的流程用一句话总结就是,创建了一个Postcard保存了pathgroup
  • navigation的源码如下
public Object navigation(Context context, NavigationCallback callback) {
    return ARouter.getInstance().navigation(context, this, -1, callback);
}

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    try {
        //核心1
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        logger.warning(Consts.TAG, ex.getMessage());

        if (debuggable()) { // Show friendly tips for user.
            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);
    }

    //判断是否调用拦截器
    //要在异步线程中调用,否则可能会因为拦截器耗时过长出现ANR
    if (!postcard.isGreenChannel()) {  
        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);
                }

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

    return null;
}

  • 核心1
//LogisticsCenter#completion
public synchronized static void completion(Postcard postcard) {
    if (null == postcard) {
        throw new NoRouteFoundException(TAG + "No postcard!");
    }
	
    //先从Warehouse.routes获取RouteMeta,上面的代码中只用到了Warehouse.groupsIndex,所以肯定是null
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {    
        //这里通过group获取具体的class,而这个class就是前面编译时缓存进去的
        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 {
            //加载路由并缓存到内存中,然后将元数据删除,这里的元数据指的是group
            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();
                //通过反射将其存入到Warehouse中
                iGroupInstance.loadInto(Warehouse.routes);
                //移除group,group的意义就是用来获取route的
                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);   
        }
    } 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 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();
                        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;
        }
    }
}

  • completion方法运行了两次:

    • 第一次从Warehouse.groupIndex中取出之前保存的数据,然后再通过loadinto把相关数据注入到Warehouse.routes
    • 第二次就是给Postcard中的属性赋值,type、Destination
  • 经过completion方法后Warehouse.route中便有了数据,并且参数postcard也有了typeDestinatio,就该执行第二个核心方法了

//_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:
            // Activity类型就是创建了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);
            }

            // 切换到UI线程去启动Activity,Activity启动了
            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;
}

  • _navigation方法中首先根据postcard.type获取到具体类型,然后根据不同类型做相关处理,代码中我们使用的是Activity因此从源码中可知在Arouter中Activity的开启也是基于Intent完成的
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值