ARouter 基本原理

ARouter 基本原理

使用目的

使用各大开源路由库的目的,比如 ARouter、TheRouter 等,都是为了组件化、模块化准备的。各大模块之间的解耦,互不依赖又能调用对方提供的服务能力,来完成功能的开发。为什么解耦呢?是为了开发方便以及扩展性和组合性更好。

要想实现一个路由,需要满足最最核心的两点功能:

  • 能建立路径(path)与目标类(Activity/Fragment等)的映射关系
  • 模块间无需类依赖即可跳转

自然,我们就想到用一个全局的地方来保存页面数据信息,于是可以设计出这样的结构:公共模块 lib_common 中有个 RouterMap 来保存页面信息

kotlin 复制代码
object RouterMap {

    const val TAG = "RouterMap"
    
    private val map = mutableMapOf<String, Class<*>>()

    fun addRouter(path: String, clazz: Class<*>) {
        map[path] = clazz
    }

    fun startActivity(context: Context, path: String) {
        val clazz = map[path]
        if (clazz == null) {
            showToast(context, "not found activity, path: $path")
            Log.e(TAG, "not found activity, path: $path")
            return
        }
        try {
            if (Activity::class.java.isAssignableFrom(clazz)) {
                val intent = Intent(context, clazz)
                intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                context.startActivity(intent)
            } else {
                Log.e(TAG, "startActivity: path is not activity", )
            }
        } catch (e: Exception) {
            Log.e(TAG, "start activity error", e )
            showToast(context, "start activity error")
        }
    }

    private fun showToast(context: Context, log: String) {
        Toast.makeText(context , log , Toast.LENGTH_SHORT).show()
    }
}

其他模块引用 lib_common 模块,然后在模块初始化时调用 addRouter 来注册。这一步可以使用 Startup / Provider

kotlin 复制代码
class UserInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        initRouter()
    }

    private fun initRouter() {
        RouterMap.addRouter("user/user", UserActivity::class.java)
        RouterMap.addRouter("user/detail", UserDetailActivity::class.java)
    }

    override fun dependencies(): List<Class<out Initializer<*>?>?> {
        return emptyList()
    }
}

这样其他地方就能跳转

java 复制代码
RouterMap.startActivity(context, "user/user")

当然,聪明的你想到手动注册太麻烦、并且容易遗忘。于是想到可以通过 AOP / ASM 通过注解去自动生成。于是 ARouter 中有 @Router@Intercept@Autowired 注解来用于表明页面或者自动注入。在编译器生成对应代码。

Warehouse 仓库

ARouter 中保存路由信息的类是 Warehouse.java 比较简单,几个 map 对象和 list,作用是保存所有的路由页面、服务和拦截器。@Router@Intercept 修饰的类都在里面。

java 复制代码
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();
    }
}

当然保存的信息不止 Class 这一点信息,于是有了 RouteMeta

RouteMeta 路由元数据

RouteMeta 顾名思义路由元数据,用来保存路由信息

java 复制代码
public class RouteMeta {
    private RouteType type;         // 路由类型
    private Element rawType;        // 路由的原始类型
    private Class<?> destination;   // 目的地
    private String path;            // 路线路径
    private String group;           // Group of route
    private int priority = -1;      // 优先级,数字越小,优先级越高
    private int extra;              // Extra data
    private Map<String, Integer> paramsType;  // Param type
    private String name;

    private Map<String, Autowired> injectConfig;  // Cache inject config.

    public RouteMeta() {
    }

    public RouteMeta build(xxx){}
}

初始化

那么是什么时候加载(addRouter)的呢?当然是 ARouter.init(application), ARouter 类是对外暴露的调用类,点进 ARouter.init() 可以看到内部的实现都是由 _ARouter、LogisticsCenter 实现。

java 复制代码
// LogisticsCenter.java
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;

        try {
            long startInit = System.currentTimeMillis();
            //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.
                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.
                    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>()));
                }

                // 遍历生成的文件路径,根据不同类型存储
                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);
                    }
                }
            }

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

从中可以看出几点:

  • ClassUtils.getFileNameByPackageName();通过扫描生成的类去获取路由表
  • 根据是否是 debugger 或新版本来更新路由表,否则从 sp 缓存中获取,避免每次打开应用都做扫描耗时操作
  • 拿到路由表 routerMap 后根据类型添加到 (loadInfo) 仓库 Warehouse

在初始化完成后,进行拦截器的初始化。看另一篇文章。

build().navigation() 跳转页面

初始化完成后,跳转页面前一般会调用 build(path) ,并且后面可能会附带参数 withXXX(param) 。build() 是创建了一个 Postcard 对象,存储对应的参数。 可以看出 Postcard 继承于 RouterMeta ,并且扩展了很多属性。

java 复制代码
public final class Postcard extends RouteMeta {
    // Base
    private Uri uri;
    private Object tag;             // 标签,拦截器出错的错误信息。 后面拦截器流程中使用到
    private Bundle mBundle;         // 数据传递
    private int flags = 0;         // 就是启动 Activity 的 Intent.FLAG
    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;

    // Animation
    private Bundle optionsCompat;    // The transition animation of activity
    private int enterAnim = -1;
    private int exitAnim = -1;
}
java 复制代码
navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback)
  
navigation(Class<? extends T> service)

核心跳转方法

ARouter的跳转核心在 _ARouter.navigation() 方法中:

java 复制代码
protected Object navigation(final Context context, final Postcard postcard, 
                           final int requestCode, final NavigationCallback callback) {
    try {
        // 1. 准备跳转 - 补全Postcard信息
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        // 路由未找到处理
        if (callback != null) {
            callback.onLost(postcard);
        }
        return null;
    }
    
    // 2. 跳转回调通知
    if (callback != null) {
        callback.onFound(postcard);
    }
    
    // 3. 绿色通道检查(是否跳过拦截器)
    if (!postcard.isGreenChannel()) {
        // 执行拦截器逻辑
        if (!interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            @Override
            public void onContinue(Postcard postcard) {
                // 拦截器通过,执行实际跳转
                _navigation(context, postcard, requestCode, callback);
            }
            
            @Override
            public void onInterrupt(Throwable exception) {
                // 被拦截器中断
                if (callback != null) {
                    callback.onInterrupt(postcard);
                }
            }
        })) {
            return null;
        }
    } else {
        // 绿色通道直接跳转
        return _navigation(context, postcard, requestCode, callback);
    }
    
    return null;
}

LogisticsCenter.completion() - 路由信息补全

这个方法负责根据path找到对应的RouteMeta并填充Postcard:

java 复制代码
public synchronized static void completion(Postcard postcard) {
    // 1. 根据path从缓存中获取路由信息
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {
        // 2. 如果缓存中没有,尝试动态加载路由组
        Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());
        if (null == groupMeta) {
            throw new NoRouteFoundException("未找到对应路由: " + postcard.getPath());
        }
        
        // 3. 实例化路由组并加载到缓存
        IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
        iGroupInstance.loadInto(Warehouse.routes);
        Warehouse.groupsIndex.remove(postcard.getGroup());
        
        // 4. 重新尝试获取
        completion(postcard);
    } else {
        // 5. 填充Postcard信息
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());
        
        // 6. 根据不同类型处理
        switch (routeMeta.getType()) {
            case PROVIDER:
                // 如果是服务,创建Provider实例
                Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                IProvider instance = Warehouse.providers.get(providerMeta);
                if (null == instance) {
                    IProvider provider = providerMeta.getConstructor().newInstance();
                    provider.init(mContext);
                    Warehouse.providers.put(providerMeta, provider);
                    instance = provider;
                }
                postcard.setProvider(instance);
                postcard.greenChannel(); // Provider跳转走绿色通道
                break;
            case FRAGMENT:
                postcard.greenChannel(); // Fragment也走绿色通道
                break;
        }
    }
}

_navigation() - 实际跳转逻辑

java 复制代码
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:
            // 1. 创建Intent
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            intent.putExtras(postcard.getExtras());
            
            // 2. 设置Flags
            int flags = postcard.getFlags();
            if (-1 != flags) {
                intent.setFlags(flags);
            } else if (!(currentContext instanceof Activity)) {
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }
            
            // 3. 执行跳转
            if (requestCode >= 0) {
                if (currentContext instanceof Activity) {
                    ((Activity) currentContext).startActivityForResult(intent, requestCode, postcard.getOptionsBundle());
                } else {
                    Log.w(TAG, "非Activity环境无法使用startActivityForResult");
                }
            } else {
                currentContext.startActivity(intent, postcard.getOptionsBundle());
            }
            
            // 4. 动画设置
            if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) 
                && currentContext instanceof Activity) {
                ((Activity) currentContext).overridePendingTransition(
                    postcard.getEnterAnim(), postcard.getExitAnim());
            }
            
            // 5. 跳转完成回调
            if (callback != null) {
                callback.onArrival(postcard);
            }
            break;
            
        case PROVIDER:
            // 返回Provider实例
            return postcard.getProvider();
            
        case FRAGMENT:
            try {
                // 返回Fragment实例
                Fragment fragment = postcard.getDestination().getConstructor().newInstance();
                Bundle args = postcard.getExtras();
                if (null != args) {
                    fragment.setArguments(args);
                }
                return fragment;
            } catch (Exception ex) {
                logger.error(Consts.TAG, "获取Fragment实例失败", ex);
            }
            break;
    }
    
    return null;
}

拦截器执行流程

拦截器的执行是一个递归过程:

java 复制代码
// InterceptorServiceImpl.java
    @Override
    public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
        if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
            // 检查是否初始化
            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 {
                        _execute(0, interceptorCounter, postcard);
                        interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                        if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn't return anythings.
                            callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                        } else if (null != postcard.getTag()) {    // Maybe some exception in the tag.
                            callback.onInterrupt((Throwable) postcard.getTag());
                        } 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 _execute(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();
                    _execute(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 execute over with fatal exception.

                    postcard.setTag(null == exception ? new HandlerException("No message.") : exception);    // save the exception message for backup.
                    counter.cancel();
                }
            });
        }
    }

跳转流程总结

整个跳转流程可以概括为以下几个关键步骤:

  1. Build阶段:创建Postcard,设置基本参数
  2. Completion阶段:根据path查找路由信息,补全Postcard
  3. 拦截检查:非绿色通道时按顺序执行拦截器
  4. 实际跳转:根据目标类型(Activity/Provider/Fragment)执行相应跳转逻辑
  5. 回调通知 :在整个过程中通过 NavigationCallback 通知调用方状态变化

这种设计实现了路由查找与实际跳转的解耦,使得框架具有很好的扩展性,可以方便地添加各种拦截逻辑和路由策略。

相关推荐
沐怡旸4 小时前
【底层机制】Handler/Looper 实现线程切换的技术细节
android·面试
轻口味5 小时前
Rokid Glasses 移动端控制应用开发初体验-助力业务创新
android·操作系统·app
老友@5 小时前
集中式架构、分布式架构与微服务架构全面解析
分布式·微服务·架构·系统架构
前端世界6 小时前
从0到1实现鸿蒙智能设备状态监控:轻量级架构、分布式同步与MQTT实战全解析
分布式·架构·harmonyos
萤丰信息6 小时前
从超级大脑到智能毛细血管:四大技术重构智慧园区生态版图
java·人工智能·科技·重构·架构·智慧园区
帅得不敢出门6 小时前
Android监听第三方播放获取音乐信息及包名
android·java
2503_928411566 小时前
10.9 鸿蒙创建和运行项目
android·华为·harmonyos
卓码软件测评6 小时前
【第三方网站代码登记测试_HTTP头语法代码详解】
网络·网络协议·http·架构·web