【Android】ARouter源码解析

本篇文章主要讲解了 ARouter 框架的源码分析,包括其初始化过程、核心方法等。

初始化

在使用ARouter的时候我们都会先进行初始化:

java 复制代码
ARouter.init(this);

我们看下 init() 源码:

java 复制代码
public static void init(Application application) {
    // 检查 ARouter 是否已经初始化,避免重复初始化
    if (!hasInit) {
        // 获取 logger,并记录初始化开始的日志
        logger = _ARouter.logger;
        _ARouter.logger.info(Consts.TAG, "ARouter init start.");
        
        // 执行 ARouter 的初始化方法,并将初始化结果赋值给 hasInit
        hasInit = _ARouter.init(application);

        // 如果初始化成功,执行后续的初始化步骤
        if (hasInit) {
            _ARouter.afterInit();
        }

        // 记录初始化完成的日志
        _ARouter.logger.info(Consts.TAG, "ARouter init over.");
    }
}

真正调用的还是里面的 _ARouter.init()

java 复制代码
protected static synchronized boolean init(Application application) {
    // 将传入的 Application 实例赋值给 mContext
    mContext = application;
    // 初始化 LogisticsCenter,通常是用来管理路由路径和目标组件的调度
    LogisticsCenter.init(mContext, executor);
    // 记录初始化成功的日志
    logger.info(Consts.TAG, "ARouter init success!");
    // 设置初始化标志,标记 ARouter 已初始化
    hasInit = true;
    // 创建一个 Handler 用于在主线程中处理消息
    mHandler = new Handler(Looper.getMainLooper());
    // 返回初始化成功
    return true;
}

最终调用了LogisticsCenter的 init() 方法:

java 复制代码
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    // 保存上下文和线程池实例
    mContext = context;
    executor = tpe;

    try {
        // 记录初始化开始时间
        long startInit = System.currentTimeMillis();
        
        // 加载路由映射
        loadRouterMap();

        // 判断是否使用插件进行自动注册
        if (registerByPlugin) {
            logger.info(TAG, "Load router map by arouter-auto-register plugin.");
        } else {
            Set<String> routerMap;

            // 如果是调试模式或者是新版本安装,则重新构建路由映射
            if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                
                // 获取路由类文件名(这些类是由 arouter-compiler 插件生成的)
                routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                
                // 如果有新的路由映射,保存到 SharedPreferences 中
                if (!routerMap.isEmpty()) {
                    context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE)
                           .edit()
                           .putStringSet(AROUTER_SP_KEY_MAP, routerMap)
                           .apply();
                }

                // 更新应用版本信息
                PackageUtils.updateVersion(context);
            } 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) {
                // 判断是否是根元素类,若是则加载到 groupsIndex
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                }
                // 判断是否是拦截器类,若是则加载到 interceptorsIndex
                else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                }
                // 判断是否是提供者类,若是则加载到 providersIndex
                else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    ((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) {
        // 捕获异常并抛出自定义的 HandlerException
        throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
    }
}

上面方法主要实现了:

  • 通过loadRouterMap方法判断是不是通过arouter-register自动加载路由表,如果是通过自动加载的则registerByPlugin=true,这里我们先不管通过arouter-register自动加载的方式
  • 紧接着通过ClassUtils.getFileNameByPackageName(此处用到了线程池、CountDownLatch面试高频考点)获取到apk中前缀为com.alibaba.android.arouter.routes的类,这里面主要是通过判断是不是支持MultiDex,如果不支持MultiDex,扫描所有的dex文件,然后压缩成zip文件,然后通过DexFile.loadDex转化成DexFile对象,如果支持MultiDex,直接new DexFile,然后循环DexFile拿里面的class文件,然后过滤出com.alibaba.android.arouter.routes前缀的class并返回。
  • 拿到了需要的class类后,放到sp里面,方便下次不去扫描apk拿class,更新版本号
  • 将com.alibaba.android.arouter.routes.ARouter$$Root前缀的class类放到Warehouse.groupsIndex中
  • 将com.alibaba.android.arouter.routes.ARouter$$Interceptors前缀的class类放到Warehouse.interceptorsIndex中
  • 将com.alibaba.android.arouter.routes.ARouter$$Providers前缀的class类放到Warehouse.providersIndex中

_ARouter.afterInit

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

ARouter.build

下面我们来看ARouter的build方法:

java 复制代码
public Postcard build(String path) {
        return _ARouter.getInstance().build(path);
    }

返回的是Postcard类型的对象,我们看一下里面的 _ARouter.build()

java 复制代码
protected Postcard build(String path, String group, Boolean afterReplace) {
    // 检查路径(path)和分组(group)是否为空,如果为空则抛出异常
    if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        // 如果 afterReplace 为 false,则进行路径替换
        if (!afterReplace) {
            // 获取 PathReplaceService 服务实例,该服务用于路径的替换操作
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            
            // 如果 PathReplaceService 服务实例不为空,则执行路径替换操作
            if (null != pService) {
                path = pService.forString(path);  // 调用替换方法替换路径
            }
        }

        // 构建并返回一个 Postcard 对象,使用处理后的 path 和 group
        return new Postcard(path, group);
    }
}
  • build 方法 :根据传入的路径 path 和分组 group 创建一个 Postcard 对象。
  • 路径替换 :如果 afterReplacefalse,则调用 PathReplaceService 服务对路径进行替换。
  • 异常处理 :如果 pathgroup 为空,抛出自定义的 HandlerException 异常。
  1. afterReplacefalse :如果 afterReplace 参数为 false,则会进行路径替换。这意味着在构建 Postcard 对象之前,会通过 PathReplaceService 服务来替换路径。这个服务允许开发者自定义路径替换逻辑,例如,根据配置或环境变量动态调整路径。这样做的好处是可以灵活地适应不同的部署环境或业务需求,而无需硬编码路径。
  2. afterReplacetrue :如果 afterReplace 参数为 true,则不会进行路径替换。这意味着直接使用原始的路径来构建 Postcard 对象。这通常用于那些不需要动态路径替换的场景,或者路径已经在其他地方被处理过,不需要再次替换。

示例代码

假设你实现了一个 PathReplaceService,它的作用是根据不同的环境替换路径中的某些部分:

java 复制代码
public class PathReplaceServiceImpl implements PathReplaceService {
    @Override
    public String forString(String path) {
        // 假设在生产环境中替换 "/test/" 为 "/prod/"
        if (BuildConfig.DEBUG) {
            return path;  // 开发环境不做替换
        } else {
            return path.replace("/test/", "/prod/");  // 生产环境进行路径替换
        }
    }
}

然后在调用 ARouter 时使用这个服务:

java 复制代码
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
    path = pService.forString(path);  // 使用服务进行路径替换
}

Postcard类

Postcard 类是 ARouter 框架中的一个核心组件,它表示一个路由的实体,包含了路由的所有信息,比如目标路径、分组、传递的数据、动画效果、标志位等。

java 复制代码
public final class Postcard extends RouteMeta {
    // Base
    private Uri uri;
    private Object tag;             // A tag prepare for some thing wrong. inner params, DO NOT USE!
    private Bundle mBundle;         // Data to transform
    private int flags = 0;         // 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;
    private Context context;        // May application or activity, check instance type before use it.
    private String action;

Postcard 类继承自 RouteMeta,是路由的实际载体,包含导航需要的额外信息。

RouteMeta:

java 复制代码
public class RouteMeta {
    // 路由类型,指示目标是 Activity、Fragment 还是 Provider 等
    private RouteType type;  

    // 原始类型元素,表示目标路由的原始类型(如 Activity 或 Fragment)
    private Element rawType;  

    // 目标页面的 Class 类型,表示路由请求的具体目标类
    private Class<?> destination;  

    // 路由路径,唯一标识一个路由,如 "/app/MainActivity"
    private String path;  

    // 路由分组,常用于模块化开发,避免路径冲突
    private String group;  

    // 路由的优先级,用于在多个路由匹配时,确定优先使用哪个路由
    private int priority;  

    // 额外的信息,通常用于存放一些配置信息
    private int extra;  

    // 路由参数类型的映射,存储参数名与其类型的对应关系
    private Map<String, Integer> paramsType;  

    // 路由的名称,通常用于调试或日志中,便于标识路由
    private String name;  

    // 注入的配置,存储需要自动注入的字段信息
    private Map<String, Autowired> injectConfig;  
}

PostCard.navigation

navigation有很多重载的方法,最终都会走_Arouter.navigation

java 复制代码
/**
 * 导航到 postcard 中路径指定的路由。
 *
 * @param context 上下文,通常是 Activity 等。
 */
public Object navigation(Context context) {
    return navigation(context, null);
}

/**
 * 导航到 postcard 中路径指定的路由。
 *
 * @param context 上下文,通常是 Activity 等。
 * @param callback 导航回调接口。
 */
public Object navigation(Context context, NavigationCallback callback) {
    return ARouter.getInstance().navigation(context, this, -1, callback);
}

/**
 * 导航到 postcard 中路径指定的路由。
 *
 * @param mContext 上下文,通常是 Activity 等。
 * @param requestCode `startActivityForResult` 的请求码。
 */
public void navigation(Activity mContext, int requestCode) {
    navigation(mContext, requestCode, null);
}

/**
 * 导航到 postcard 中路径指定的路由。
 *
 * @param mContext 上下文,通常是 Activity 等。
 * @param requestCode `startActivityForResult` 的请求码。
 * @param callback 导航回调接口。
 */
public void navigation(Activity mContext, int requestCode, NavigationCallback callback) {
    ARouter.getInstance().navigation(mContext, this, requestCode, callback);
}

这几个方法会调用ARouter.navigation:

java 复制代码
 public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
        return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
    }

接着调用_Arouter.navigation:

java 复制代码
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    // 获取预处理服务实例
    PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);

    // 如果预处理服务存在并且返回的结果为 false,则导航失败,取消导航
    if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
        // 预处理失败,导航被取消
        return null;
    }

    // 设置 postcard 的上下文,如果传入的 context 为 null,则使用 mContext
    postcard.setContext(null == context ? mContext : context);

    try {
        // 完成路由的物流处理
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        logger.warning(Consts.TAG, ex.getMessage());

        // 如果启用了调试模式,则显示友好的提示信息给用户
        if (debuggable()) {
            runInMainThread(new Runnable() {
                @Override
                public void run() {
                    // 弹出提示框,告知没有找到匹配的路由
                    Toast.makeText(mContext, "没有找到匹配的路由!\n" +
                            " 路径 = [" + postcard.getPath() + "]\n" +
                            " 分组 = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
                }
            });
        }

        // 如果有回调函数,调用 onLost 方法
        if (null != callback) {
            callback.onLost(postcard);
        } else {
            // 如果没有回调,则使用全局的降级服务
            DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
            if (null != degradeService) {
                degradeService.onLost(context, postcard);
            }
        }

        return null;
    }

    // 如果有回调函数,调用 onFound 方法
    if (null != callback) {
        callback.onFound(postcard);
    }

    // 如果不是绿色通道,则执行拦截器,可能会导致 ANR 问题
    if (!postcard.isGreenChannel()) {
        // 执行拦截器
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            /**
             * 继续处理
             *
             * @param postcard 路由元数据
             */
            @Override
            public void onContinue(Postcard postcard) {
                // 调用实际的导航方法
                _navigation(postcard, requestCode, callback);
            }

            /**
             * 中断处理,管道将在此方法被调用时销毁。
             *
             * @param exception 中断原因
             */
            @Override
            public void onInterrupt(Throwable exception) {
                // 如果有回调,调用 onInterrupt 方法
                if (null != callback) {
                    callback.onInterrupt(postcard);
                }

                // 记录导航失败信息
                logger.info(Consts.TAG, "导航失败,因拦截器中止 : " + exception.getMessage());
            }
        });
    } else {
        // 如果是绿色通道,直接执行导航
        return _navigation(postcard, requestCode, callback);
    }

    return null;
}

最后会执行_navigation:

java 复制代码
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    // 获取当前的上下文
    final Context currentContext = postcard.getContext();

    // 根据 Postcard 的类型执行不同的导航操作
    switch (postcard.getType()) {
        case ACTIVITY:
            // 如果是 Activity 类型,构建 Intent 对象
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            intent.putExtras(postcard.getExtras());  // 设置 extras 数据

            // 设置 flags
            int flags = postcard.getFlags();
            if (0 != flags) {
                intent.setFlags(flags);  // 如果有 flags,设置到 intent 中
            }

            // 如果当前上下文不是 Activity,需要设置 FLAG_ACTIVITY_NEW_TASK
            if (!(currentContext instanceof Activity)) {
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }

            // 设置 Action
            String action = postcard.getAction();
            if (!TextUtils.isEmpty(action)) {
                intent.setAction(action);  // 如果有 action,设置到 intent 中
            }

            // 在主线程中启动 Activity
            runInMainThread(new Runnable() {
                @Override
                public void run() {
                    startActivity(requestCode, currentContext, intent, postcard, callback);
                }
            });

            break;

        case PROVIDER:
            // 如果是 Provider 类型,直接返回对应的 Provider
            return postcard.getProvider();

        case BOARDCAST:
        case CONTENT_PROVIDER:
        case FRAGMENT:
            // 如果是 Broadcast、ContentProvider 或 Fragment 类型
            Class<?> fragmentMeta = postcard.getDestination();
            try {
                // 通过反射创建 Fragment 实例
                Object instance = fragmentMeta.getConstructor().newInstance();
                if (instance instanceof Fragment) {
                    ((Fragment) instance).setArguments(postcard.getExtras());  // 设置 extras 数据
                } else if (instance instanceof android.support.v4.app.Fragment) {
                    ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                }

                // 返回创建的 Fragment 实例
                return instance;
            } catch (Exception ex) {
                logger.error(Consts.TAG, "获取 Fragment 实例出错, " + TextUtils.formatStackTrace(ex.getStackTrace()));
            }

        case METHOD:
        case SERVICE:
        default:
            // 如果是 METHOD 或 SERVICE 类型,或者未知类型,返回 null
            return null;
    }

    return null;
}

参考:阿里ARouter全面全面全面解析(使用介绍+源码分析+设计思路)_arouter init-CSDN博客

已经到底啦!!

相关推荐
空の鱼10 分钟前
java开发,IDEA转战VSCODE配置(mac)
java·vscode
水瓶丫头站住34 分钟前
安卓APP如何适配不同的手机分辨率
android·智能手机
P7进阶路1 小时前
Tomcat异常日志中文乱码怎么解决
java·tomcat·firefox
xvch1 小时前
Kotlin 2.1.0 入门教程(五)
android·kotlin
小丁爱养花2 小时前
Spring MVC:HTTP 请求的参数传递2.0
java·后端·spring
CodeClimb2 小时前
【华为OD-E卷 - 第k个排列 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
等一场春雨2 小时前
Java设计模式 九 桥接模式 (Bridge Pattern)
java·设计模式·桥接模式
带刺的坐椅2 小时前
[Java] Solon 框架的三大核心组件之一插件扩展体系
java·ioc·solon·plugin·aop·handler
不惑_3 小时前
深度学习 · 手撕 DeepLearning4J ,用Java实现手写数字识别 (附UI效果展示)
java·深度学习·ui
费曼乐园3 小时前
Kafka中bin目录下面kafka-run-class.sh脚本中的JAVA_HOME
java·kafka