注册

ARouter原理解析分享

前言


炎炎夏日,不知道大家的周末会是如何安排。本文将给大家带来的分享是ARouter的的原理介绍,通过了解它的原理,我们可以知道它是如何支持组件化和不互相依赖的模块间进行调用或者页面的跳转。


正文


1.ARouter介绍


ARouter是阿里开源的一个用于进行组件化的路由框架,它可以帮助互不依赖的组件间进行页面跳转和服务调用。


2.ARouter使用


添加依赖:


android {
//...
defaultConfig {
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
}
//...
}

dependencies {
api 'com.alibaba:arouter-api:1.5.0'
kapt 'com.alibaba:arouter-compiler:1.2.2'
}

定义跳转Activity的path:


@Route(path = "/test/router_activity")
class RouterActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_router)
}
}

初始化Router框架:


class RouterDemoApp : Application() {
override fun onCreate() {
super.onCreate()
//初始化、注入
ARouter.init(this)
}
}

调用跳转:


ARouter.getInstance().build("/test/router_activity").navigation()

3.生成的代码(生成的路由表)


当我们给Activity或者服务等加上Route注解后,build一下,ARouter框架便会按照模版帮我们生成java文件,并且是在运行的时候可以访问的。其中使用的技术是apt技术。下面我们一起看看上边的示例生成的代码:


public class ARouter$$Root$$app implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("test", ARouter$$Group$$test.class);
}
}

public class ARouter$$Group$$test implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/test/router_activity", RouteMeta.build(RouteType.ACTIVITY, RouterActivity.class, "/test/router_activity", "test", null, -1, -2147483648));
}
}

根据上边生成的代码可以看出,生成的代码就是一张路由表,先将群组跟群组的class对应起来,每个群组里边是该群组下的路由表。


4.初始化init()(加载路由表的群组)


接下来我们看看初始化时,路由框架里边做了哪些事情:


//#ARouter
public static void init(Application application) {
if (!hasInit) {
//...省略部分代码
hasInit = _ARouter.init(application);
//...省略部分代码
}
}

//#_ARouter
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;
}

初始化的核心代码看起来就在LogisticsCenter中:


public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;

try {
//...省略代码
if (registerByPlugin) {
//...省略代码
} else {
Set<String> routerMap;

// 如果是debug包或者更新版本
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
//获取在com.alibaba.android.arouter.routes下的所以class类名
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
//更新到sp中
if (!routerMap.isEmpty()) {
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}
//更新版本
PackageUtils.updateVersion(context);
} else {
//直接从缓存拿出之前存放的class
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}

//遍历routerMap,将group的类加载到缓存中
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
//生成的Root、比如我们上面示例的ARouter$$Root$$app,调用loadInto相当于加载了routes.put("test", ARouter$$Group$$test.class)
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
//加载拦截器,例如生成的ARouter$$Interceptors$$app
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// 加载Provider,例如生成的ARouter$$Providers$$app
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
}
//...省略代码
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}

上边的核心逻辑就是如果是debug包或者更新版本,那么就去获取com.alibaba.android.arouter.routes下的所以class类名,然后更新到sp中,并且更新版本号。然后通过反射加载IRouteRoot,去加载群组及对应的class对象,除此还会加载拦截器,Provider。


这里我们重点看一下获取class文件路径的方法getFileNameByPackageName:


public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws NameNotFoundException, IOException, InterruptedException {
final Set<String> classNames = new HashSet();
//获取到dex文件路径
List<String> paths = getSourcePaths(context);
final CountDownLatch parserCtl = new CountDownLatch(paths.size());
Iterator var5 = paths.iterator();

while(var5.hasNext()) {
final String path = (String)var5.next();
DefaultPoolExecutor.getInstance().execute(new Runnable() {
public void run() {
DexFile dexfile = null;
try {
//加载出dexfile文件
if (path.endsWith(".zip")) {
dexfile = DexFile.loadDex(path, path + ".tmp", 0);
} else {
dexfile = new DexFile(path);
}

Enumeration dexEntries = dexfile.entries();
// 遍历dexFile里边的元素,加载出.class文件
while(dexEntries.hasMoreElements()) {
String className = (String)dexEntries.nextElement();
//开头"com.alibaba.android.arouter.routes"
if (className.startsWith(packageName)) {
classNames.add(className);
}
}
} catch (Throwable var12) {
Log.e("ARouter", "Scan map file in dex files made error.", var12);
} finally {
//...省略代码
parserCtl.countDown();
}
}
});
}

parserCtl.await();
//。。。省略代码
return classNames;
}

此方法里边的核心逻辑就是加载出dex文件的路径,然后通过路径构建出DexFile,构建后遍历它里边的元素,如果是com.alibaba.android.arouter.routes开头的class文件,则保存到列表里等待返回。


getSourcePaths:


public static List<String> getSourcePaths(Context context) throws NameNotFoundException, IOException {
ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
File sourceApk = new File(applicationInfo.sourceDir);
List<String> sourcePaths = new ArrayList();
sourcePaths.add(applicationInfo.sourceDir);
String extractedFilePrefix = sourceApk.getName() + ".classes";
//是否开启了multidex,如果开启的话,则需获取每个dex路径
if (!isVMMultidexCapable()) {
int totalDexNumber = getMultiDexPreferences(context).getInt("dex.number", 1);
File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
//遍历每一个dex文件
for(int secondaryNumber = 2; secondaryNumber <= totalDexNumber; ++secondaryNumber) {
//app.classes2.zip、app.classes3.zip ...
String fileName = extractedFilePrefix + secondaryNumber + ".zip";
File extractedFile = new File(dexDir, fileName);
if (!extractedFile.isFile()) {
throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
}
sourcePaths.add(extractedFile.getAbsolutePath());
}
}

if (ARouter.debuggable()) {
sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));
}

return sourcePaths;
}

getSourcePaths的功能就是获取app的所有dex文件的路径,为后面转成class文件从而获取class文件路径提供数据。


小结:



  • ARouter.init(this)调用交给了内部的_ARouter.init(application),然后真正做事的是LogisticsCenter.init(mContext, executor)
  • 如果是debug包或者升级版本,则去加载出com.alibaba.android.arouter.routes包下的dex文件的路径,并且更新到缓存里边
  • 通过这些dex去获取对应的所有class文件的路径
  • 最后根据类名的前缀加载到Warehouse中对应的map里,其中就有group、interceptor和provider

5.调用及处理


ARouter.getInstance().build("/test/router_activity").navigation()

build会构建一个Postcard对象出来:


//#Router
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}

//#_ARouter
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方法就是从path中提取出group,比如"/test/router_activity",test便是提取出来的group
return build(path, extractGroup(path));
}
}

build(path, group)方法最终会构建一个Postcard对象出来。


构建好PostCard之后,调用它的navigation方法便可以实现我们的跳转或者获取对应的实体。navigation方法最后会调用到_ARouter的navigation方法:


protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
//...省略代码
try {
//1.根据postCard的group加载路由表,并且补全postCard的信息
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
//...异常处理
return null;
}
if (null != callback) {
callback.onFound(postcard);
}

//如果不是绿色通道的话,需要走拦截器的逻辑,否则会跳过拦截器
if (!postcard.isGreenChannel()) {
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
//2.真正实现动作处理
_navigation(context, postcard, requestCode, callback);
}
@Override
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
//...省略代码
}
});
} else {
//2.真正实现动作处理
return _navigation(context, postcard, requestCode, callback);
}
return null;
}

navigation方法的核心逻辑为:加载路由表,并且补全postCard的信息,然后真正处理跳转或者请求逻辑。


LogisticsCenter.completion(postcard)的核心源码如下:


public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
}

RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) {
//groupsIndex在init的时候已经加载好了,这里就可以通过group获取到对应group的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 {
// Load route and cache it into memory, then delete from metas.
try {
//...省略代码
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
//将group里边的路由表加载进内存,我们最开始的例子想当于执行:atlas.put("/test/router_activity", RouteMeta.build(RouteType.ACTIVITY, RouterActivity.class, "/test/router_activity", "test", null, -1, -2147483648));
iGroupInstance.loadInto(Warehouse.routes);
//因为加载路由表了,所以可以将group从内存中移除,节省内存
Warehouse.groupsIndex.remove(postcard.getGroup());
//...省略代码
} catch (Exception e) {
throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
}
//已经将group里的路由表加载出来了,再执行一遍函数。
completion(postcard); // Reload
}
} else {
// 第二次时,给postCard填补信息
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
//...省略代码,主要是解析uri然后参数的赋值

//根据路由获取的不同的类型,继续补充一些信息给postCard
switch (routeMeta.getType()) {
case PROVIDER:
//...省略代码,主要是补充一些其他参数
postcard.greenChannel(); // Provider should skip all of interceptors
break;
case FRAGMENT:
postcard.greenChannel(); // Fragment needn't interceptors
default:
break;
}
}
}

补充完postCard信息之后,接下来我们看看_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:
//构造Intent,然后切换到主线程,并且跳转到指定的Activity
break;
case PROVIDER:
return postcard.getProvider();
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT:
//反射构造出实例并返回
case METHOD:
default:
return null;
}

return null;
}

可以看到,最终会根据不同的type,去做出不同的响应,例如ACTIVITY的话,会进行activity的跳转,其他的会通过反射构造出实例返回等操作。


小结:



  • 调用的最开始,会构建一个PostCard对象,初始化path和group
  • navigation方法最终会调用到_ARouter的navigation方法,然后通过LogisticsCenter.completion(postcard)去加载group里边的路由表,并且补全postcard信息。
  • 如果是有绿色通道的话,则不执行拦截器,直接跳过,否则需要执行拦截器。
  • 最后便是通过不同类型执行对应的操作。

结语


本文的分享到这里就结束了,相信看完后,能够对ARouter的原理有了一定的理解,以便我们后面如果有使用到它的时候,能够更好的地使用,或者为项目定制出路由框架提供了很好的思路参考。同时,这么优秀的框架也值得我们去学习它的一些设计思路。


作者:敖森迪
链接:https://juejin.cn/post/7118385586850758686
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册