RePlugin集成ARouter
发布日期:2021-06-30 18:38:05 浏览次数:2 分类:技术文章

本文共 25983 字,大约阅读时间需要 86 分钟。

欢迎关注微信公众号:

一、功能需求

1、组件解耦 (ARouter)

阿里开源的 在组件化开发中这个库相当有用,其核心功能就是组件解耦,比如以往要跳转另一个 Activity 时,会使用如下代码:

val intent = Intent(this, OtherActivity::class.java)startActivity(intent)

这样的代码意味着耦合性极强,当前的 Activity 代码层面上直接引用了 OtherActivity,当项目中应用了模块化开发(Module 之间不直接依赖)或多渠道变体(不同渠道指定不同的 src 目录)时,将会导致 OtherActivity 无法引用,这时工程就会报错了,而使用 ARouter 时,上述代码可以转变成如下代码:

ARouter.getInstance().build("/module2/other").navigation()@Route(path = "/module2/other")class OtherActivity : BaseActivity() {
...}

很显示,使用了 ARouter 之后,Activity 之间不再存在代码级引用,这就是组件解耦。ARouter 还支持 Fragment,更多使用方式请查看官方文档:

2、插件化 (RePlugin)

项目的架构设计涉及到插件化,我们使用的是 ,当然,每个公司对 RePlugin 的使用方式可能是不一样的,大致分为以下两种:

  • 方式一: 宿主是主 app,包含主要的业务功能,只一些小功能需要通过插件方式动态更新。
  • 方式二: 宿主是马甲包,插件才是主 app,宿主 apk 只管一件事:更新主 app 插件。

这里,我们基于方式二来使用 RePlugin,因为方式二中主 app 是插件,涉及大量业务逻辑,并且工程会用到多渠道变体(不同渠道指定不同的 src 目录),所以,我们需要在插件中使用 ARouter,而宿主的功能很简单,用不到 ARouter。

>>>>>>>>>>>>>>>>>>>注意:以下所有的分析都以此为基础<<<<<<<<<<<<<<<<<<<<

二、问题与解决方案

按照 ARouter 的官方文档,在插件工程中对 ARouter 进行了依赖,作为单品时,所有功能都正常使用,但作为插件时,就完全失效了~ 界面提示:There's no route matched! Path = [/arouter/service/interceptor] Group = [arouter]

1、ARouter 初始化失败

在 ARouter 的 issue 中也有很多 issue 提到与插件化框架搭配时出了问题,官方的回应表示对 RePlugin 等插件化框架支持不好,相关 issue 有:

以下是 app 分别作为单品或插件时 ARouter 的日志输出:

// >>>>>>>>>>>>>>>>>>> 单品日志输出I/ARouter::: ARouter openLog[ ]I/ARouter::: ARouter openDebug[ ]I/ARouter::: ARouter init start.[ ]I/ARouter::: Run with debug mode or new install, rebuild router map.[ ]I/ARouter::: VM with name 'Android' has multidex supportE/ARouter::: InstantRun support error, com.android.tools.fd.runtime.PathsI/ARouter::: Thread production, name is [ARouter task pool No.1, thread No.1][ ]D/ARouter::: Filter 6 classes by packageName 
I/ARouter::: Find router map finished, map size = 6, cost 43 ms.[ ]I/ARouter::: Load root element finished, cost 4 ms.[ ]D/ARouter::: LogisticsCenter has already been loaded, GroupIndex[2], InterceptorIndex[0], ProviderIndex[2][ ]I/ARouter::: ARouter init success![ ]D/ARouter::: The group [arouter] starts loading, trigger by [/arouter/service/interceptor][ ]D/ARouter::: The group [arouter] has already been loaded, trigger by [/arouter/service/interceptor][ ]I/ARouter::: Thread production, name is [ARouter task pool No.1, thread No.2][ ]I/ARouter::: ARouter init over.[ ]// >>>>>>>>>>>>>>>>>>> 插件日志输出I/ARouter::: ARouter openLog[ ]I/ARouter::: ARouter openDebug[ ]I/ARouter::: ARouter init start.[ ]I/ARouter::: Run with debug mode or new install, rebuild router map.[ ]I/ARouter::: VM with name 'Android' has multidex supportE/ARouter::: InstantRun support error, com.android.tools.fd.runtime.PathsI/ARouter::: Thread production, name is [ARouter task pool No.1, thread No.1][ ]D/ARouter::: Filter 0 classes by packageName
I/ARouter::: Find router map finished, map size = 0, cost 14 ms.[ ]I/ARouter::: Load root element finished, cost 0 ms.[ ]E/ARouter::: No mapping files were found, check your configuration please![ ]D/ARouter::: LogisticsCenter has already been loaded, GroupIndex[0], InterceptorIndex[0], ProviderIndex[0][ ]I/ARouter::: ARouter init success![ ]W/ARouter::: ARouter::There is no route match the path [/arouter/service/interceptor], in group [arouter][ ]I/ARouter::: ARouter init over.[ ]

大致分析一下日志发现,当作为插件时,路由映射配置为 0(Find router map finished, map size = 0),为了找到 ARouter 初始化失败的原因,简单的跟踪了一下源码:

ARouter 初始化失败,那么一般要从初始化入口开始查起,即 ARouter.init(application)

public final class ARouter {
public static void init(Application application) {
if (!hasInit) {
hasInit = _ARouter.init(application); ... } }}final class _ARouter {
protected static synchronized boolean init(Application application) {
LogisticsCenter.init(mContext, executor); ... return true; }}

可以看到 ARouter#init() 最终会执行 LogisticsCenter.init(mContext, executor),下面来看 LogisticsCenter.init(mContext, executor) 的源码:

public class LogisticsCenter {
/** * LogisticsCenter init, load all metas in memory. Demand initialization */ public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
// Step1. 获取到 app 所有的 class Set
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
())); } logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms."); // Step2. 加载 路由配置、拦截器 相关类 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); } } ... }}

这段代码的核心是最后的 for 循环,大致意思就是通过判断类名规则,过滤出 ARouter 相关的特定类,比如 路由分组信息 相关类、拦截器 相关类,并分别加载进 Warehouse.groupsIndexWarehouse.interceptorsIndex,不过它不是出问题的关键。

补充:ARouter 的工程源码中有 arouter-annotationarouter-compiler 这 2 个额外的 Module,这是使用 ARouter 能够进行 依赖注入组件解耦 的技术核心: 编译时注解 + JavaPoet。就组件解耦而言,ARouter 通过编译时注解技术,可以在工程编译期间,获取到使用了 @Route 注解的类,结合 JavaPoet 生成特定的 class 文件,用来描述 path 与组件之间的对应关系(以及各个参数)。

根据输出日志信息,可以知道 routerMap 中的元素个数为 0,而 routerMap 是通过 ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE) 得到的,再来看 ClassUtils 源码:

public class ClassUtils {
/** * 通过指定包名,扫描包下面包含的所有的ClassName * * @param context U know * @param packageName 包名 * @return 所有class的集合 */ public static Set
getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
final Set
classNames = new HashSet<>(); List
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); } Enumeration
dexEntries = dexfile.entries(); while (dexEntries.hasMoreElements()) {
String className = dexEntries.nextElement(); if (className.startsWith(packageName)) {
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; } /** * get all the dex path * * @param context the application context * @return all the dex path * @throws PackageManager.NameNotFoundException * @throws IOException */ public static List
getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0); File sourceApk = new File(applicationInfo.sourceDir); List
sourcePaths = new ArrayList<>(); sourcePaths.add(applicationInfo.sourceDir); //add the default apk path //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配置的 if (!isVMMultidexCapable()) { //the total dex numbers 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; }}

因为 ClassUtilsgetFileNameByPackageName()getSourcePaths() 代码很关键,所以原封不动拷贝过来了,大致的逻辑是:通过包名获取到 apk 安装时解压出来的所有 dex 文件路径,再通过加载 dex 文件,提取出其中所有的 class,然后再回到 LogisticsCenter.init(mContext, executor) 中最后的那个 for 循环,加载出所有的路由和拦截器配置。那问题就来了:

Q: 为什么 routerMap 中的元素个数为 0 ?或者说,为什么 dex 文件提取不出 class ?

A: 根据 RePlugin 官方 wiki 中,对插件的目录结构的介绍中,可以知道,插件的 dex 文件就不在常规目录下,所以 ARouter 压根就获取不到插件的 dex 文件,更别说加载插件中的路由及拦截器配置了。

Q: 那要怎样才能让 ARouter 加载到插件的 dex 文件呢?

A: 理论上可以在插件中,通过反射的方式给 ARouter 的 Warehouse.groupsIndex 追加路由配置信息。或者对 ARouter 进行代码改造,在 LogisticsCenter.init(mContext, executor) 中插入获取当前插件 dex 文件中所有 class 的代码。

Q: 这样就能让 ARouter 在插件中正常工作了吗?

A: 理论上是的。不过 Activity 可能还是会跳转失败。

2、Activity 跳转失败

ARouter 组件路由的关键方法就是 navigation(),而所有重载的 navigation() 方法最终都会走向 _ARouter#_navigation() 方法,其源码如下:

final class _ARouter {
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = postcard.getContext(); switch (postcard.getType()) {
case ACTIVITY: // Build intent final Intent intent = new Intent(currentContext, postcard.getDestination()); intent.putExtras(postcard.getExtras()); // Set flags. int flags = postcard.getFlags(); if (0 != flags) {
intent.setFlags(flags); } // Non activity, need FLAG_ACTIVITY_NEW_TASK if (!(currentContext instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } // Set Actions String action = postcard.getAction(); if (!TextUtils.isEmpty(action)) {
intent.setAction(action); } // Navigation in main looper. runInMainThread(new Runnable() {
@Override public void run() {
startActivity(requestCode, currentContext, intent, postcard, callback); } }); 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; }}

这里主要看 switch-caseACTIVITY 分支部分,在创建好 Intent 之后,就会调用 _ARouter#startActivity() 来启动 Activity,其源码如下:

final class _ARouter {
/** * Start activity * * @see ActivityCompat */ private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
if (requestCode >= 0) {
// Need start for result if (currentContext instanceof Activity) {
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle()); } else {
logger.warning(Consts.TAG, "Must use [navigation(activity, ...)] to support [startActivityForResult]"); } } else {
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle()); } if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) {
// Old version. ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim()); } if (null != callback) {
// Navigation over. callback.onArrival(postcard); } }}

可以看到 _ARouter#startActivity() 是通过 ActivityCompat.startActivity() 来启动 Activity,有一点很关键,插件中的 Activity,必须由插件的 context 来启动,那么这里的 currentContext 是谁?通过 Postcard#setContext() 的调用可以定位到以下源码:

final class _ARouter {
private static Context mContext; protected static synchronized boolean init(Application application) {
mContext = application; ... return true; } protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
// Set context to postcard. postcard.setContext(null == context ? mContext : context); ... }}

也就是说,在调用 _ARouter#navigation() 时,如果外部有传入 context 就使用外部的 context,否则使用 application,而 _ARouter 的 application 来自框架初始化时 ARouter#init(application) 传入的 application,好了,现在的问题就是,这个 application 是宿主的,还是插件的?

下面只考虑作为插件的情况,因为作为单品没有宿主插件之分。

情景 1:在自定义 Application 中初始化 ARouter

public class MyApplication extends Application {
@Override public void onCreate() {
super.onCreate(); ARouter.init(this); // this是插件的application }}
  • 自定义 Application 中的 this 就是【插件】的 application。

情景 2:在 Activity 中初始化 ARouter

public class MyActivity extends AppCompatActivity {
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); ARouter.init(this.getApplication()); // 宿主application ARouter.init(((Application) this.getApplicationContext())); // 插件application }}
  • activity.getApplication():拿到【宿主】的 application
  • activity.getApplicationContext():拿到【插件】的 application

相关 issue:

综上,出现 Activity 跳转失败的本质原因就是使用了宿主的 context,主要发生在非自定义 Application 中初始化 ARouter 的场景,现在解决这个问题就很简单了,有 2 种解决方案:

如果你是在自定义 Application 中初始化 ARouter 的话,下面就不用看了。

方案 1:在非自定义 Application 中使用 applicationContext 初始化 ARouter:

public class MyActivity extends AppCompatActivity {
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); ARouter.init(((Application) this.getApplicationContext())); }}

方案 2:使用带 context 参数的 navigation(Context) 方法进行路由操作:

public class MyActivity extends AppCompatActivity implements View.OnClickListener {
private Button button; @Override public void onClick(View view) {
ARouter.getInstance().build("/module2/other").navigation(this); }}

三、LiteARouter

通过以上分析得出,为了让 ARouter 能够在 RePlugin 中正常初始化,有两种方案:

  • 反射:通过反射的方式给 ARouter 的追加路由配置信息 Warehouse.groupsIndex ,拦截器 Warehouse.interceptorsIndex 以及 IProvider 服务 Warehouse.providersIndex
  • 改造:对 ARouter 进行代码改造,在 LogisticsCenter.init(mContext, executor) 中插入获取当前插件 dex 文件中所有 class 的代码。

以上两种方案各有利弊,个人觉得改造的方式应该比较好一点,不过呢,我并没有完全按上面的方式来处理,一方面是因为 ARouter 的设计思路是比较复杂的,除了路由,还有拦截器,依赖注入,以及 IProvider 等服务,并不能完全保证除了路由以外的其他功能是否能正常使用,另一方面是因为项目时间紧、任务重,开发时间严重不足,而且我们只需要用到路由功能。于是,我的做法是改造 ARouter,只保留路由功能,并命名为 。

  • LiteARouter 的 Git 仓库:

1、arouter-compiler 分析

通过分析 arouter-compiler 代码,结合 jadx 反编译,可以知道最终会在 com.alibaba.android.arouter.routes 包下生成 ARouter$$Root$$XXX 的类,比如:

package com.alibaba.android.arouter.routes;import com.alibaba.android.arouter.facade.template.IRouteGroup;import com.alibaba.android.arouter.facade.template.IRouteRoot;import java.util.Map;public class ARouter$$Root$$substance implements IRouteRoot {
public void loadInto(Map
> routes) {
routes.put("substance", ARouter$$Group$$substance.class); }}

substance 是我的主程序 Module 名,一般是 app,也就是 gradle 文件中 AROUTER_MODULE_NAME 参数对应的值。

可以知道 ARouter$$Root$$XXXloadInto(Map<String, Class<? extends IRouteGroup>> routes) 方法会将路由的【分组信息】保存到 routes 中,而这个 route 正是 Warehouse.groupsIndex 。另外,真正的路由【映射信息】则是生成在了 ARouter$$Group$$XXX 类中,比如:

package com.alibaba.android.arouter.routes;import com.alibaba.android.arouter.facade.enums.RouteType;import com.alibaba.android.arouter.facade.model.RouteMeta;import com.alibaba.android.arouter.facade.template.IRouteGroup;import com.charylin.substance.screen.main.MainActivity;import java.util.Map;public class ARouter$$Group$$substance implements IRouteGroup {
public void loadInto(Map
atlas) {
atlas.put("/substance/main", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/substance/main", "substance", (Map
) null, -1, Integer.MIN_VALUE)); }}

ARouter$$Group$$XXXloadInto(Map<String, RouteMeta> atlas) 会将路由【映射信息】保存到 atlas 中,而这个 atlas 正是 Warehouse.routes

2、LogisticsCenter 分析

现在回过头再来看 LogisticsCenter.init(mContext, executor) 中最后的那个 for 循环,就比较清楚它是怎么加载路由配置信息的了:

public class LogisticsCenter {
/** * LogisticsCenter init, load all metas in memory. Demand initialization */ public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
... // Step2. 加载 路由配置、拦截器 相关类 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); } } ... }}

其核心就是反射拿到 ARouter$$Root$$XXXARouter$$Interceptors$$XXXARouter$$Providers$$XXX 类并创建实例,最终丢到 Warehouse 中,细心的你可能发现了,这里怎么没有 Warehouse.routes ?其实 LogisticsCenter 有一个很重要的 completion(Postcard) 方法,主要是对 Postcard 中的信息进行完善填充,以下是 completion(Postcard) 方法源码:

public class LogisticsCenter {
/** * Completion the postcard by route metas * * @param postcard Incomplete postcard, should complete by this method. */ public synchronized static void completion(Postcard postcard) {
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath()); if (null == routeMeta) {
// Maybe its does't exist, or didn't load. if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
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 {
addRouteGroupDynamic(postcard.getGroup(), null); } catch (Exception e) {
throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]"); } completion(postcard); // Reload } } ... } public synchronized static void addRouteGroupDynamic(String groupName, IRouteGroup group) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
if (Warehouse.groupsIndex.containsKey(groupName)){
// If this group is included, but it has not been loaded // load this group first, because dynamic route has high priority. Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes); Warehouse.groupsIndex.remove(groupName); } // cover old group. if (null != group) {
group.loadInto(Warehouse.routes); } }}

LogisticsCenter#completion(Postcard) 源码的大致逻辑是,当根据 Postcard 中的路由 path 从 Warehouse.routes 中获取不到 routeMeta 时,即为路由信息缺失,这时,会根据 Postcard 中的路由 group (分组信息)动态加载到具体的路由配置,并保存到 Warehouse.routes 中,所以说,其实 Warehouse.routes 中具体的 routeMeta 是按需加载的。

3、LogisticsCenter 改造

综上,因为 Warehouse.routes 会在 LogisticsCenter#completion(Postcard) 方法中动态加载填充,所以,我只需要通过反射把路由【分组信息】加载进 Warehouse.groupsIndex 即可,于是我改造了 LogisticsCenter#loadRouterMap() 方法:

public class LogisticsCenter {
private static Context sContext; private static ThreadPoolExecutor sExecutor; private LogisticsCenter() {
} public synchronized static void init(Context context, ThreadPoolExecutor tpe) {
loadRouterMap(); } private static void loadRouterMap() {
try {
String nameOfRouteRootClass = Consts.NAME_OF_ROUTE_ROOT_CLASS; // com.charylin.litearouter.routes.LiteARouter$$Root Class
routeRootClz = Class.forName(nameOfRouteRootClass); if (routeRootClz != null) {
IRouteRoot routeGroup = routeRootClz.asSubclass(IRouteRoot.class).newInstance(); if (routeGroup != null) {
routeGroup.loadInto(Warehouse.groupsIndex); } } } catch (Exception e) {
LiteARouter.logger.error(Consts.TAG, "load router map error.", e); } }}public final class Consts {
public static final String SEPARATOR = "$$"; public static final String PROJECT = "LiteARouter"; public static final String TAG = PROJECT + "::"; public static final String NAME_OF_ROOT = PROJECT + SEPARATOR + "Root"; public static final String PACKAGE_OF_GENERATE_FILE = "com.charylin.litearouter.routes"; public static final String NAME_OF_ROUTE_ROOT_CLASS = PACKAGE_OF_GENERATE_FILE + '.' + NAME_OF_ROOT;}

为了反射足够简单,我把 ARouter$$Root$$XXX 类名改为 LiteARouter$$Root ,也就是说类名后面不追加模块名。即 gradle 文件中不需要配置 AROUTER_MODULE_NAME 参数了。

4、arouter-compiler 改造

既然记载了路由【分组信息】的类名( ARouter$$Root$$XXX )设计变化了,那么在 compiler 中也需要对生成的类名规则进行修改:

@AutoService(Processor.class)@SupportedAnnotationTypes({
ANNOTATION_TYPE_ROUTE})public class RouteProcessor extends BaseProcessor {
private void parseRoutes(Set
routeElements) throws IOException {
... // Write root meta into disk. String rootFileName = NAME_OF_ROOT; // String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName; JavaFile.builder(PACKAGE_OF_GENERATE_FILE, TypeSpec.classBuilder(rootFileName) .addJavadoc(WARNING_TIPS) .addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITROUTE_ROOT))) .addModifiers(PUBLIC) .addMethod(loadIntoMethodOfRootBuilder.build()) .build() ).build().writeTo(mFiler); }}

因为只需要保留了路由功能,所以,把拦截器、依赖注入等不需要用到的功能对应的生成类逻辑一并精简掉了。

至此,便是我在处理 RePlugin+ARouter 搭配时发现的问题思考及解决方案,以上只是此次改造 ARouter 中最重要的部分,更多细节可通过对比 与 ARouter 各个文件的差异来了解。

转载地址:https://linqiarui.blog.csdn.net/article/details/115665330 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Flutter - StateWidget与生命周期
下一篇:Flutter - Dart事件循环机制与异步

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2024年04月18日 07时16分10秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

Hourglass Network 沙漏网络 (pose estimation姿态估计) 2019-04-30
OpenCV实战(二)——答题卡识别判卷 2019-04-30
目标检测神经网络的发展历程(52 个目标检测模型) 2019-04-30
Boundary loss 损失函数 2019-04-30
神经网络调参实战(一)—— 训练更多次数 & tensorboard & finetune 2019-04-30
tensorflow使用tensorboard进行可视化 2019-04-30
神经网络调参实战(二)—— activation & initializer & optimizer 2019-04-30
凸优化 convex optimization 2019-04-30
数据库索引 & 为什么要对数据库建立索引 / 数据库建立索引为什么会加快查询速度 2019-04-30
IEEE与APA引用格式 2019-04-30
research gap 2019-04-30
pytorch训练cifar10数据集查看各个种类图片的准确率 2019-04-30
Python鼠标点击图片,获取点击点的像素坐标 2019-04-30
路径规划(一) —— 环境描述(Grid Map & Feature Map) & 全局路径规划(最优路径规划(Dijkstra&A*star) & 概率路径规划(PRM&RRT)) 2021-07-03
神经网络调参实战(四)—— 加深网络层次 & 批归一化 batch normalization 2021-07-03
数据挖掘与数据分析(三)—— 探索性数据分析EDA(多因子与复合分析) & 可视化(1)—— 假设检验(μ&卡方检验&方差检验(F检验))&相关系数(皮尔逊&斯皮尔曼) 2021-07-03
RRT算法(快速拓展随机树)的Python实现 2021-07-03
路径规划(二) —— 轨迹优化(样条法) & 局部规划(人工势能场法) & 智能路径规划(生物启发(蚁群&RVO) & 强化学习) 2021-07-03
D*算法 2021-07-03
强化学习(四) —— Actor-Critic演员评论家 & code 2021-07-03