1. Android 组件化一 Arounter核心路由框架 原理深入解析 和实战

1.组件化的整体架构

1.1 组件化和非组件化

1.1.1 非组件化的架构图

KEEP APP分为3个模块

首页,教练,运动,商城

每个tab都可以进入到运动的健身入口,这样就导致模块之间存在复杂的依赖关系 ,导致模块间有着高耦合度。从而引发下面的问题

1).合代码经常发生冲突

2).做一个需求,发现还要去改动很多别人模块的代码?

3).别的模块已实现的类似功能,自己要用只能去复制一份代码再改改?

4).做了个需求,但不知不觉导致其他模块出现bug?

1.1.2 组件化

依赖关系详细:

基础组件 是通用基础能力,修改频率极低,作为SDK可共公司所有项目集成使用。 基础组件之间可以互相依赖吗,比如说基础网络组件需要打印日志,是直接依赖日志基础组件,还是网络组件自己实现 其实:都放common中 common组件,包含基础组件,作为支撑业务组件、业务基础组件的基础(BaseActivity/BaseFragment等基础能力),同时依赖所有的基础组件,提供多数业务组件需要的基本功能,并且统一了基础组件的版本号。所以 业务组件、业务基础组件 所需的基础能力只需要依赖common组件即可获得。

业务组件、功能基础组件,都依赖common组件。但业务组件之间不存在依赖关系,功能基础组件之间不存在依赖关系。而 业务组件 是依赖所需的功能基础组件的,例如几乎所有业务组件都会依赖广告组件 来展示Banner广告、弹窗广告等。

1.1.3 组件化的核心作用:

解决模块强耦合,协作效率低下、工程结构臃肿

1.2 通信问题解决方案

业务组件间 没有依赖,如何实现页面的跳转?

业务组件间 没有依赖,如何实现组件间通信/方法调用?

业务组件间 没有依赖,如何获取fragment实例?

业务组件不能反向依赖壳工程,如何获取Application实例、如何获取Application onCreate()回调

下面我们具体来分析解决这些问题

1.2.1 业务组件间 没有依赖,如何实现页面的跳转?

Arounter框架 发起跳转

vbscript 复制代码
ARouter.getInstance()
        .build("/cart/cartActivity")
        .withString("key1","param1")//携带参数1
        .withString("key2","param2")//携带参数2
        .navigation();

接收方:

scala 复制代码
@Route(path = CartRouterTable.PATH_PAGE_CART)
public class CartActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_cart);
        
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction transaction= manager.beginTransaction();
        Fragment userFragment = CartFragment.newInstance("param1","param2");
        transaction.add(R.id.fl_test_fragment, userFragment, "tag");
        transaction.commit();
    }
}

1.2.2 业务组件间 没有依赖,如何实现组件间通信/方法调用?

服务暴露组件

  • 暴露组件 只存放 服务接口、服务接口相关的实体类、路由信息、便于服务调用的util等

  • 服务调用方 只依赖 服务提供方的 露组件,如module_home依赖export_cart,而不依赖module_cart

  • 组件 需要依赖 自己的暴露组件,并实现服务接口,如module_cart依赖export_cart 并实现其中的服务接口

  • 接口的实现注入 依然是由ARouter完成,和页面跳转一样使用路由信息

Home模块获取Card模块的方法

ini 复制代码
//调用购物车组件服务:获取购物车商品数量
TextView tvCartProductCount = findViewById(R.id.tv_cart_product_count);
tvCartProductCount.setText("购物车商品数量:"+ CartServiceUtil.getCartProductCount().productCount);
scss 复制代码
/**
 * 获取购物车中商品数量
 * @return
 */
public static CartInfo getCartProductCount(){
    return getService().getProductCountInCart();
}
java 复制代码
@Route(path = CartRouterTable.PATH_SERVICE_CART)
public class CartServiceImpl implements ICartService {

    @Override
    public CartInfo getProductCountInCart() {
        CartInfo cartInfo = new CartInfo();
        cartInfo.productCount = 666;
        return cartInfo;
    }

总结:暴露有2种方式

一种: 在base中暴露, 另外一种:要放在一个module种

我更倾向于第一种,但是也容易导致common模块臃肿!

另外一种:

(一个模块编译)业务组件是一个APP= MOduleB+exportB

(集成编译) 一个Module = MOduleB+exportB , 请问这个怎么实现

模拟嵌套结构(物理路径嵌套)

虽然 Gradle 不支持真正嵌套,但可通过路径配置实现:

缺点: 数据库跨表查询和 导致export对外暴露的多,同时别的模块依赖很多个export模块!

1.2.3 业务组件间 没有依赖,如何获取fragment实例?

scss 复制代码
// 拿到cart_fragment ,但是不是完整的实例,获取实例不合理
Fragment cart_Fragment = (Fragment) ARouter.getInstance().build(CartRouterTable.PATH_FRAGMENT_CART).navigation();
transaction.add(R.id.fl_test_fragment, cart_Fragment, "tag");
transaction.commit();
java 复制代码
@Route(path = CartRouterTable.PATH_FRAGMENT_CART)
public class CartFragment extends Fragment implements CartServiceAidlImpl.ValueUpdateListener{
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    private String mParam1;
    private String mParam2;

    public CartFragment() {
        // Required empty public constructor
    }

    public static CartFragment newInstance(String param1, String param2) {
        CartFragment fragment = new CartFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        r

获取到的是一个Fragment,Fragment cart_Fragment, 不是CartFragment,因为不存在依赖关系,肯定是拿不到的!

1.2.4 业务组件不能反向依赖壳工程,如何获取Application实例、如何获取Application onCreate()回调

方案一: 注入Common模块 就是在common模块中设置Application,同时可以监控Application的各个生命周期函数回调,各个业务组件都是需要依赖Common模块的!

方案二:Arounter 接口下沉 + 服务发现

// 复制代码
public interface IApplicationProvider {
    Application getApplication();
    // 可扩展其他全局方法:getGlobalConfig()等
}

// base模块
public interface IApplicationProvider {
    Application getApplication();
    // 可扩展其他全局方法:getGlobalConfig()等
}

// 业务模块获取Application
IApplicationProvider provider = 
    (IApplicationProvider) ARouter.getInstance()
        .build("/service/application")
        .navigation();

Application app = provider.getApplication();

1.3 更深度的通信问题

1).唯一不同是没有拆分出"服务暴露组件",事件通信相关的实体类下沉到了基础库

答:也是

2). 请教下服务暴露组件那里,比如module_cart与export _cart, export_cart里的暴露的服务接口需要有CartFragment实现,但是我的CartFragment的实例对象是有app工程里的HomeActivity所持有的, 这种情况那我的export_cart怎么对外暴露服务啊?

答: fragment是否需要暴露,

如果不暴露,export_cart里的暴露的服务接口需要有CartFragment实现! 还得在底层暴露接口给export_cart

3)请问在fragment实例获取那里,如果在activity中想要调用fragment的方法该怎么调用

答:添加暴露的接口或者把fragment也暴露

4).老哥,那个假如我ListFragment 里面有个点击操作然后打开DetailFragment 并传入List Fragment 里面那个点击的Item。像这种情况怎么用IService 实现?

我尝试在IService里面创建 fun getItem():Observable但是通过ARouter 传出去之后并没有Item 从Stream里面Emit 出来。我估计是ARouter 是copy IService Object到Activity里面了,所以在Fragment 里面的点击并没有传出去。但是像这种case的一般方法是怎么做呢?感谢

答:可以暴露fragment,然后调用传参,不暴露fragment,一样可以传参数

5). 组件间通信问题:在HomeFragment里面关注某个用户,然后在MineFragment(懒加载)显示关注的用户数量,当在HomeFragment点击关注用户后,如何通知MineFragment刷新关注用户数量呢?

这种情况只能用EventBus来通知吗?

答:可以传listener,是一样的

5).大佬,着急想请教一个问题。组件化后 对于本地数据库该如何设计?如果单抽出一个模块专门整数据库,写起来容易,但是如果多部门协作每个部门都要对这个模块改动;如果每个组件都独立一个数据库互相协作没有问题。但是无法跨表查询,还有就是比如用户信息这种通用性较强的数据每个组件都要写一份逻辑。求大佬帮忙分析下!谢谢!

答:需要export模块,提供

6).单个模块独立运行的时候依赖问题?

暴露组件服务来解决组件与组件之间的耦合,就拿上面的来说,module_home依赖的是export_cart,但是ICartService的实现类是在module_cart的,也就是单独运行home的时候,cart没有进行编译,当你调用getService的时候会报空指针(我没有用aar的方式,不过应该跟这个没关系吧)。

单独运行home工程的话,需要home工程的app module依赖module_cart(感觉不合理,不太适合运行单个模块 )

当isModule=true的时候是不是只能单独运行module_home,然后module_home如果要跳转到module_cart或者获取module_cart的东西是不是做不了,因为module_home只依赖了export_cart,这时module_cart没参与编译。

但是单独运行应该只适合调试单一模块内的业务。涉及到多模块跳转,那和集成运行没什么区别了

7).每次调用CartServiceUtil.getCartProductCount()是不是都会产生一个CartServiceImpl对象呢,如果CartServiceImpl 仅仅是一个提供数据的工具,有没有办法做到是单例的呢

这个实例的注入是arouter完成的,是单例了

有的:添加这个注解:

typescript 复制代码
@Route(path = "/cart/service", singleton = false)

public class HeavyResourceService {

    // 持有大量资源的服务

    private Bitmap largeImage;

    private DatabaseConnection dbConn;

    // 需要手动释放资源

    public void releaseResources() {

        largeImage.recycle();

        dbConn.close();

    

}

8)如何实现跨进程呢?

很简单.AIDL + arount+ 接口

Arounter只是为了帮助你做到跨组件的方法调用而已,至于你有一个组件的功能是做在独立进程,比如这个组件是个单独进程的service,这时候还是要自己做aidl解决啊。说白了就是aidl + router结合 案例: Home模块调用

typescript 复制代码
private ICartServiceAidl cartService;


private  void bindCartService() {
    Intent intent = new Intent();
    // 设置Cart服务的完整包名
    intent.setComponent(new ComponentName(
            "com.example.cart",  // Cart模块包名
            "com.example.cart.CartServiceImpl" // 服务类全名
    ));

    bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}

private ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        cartService = ICartServiceAidl.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        cartService = null;
    }
};

private void updateCartValue(int newValue) {
    if (cartService != null) {
        try {
            cartService.updateValue(newValue);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

定义服务和AIDL接口

csharp 复制代码
// ICartServiceAidl.aidl
package com.example.common;
parcelable android.os.Bundle;

interface ICartServiceAidl {
     void updateValue(int newValue);
     int getCurrentValue();
}
java 复制代码
public class CartServiceAidlImpl extends Service {

    private int currentValue = 0;

    // 使用回调接口通知 UI
    public interface ValueUpdateListener {
        void onValueUpdated(int newValue);
    }

    private static WeakReference<ValueUpdateListener> listenerRef;

    public static void setUpdateListener(ValueUpdateListener listener) {
        listenerRef = new WeakReference<>(listener);
    }

    private final IBinder binder = new ICartServiceAidl.Stub() {
        @Override
        public void updateValue(int newValue) {
            currentValue = newValue;
            notifyValueUpdate(newValue);
        }

        @Override
        public int getCurrentValue() {
            return currentValue;
        }
    };

    private void notifyValueUpdate(final int newValue) {
        // 确保在主线程更新 UI
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                if (listenerRef != null && listenerRef.get() != null) {
                    listenerRef.get().onValueUpdated(newValue);
                }
            }
        });
    }

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}

1.4 组件化的搭建最重要的是

先把项目模块化,进行解耦

2.整体的流程三大步骤

使用:

vbscript 复制代码
//通过路由跳转到 购物车组件的购物车页面(但没有依赖购物车组件),
ARouter.getInstance()
        .build("/cart/cartActivity")
        .withString("key1","param1")//携带参数1
        .withString("key2","param2")//携带参数2
        .navigation();
java 复制代码
@Route(path = CartRouterTable.PATH_FRAGMENT_CART)
public class CartFragment extends Fragment implements CartServiceAidlImpl.ValueUpdateListener{
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

从Module_home跳转到Module_card的过程

路由框架: 把明信片(PostCard)投递到中心站,然后中心站进行处理(Warehouse),给到接收端

2.1 发送

scss 复制代码
ARouter.getInstance().build(CartRouterTable.PATH_FRAGMENT_CART).navigation();

2.2 中转

java 复制代码
/**
 * 核心导航方法,处理路由跳转的全流程
 * 
 * @param context     上下文(Activity 或 Application)
 * @param postcard    路由信息封装对象(包含目标路径、参数等)
 * @param requestCode startActivityForResult 使用的请求码
 * @param callback    路由跳转回调监听器
 * @return 目标对象(如 Fragment 或服务实例),页面跳转时返回 null
 */
protected Object navigation(final Context context, final Postcard postcard, 
                            final int requestCode, final NavigationCallback callback) {
    
    // 1. 预处理检查 - 全局拦截器
    // 获取全局预处理服务,执行前置拦截逻辑(如登录验证)
    PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
    if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
        // 拦截器返回 false 时取消导航
        return null;
    }

    // 2. 设置上下文对象
    // 确保 postcard 持有有效的 Context 实例
    postcard.setContext(null == context ? mContext : context);

    try {
        // 3. 路由信息补全 - 核心步骤
        // 根据 path 查找路由表,将元数据注入 postcard(目标类、类型等)
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        // 4. 路由查找失败处理
        logger.warning(Consts.TAG, ex.getMessage());

        // 调试模式下显示 Toast 提示
        if (debuggable()) {
            runInMainThread(() -> 
                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 {
            // 无自定义回调时使用全局降级服务
            DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
            if (null != degradeService) {
                degradeService.onLost(context, postcard);
            }
        }
        return null; // 终止导航
    }

    // 5. 触发路由找到回调
    if (null != callback) {
        callback.onFound(postcard);
    }

    // 6. 拦截器通道处理
    if (!postcard.isGreenChannel()) { 
        // 非绿色通道:执行拦截器链(可能耗时,在异步线程执行)
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            @Override
            public void onContinue(Postcard postcard) {
                // 拦截器放行,继续导航流程
                _navigation(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 {
        // 绿色通道:跳过拦截器直接执行
        return _navigation(postcard, requestCode, callback);
    }

    return null; // 异步拦截器模式下返回 null
}

使用前面构建好的PastCard经过整体3个步骤,就完成了路由:

  1. 完善postcard信息:只有path、group还不行,还需要知道具体目的地,例如要跳转到的Activity信息。
  2. 拦截器处理:不是绿色通道的话,要先经过路由器的处理,结果是中断或者继续。
  3. 获取路由结果:例如进行目标Activity的跳转、获取IProvider实现对象、获取Fragment等。

采用策略模式:

方法内容就是根据路由类型来走对应逻辑:

  • Activity, 使用postcard.getDestination()来构建intent、传入Extras、设置 flags、action,最后在主线程执行 熟悉的startActivity
  • provider,指的是想要获取的服务,即IProvider的实现类。是直接从postCard中获取的,因为服务类是单例,只会在首次获取时创建()。
  • Broadcast、ContentProvider、Fragment,都是使用postcard.getDestination()反射创建实例

2.3 接受, 跳转

类型驱动:通过 postcard.getType() 分发处理逻辑

java 复制代码
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    final Context currentContext = postcard.getContext();
    //根据路由类型来走对应逻辑
    switch (postcard.getType()) {
        case ACTIVITY:
            // Activity, 使用postcard.getDestination()来构建intent、传入Extras、设置 flags、action
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            intent.putExtras(postcard.getExtras());
            int flags = postcard.getFlags();
            if (0 != flags) {
                intent.setFlags(flags);
            }
            // 当前的context不是activity,需要添加flag:FLAG_ACTIVITY_NEW_TASK
            //(启动startActivity需有任务栈的,application/service启动activity没有任务栈,所以必须要new_task_flag新建一个任务栈)
            if (!(currentContext instanceof Activity)) {
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }

            String action = postcard.getAction();
            if (!TextUtils.isEmpty(action)) {
                intent.setAction(action);
            }

            // 最后在主线程执行 熟悉的startActivity,
            runInMainThread(new Runnable() {
                @Override
                public void run() {
                    startActivity(requestCode, currentContext, intent, postcard, callback);
                }
            });
            break;
        case PROVIDER:
            //provider,指的是想要获取的服务,即IProvider的实现类。直接从postCard中获取。
            return postcard.getProvider();
        case BOARDCAST:
        case CONTENT_PROVIDER:
        case FRAGMENT:
        //Broadcast、ContentProvider、Fragment,都是使用postcard.getDestination()反射创建实例
            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;
            } ...
    }
    return null;
}

问题: 平时用的方法调用,用的是Iprovider接口,那么是怎么走的流程?

图解:

2.4 详细的流程解读:完善postcard信息

2.4.1 Postcard的信息填充

scss 复制代码
/**
 * 核心路由补全方法:将路径字符串转换为完整的路由元数据
 * 
 * @param postcard 路由信息载体(包含原始路径/参数等)
 * @throws NoRouteFoundException 路由查找失败时抛出
 * @throws HandlerException 路由加载异常时抛出
 */
public synchronized static void completion(Postcard postcard) {

    // 2. 尝试从内存路由表获取路由元数据
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    
    if (null == routeMeta) {
        // 3. 路由未找到:检查分组是否存在
        if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
            throw new NoRouteFoundException(TAG + "There is no route match the path [" + 
                postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
        } else {
            // 4. 动态加载路由组(按需加载)
            try {
                // 5. 核心:动态加载路由组
                addRouteGroupDynamic(postcard.getGroup(), null);
            } catch (Exception e) {
                throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + 
                    e.getMessage() + "]");
            }

            // 6. 递归重试:重新执行路由查找
            completion(postcard);   // Reload
        }
    } else {
        // 7. 路由找到:注入元数据到Postcard
        postcard.setDestination(routeMeta.getDestination());  // 目标类
        postcard.setType(routeMeta.getType());                // 类型(Activity/Provider等)
        postcard.setPriority(routeMeta.getPriority());        // 优先级
        postcard.setExtra(routeMeta.getExtra());              // 附加数据

        // 8. 处理URI参数(深度链接场景)
        Uri rawUri = postcard.getUri();
        if (null != rawUri) {
            // 8.1 解析URI中的查询参数
            Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
            Map<String, Integer> paramsType = routeMeta.getParamsType();

            if (MapUtils.isNotEmpty(paramsType)) {
                // 8.2 类型转换:将字符串参数转换为目标类型
                for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                    setValue(postcard, 
                            params.getValue(),    // 参数类型
                            params.getKey(),       // 参数名
                            resultMap.get(params.getKey()) // 参数值
                    );
                }

                // 8.3 标记需要自动注入的参数
                postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, 
                    paramsType.keySet().toArray(new String[]{}));
            }

            // 8.4 保存原始URI
            postcard.withString(ARouter.RAW_URI, rawUri.toString());
        }

        // 9. 特殊类型处理
        switch (routeMeta.getType()) {
            case PROVIDER:  
                // 9.1 服务提供者特殊处理
                Class<? extends IProvider> providerMeta = 
                    (Class<? extends IProvider>) routeMeta.getDestination();
                
                // 尝试获取缓存实例
                IProvider instance = Warehouse.providers.get(providerMeta);
                if (null == instance) { 
                    // 9.2 首次访问:创建并初始化实例
                    try {
                        IProvider provider = providerMeta.getConstructor().newInstance();
                        provider.init(mContext);  // 初始化服务
                        Warehouse.providers.put(providerMeta, provider); // 缓存
                        instance = provider;
                    } catch (Exception e) {
                        logger.error(TAG, "Init provider failed!", e);
                        throw new HandlerException("Init provider failed!");
                    }
                }
                postcard.setProvider(instance);  // 注入服务实例
                postcard.greenChannel();         // 服务提供者跳过拦截器
                break
            case FRAGMENT:
                // 9.3 Fragment跳过拦截器
                postcard.greenChannel();    
                break;
            default:
                // 其他类型保持默认处理
                break;
        }
    }
}

2.4.2 Warehouse :仓库的填充(路由表的填充)

在哪里填充的? 有哪些核心的数据?

ini 复制代码
protected static synchronized boolean init(Application application) {
    mContext = application;
    LogisticsCenter.init(mContext, executor);
    logger.info(Consts.TAG, "ARouter init success!");
    hasInit = true;
    mHandler = new Handler(Looper.getMainLooper());

    return true;
}
scss 复制代码
/**
 * ARouter 核心初始化方法(同步)
 * 
 * 负责加载路由表、拦截器表和服务提供者表,建立全局路由索引
 * 
 * @param context 应用上下文
 * @param tpe 线程池(用于后续异步任务)
 * @throws HandlerException 初始化失败时抛出
 */
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {

    try {
  
        // 2. 优先尝试插件化加载(gradle插件自动注册)
        loadRouterMap();  // 内部检查是否启用了自动注册插件
        
        // 3. 判断是否通过插件完成注册
        if (registerByPlugin) {
            logger.info(TAG, "Load router map by arouter-auto-register plugin.");
        } else {
            Set<String> routerMap;

            // 4. 判断是否需要重新构建路由表:
            //    - 调试模式
            //    - 新安装/版本升级(通过版本号比对)
            if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                
                // 5. 扫描编译期生成的路由类(ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes")
                routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                
                if (!routerMap.isEmpty()) {
                    // 6. 缓存路由表到SharedPreferences
                    context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE)
                          .edit()
                          .putStringSet(AROUTER_SP_KEY_MAP, routerMap)
                          .apply();
                }

                // 7. 更新版本记录(避免重复扫描)
                PackageUtils.updateVersion(context);
            } else {
                // 8. 从缓存加载路由表
                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>()));
            }

            // 10. 动态加载三类核心组件
            for (String className : routerMap) {
                // 10.1 加载路由组(IRouteRoot实现类)
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    ((IRouteRoot) Class.forName(className).getConstructor().newInstance())
                        .loadInto(Warehouse.groupsIndex); // 注入到分组索引
                
                // 10.2 加载拦截器组(IInterceptorGroup实现类)
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    ((IInterceptorGroup) Class.forName(className).getConstructor().newInstance())
                        .loadInto(Warehouse.interceptorsIndex); // 注入到拦截器索引
                
                // 10.3 加载服务提供者组(IProviderGroup实现类)
                } 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) {
        // 14. 异常处理
        throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
    }
}

核心数据:

  • groupsIndex所有路由组元信息。是所有IRouteGroup实现类的class对象,是在ARouter初始化中赋值,key是path第一级。IRouteGroup实现类是编译时生成,代表一个组,即path第一级相同的所有路由,包括Activity和Provider服务)。

  • routes所有路由元信息。是在LogisticsCenter.completion中赋值,key是path。首次进行某个路由时就会加载整个group的路由,即IRouteGroup实现类中所有路由信息。包括Activity和Provider服务

  • providers所有服务provider实例。在LogisticsCenter.completion中赋值,key是IProvider实现类的class

  • providersIndex所有provider服务元信息 (实现类的class对象)。是在ARouter初始化中赋值,key是IProvider实现类的全类名。用于使用IProvider实现类class发起的获取服务的路由,例如ARouter.getInstance().navigation(HelloService.class)

  • interceptorsIndex所有拦截器实现类class对象。是在ARouter初始化时收集到,key是优先级

  • interceptors所有拦截器实例。是在ARouter初始化完成后立即创建

分层存储:

索引层:groupsIndex, providersIndex, interceptorsIndex

实例层:routes, providers, interceptors

填充数据的核心过程:

LogisticsCenter初始化 就是加载所有的路由元信息的过程,有两种方式:

  1. 走loadRouterMap()方法:直接使用在编译时收集好的帮助类信息,然后反射创建帮助类实例后,调用其loadInto方法来填充Warehouse相应的Map。这需要开发者要先引入插件才行 apply plugin: 'com.alibaba.arouter'。如果加载成功,registerByPlugin这个值就为true,否则false。
  2. 若第一步没有加载成功,即registerByPlugin为false:就会使用ClassUtils.getFileNameByPackageName在运行时搜集dex中"com.alibaba.android.arouter.routes"包下的所有类(即帮助类),然后遍历,区分是哪种帮助类。接着反射创建帮助类实例后,调用其loadInto方法来填充Warehouse相应的Map。

两种方式对比:反射和AGP

  • 相同点:都是使用帮助类信息反射创建帮助类实例后,调用其loadInto方法来填充Warehouse相应的Map。
  • 不同点:在于帮助类信息的收集方式。前者是在编译时搜集就完毕了,后者是运行时。

一般都是使用第一种,因为运行时遍历dex搜集会比较耗时,而第一种在编译时已经收集好了。 第一种方式的loadRouterMap()方法的实现逻辑 DEX的方式:

找到需要的类名通过loadInto();

可以看看:自己生成的类!

调用其loadInto方法来填充Warehouse相应的Map。

root-----》group--->provider

编译期层(APT生成)
组件 生成文件示例 作用
RouteProcessor ARouter$$Group$$user.class @Route注解的类按分组生成路由表
InterceptorProcessor ARouter$$Interceptors$$app.class 收集@Interceptor类并按优先级排序
ProviderProcessor ARouter$$Providers$$module.class 处理@Route标记的IProvider实现类

3.拦截器的原理

拦截器:InterceptorService

InterceptorServiceImpl拦截器的实现类

具体做的事情:登录,权限,埋点

地域限制:根据地理位置限制访问特定内容

参数自动注入:统一添加公共参数

限流控制:防止短时间内重复跳

线程相关+责任链模

拦截器是如何创建出来的 ?

scss 复制代码
  static void afterInit() {

       // Trigger interceptor init, use byName.

        interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();

   }

InterceptorService 是 ARouter 路由框架中拦截器机制的核心调度服务,负责拦截器的统一管理和执行流程控制。其作用与实现原理如下:


⚙️ 3.1、核心作用

  1. 拦截器链式执行

    管理所有注册的拦截器(IInterceptor),按优先级(priority值越小优先级越高)顺序执行拦截逻辑,支持跳转前的统一处理(如登录验证、权限检查、埋点统计等)37。

  2. 异步线程调度

    拦截器在独立线程池中执行,避免主线程阻塞(防止ANR),超时默认300秒可配置28。

  3. 流程控制决策

    通过回调接口 InterceptorCallback 决定跳转流程:

    • onContinue():放行,执行下一拦截器或最终跳转
    • onInterrupt():中断,触发全局或局部中断回调27。
  4. 超时与异常处理

    监控拦截器执行超时,自动中断并抛出 HandlerException("The interceptor processing timed out.")8。


🛠️ 3.2、实现原理

1. 编译期:APT生成映射类
  • 通过注解处理器 InterceptorProcessor 扫描 @Interceptor 注解类,生成映射文件(如 ARouter$$Interceptors$$app.java):

    java

    typescript 复制代码
    public class ARouter$$Interceptors$$app implements IInterceptorGroup {
      @Override
      public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
        interceptors.put(5, LoginInterceptor.class); // priority=5
        interceptors.put(10, LoggingInterceptor.class); // priority=10
      }
    }

    按优先级存储拦截器类信息36。

2. 初始化期:加载与实例化
  • 步骤1LogisticsCenter.init() 加载映射类到 Warehouse.interceptorsIndex(类型为 Map<Integer, Class<? extends IInterceptor>>)25。

  • 步骤2_ARouter.afterInit() 获取 InterceptorService 实例(实际为 InterceptorServiceImpl):

    java

    scss 复制代码
    interceptorService = (InterceptorService) ARouter.getInstance()
        .build("/arouter/service/interceptor").navigation();

    触发 InterceptorServiceImpl.init(),在子线程中反射创建拦截器实例 并调用其 init(context) 方法,缓存至 Warehouse.interceptors 列表14。

3. 执行期:责任链调度

当路由跳转触发拦截时(!postcard.isGreenChannel()):

java

typescript 复制代码
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
  @Override
  public void onContinue(Postcard postcard) {
    _navigation(); // 继续跳转
  }
  @Override
  public void onInterrupt(Throwable exception) {
    callback.onInterrupt(postcard); // 中断跳转
  }
});

执行流程

  1. 检查拦截器初始化状态,未完成则直接中断8。

  2. 通过 CancelableCountDownLatch 控制超时,按优先级顺序递归执行拦截器链:

    java

    typescript 复制代码
    private static void _execute(int index, CancelableCountDownLatch counter, Postcard postcard) {
      IInterceptor interceptor = Warehouse.interceptors.get(index);
      interceptor.process(postcard, new InterceptorCallback() {
        @Override
        public void onContinue(Postcard postcard) {
          counter.countDown();
          _execute(index + 1, counter, postcard); // 递归执行下一个
        }
        @Override
        public void onInterrupt(Throwable exception) {
          postcard.setTag(exception.getMessage()); // 保存异常信息
          counter.cancel(); // 终止链式调用
        }
      });
    }

    48

  3. 若所有拦截器放行(counter.getCount() == 0),调用 callback.onContinue();若超时或中断,触发 onInterrupt()8。


⚠️ 3.3、关键设计要点

  1. 优先级不可重复

    编译期校验 @Interceptor(priority) 值,重复则报错,确保执行顺序确定性7。

  2. 绿色通道机制
    ProviderFragment 类型路由自动跳过拦截器(postcard.greenChannel())12。

  3. 线程安全与资源控制

    • 拦截器实例单例化,避免重复创建。
    • 子线程执行 + 超时计数锁,防止死锁48。
  4. 中断传递

    调用 onInterrupt() 时需传入非空 Throwable,否则无法触发中断回调(依赖 postcard.getTag() 判空)8。


🔧 3.4、典型应用场景

场景 拦截器作用 实现示例
登录验证 拦截未登录跳转,重定向到登录页 if (!isLogin) callback.onInterrupt()
权限检查 校验用户权限,拒绝无权限访问 if (!checkPermission()) callback.onInterrupt()
动态参数注入 统一添加公共参数(如设备ID) postcard.withString("deviceId", getDeviceId())
埋点统计 记录页面跳转路径 Analytics.logRoute(postcard.getPath())

4.Arounter的version关系

arounter的version关系,新增,然后重新,怎么时候会更新aounter,重新加载!

  • 新安装/升级 :通过PackageUtils检测版本变化
    • 生产环境 :读取SharedPreferences缓存提升加载速度

在 ARouter 框架中,PackageUtils 类负责处理版本升级控制,主要通过 版本号比对SharedPreferences 缓存机制 来实现。以下是核心实现逻辑的详细解析:

java 复制代码
public class PackageUtils {
    // 存储键值常量
    private static final String AROUTER_SP_CACHE_KEY = "AROUTER_SP_CACHE";
    private static final String LAST_VERSION_NAME = "LAST_VERSION_NAME";
    private static final String LAST_VERSION_CODE = "LAST_VERSION_CODE";

    /**
     * 检测是否为新版本(需重建路由表)
     */
    public static boolean isNewVersion(Context context) throws PackageManager.NameNotFoundException {
        // 1. 获取当前版本信息
        PackageInfo packageInfo = context.getPackageManager().getPackageInfo(
                context.getPackageName(), 0);
        String currentVersionName = packageInfo.versionName;
        int currentVersionCode = packageInfo.versionCode;
        
        // 2. 获取上次存储的版本信息
        SharedPreferences sp = context.getSharedPreferences(
                AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE);
        String lastVersionName = sp.getString(LAST_VERSION_NAME, null);
        int lastVersionCode = sp.getInt(LAST_VERSION_CODE, -1);
        
        // 3. 版本比对逻辑
        return !currentVersionName.equals(lastVersionName) || 
               currentVersionCode != lastVersionCode;
    }

    /**
     * 更新版本信息(路由表重建后调用)
     */
    public static void updateVersion(Context context) throws PackageManager.NameNotFoundException {
        // 1. 获取当前版本
        PackageInfo packageInfo = context.getPackageManager().getPackageInfo(
                context.getPackageName(), 0);
        
        // 2. 持久化存储版本信息
        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE)
            .edit()
            .putString(LAST_VERSION_NAME, packageInfo.versionName)
            .putInt(LAST_VERSION_CODE, packageInfo.versionCode)
            .apply();
    }
}

5.整体梳理

5.1 架构图

5.2 设计思想

整个路由详细过程:moduelA通过中间人ARouter把路由信息的存到仓库WareHouse;moduleB发起路由时,再通过中间人ARouter从仓库WareHouse取出路由信息,这要就实现了没有依赖的两者之间的跳转与通信。其中涉及Activity的跳转、服务provider的获取、拦截器的处理等。

需要重点理解的是:路由框架的整体思路,通过中间人ARouter使用WareHouse加载和获取路由信息;路由信息加载实现原理,各帮助类作用和路由完善过程。

5.3 核心类

LogisticsCenter: (后勤中心 ),职责 :负责路由信息的注册与补全 ,是连接Warehouse和路由请求的枢纽

Warehouse: (数据仓库) , 职责 :作为全局内存存储中心,集中管理所有路由元数据、拦截器及服务实例

_ARouter: (路由执行引擎) ,职责控制导航流程,处理拦截、降级及最终跳转逻辑

类名 核心职责 关键数据结构/方法 性能影响
Warehouse 内存存储路由元数据 groupsIndex, routes, providers 高频读取,内存级速度
LogisticsCenter 路由注册与元数据补全 init(), completion() 首次加载耗时,后续快速
_ARouter 导航流程控制与跳转执行 navigation(), 拦截器调度 跳转逻辑耗时,异步优化

5.4 做了哪些优化

1.AGP: 减少反射 插件化自动注册(arouter-register)

  • 原理:通过 Gradle 插件在编译期生成路由注册代码,替代运行时扫描 Dex 文件,避免首次启动时全量加载路由表的耗时操作

2.动态按需加载 路由组(如 ARouter$$Group$$user)在首次访问时动态加载,减少启动时内存占用

  1. 非调试模式下复用 SharedPreferences 缓存的路由表,避免重复扫描

5.5 一些问题

问题:为什么ARouter <math xmlns="http://www.w3.org/1998/Math/MathML"> P r o v i d e r s Providers </math>ProvidersXXX这个类? 不需要ARouter <math xmlns="http://www.w3.org/1998/Math/MathML"> A c t i v i t y Activity </math>ActivityXXX这样的类

服务可能在应用启动时就需可用(如全局服务)

服务注册需要更高的性能(无分组加载)

相关推荐
LaoZhangAI35 分钟前
Kiro vs Cursor:2025年AI编程IDE深度对比
前端·后端
止观止38 分钟前
CSS3 粘性定位解析:position sticky
前端·css·css3
爱编程的喵1 小时前
深入理解JavaScript单例模式:从Storage封装到Modal弹窗的实战应用
前端·javascript
lemon_sjdk1 小时前
Java飞机大战小游戏(升级版)
java·前端·python
G等你下课1 小时前
如何用 useReducer + useContext 构建全局状态管理
前端·react.js
欧阳天羲1 小时前
AI 增强大前端数据加密与隐私保护:技术实现与合规遵
前端·人工智能·状态模式
慧一居士1 小时前
Axios 和Express 区别对比
前端
I'mxx1 小时前
【html常见页面布局】
前端·css·html
万少1 小时前
云测试提前定位和解决问题 萤火故事屋 上架流程
前端·harmonyos·客户端