ARouter源码详解

logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + “, cost " + (System.currentTimeMillis() - startInit) + " ms.”);

startInit = System.currentTimeMillis();

for (String className : routerMap) {

//通过 className 的前缀来判断该 class 对应的什么类型,并同时缓存到 Warehouse 中

//1.IRouteRoot

//2.IInterceptorGroup

//3.IProviderGroup

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() + “]”);

}

}

}

复制代码

对于第三步,可以举个例子来加强理解。对于上文所讲的 UserHomeActivity,其对应的 path 是 /account/userHome,ARouter 默认会将 path 的第一个单词即 account 作为其 group,而且 UserHomeActivity 是放在名为 user 的 module 中

而 ARouter 在通过注解处理器生成辅助文件的时候,类名就会根据以上信息来生成,所以最终就会生成以下两个文件:

package com.alibaba.android.arouter.routes;

/**

  • DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */

public class ARouter R o o t Root Rootuser implements IRouteRoot {

@Override

public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {

routes.put(“account”, ARouter G r o u p Group Groupaccount.class);

}

}

复制代码

package com.alibaba.android.arouter.routes;

/**

  • DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */

public class ARouter G r o u p Group Groupaccount implements IRouteGroup {

@Override

public void loadInto(Map<String, RouteMeta> atlas) {

atlas.put(“/account/userHome”, RouteMeta.build(RouteType.ACTIVITY, UserHomeActivity.class, “/account/userhome”, “account”, null, -1, -2147483648));

}

}

复制代码

LogisticsCenterinit 方法就会根据文件名的固定前缀 ARouter$$Root$$ 定位到 ARouter$$Root$$user 这个类,然后通过反射构建出该对象,然后通过调用其 loadInto 方法将键值对保存到 Warehouse.groupsIndex 中。等到后续需要跳转到 groupaccount 的页面时,就会再来反射调用 ARouter$$Group$$accountloadInto 方法,即按需加载,等到需要的时候再来获取详细的路由对应信息

因为对于一个大型的 App 来说,可能包含一百或者几百个页面,如果一次性将所有路由信息都加载到内存中,对于内存的压力是比较大的,而用户每次使用可能也只会打开十几个页面,所以这里必须是按需加载

四、跳转到 Activity

讲完初始化流程,那就再来看下 ARouter 实现 Activity 跳转的流程

跳转到 Activity 最简单的方式就是只指定 path:

ARouter.getInstance().build(RoutePath.USER_HOME).navigation()

复制代码

build() 方法会通过 ARouter 中转调用到 _ARouterbuild() 方法,最终返回一个 Postcard 对象

/**

  • Build postcard by path and default group

*/

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) {

//用于路径替换,这对于某些需要控制页面跳转流程的场景比较有用

//例如,如果某个页面需要登录才可以展示的话

//就可以通过 PathReplaceService 将 path 替换 loginPagePath

path = pService.forString(path);

}

//使用字符串 path 包含的第一个单词作为 group

return build(path, extractGroup(path));

}

}

/**

  • Build postcard by path and group

*/

protected Postcard build(String path, String group) {

if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {

throw new HandlerException(Consts.TAG + “Parameter is invalid!”);

} else {

PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);

if (null != pService) {

path = pService.forString(path);

}

return new Postcard(path, group);

}

}

复制代码

返回的 Postcard 对象可以用于传入一些跳转配置参数,例如:携带参数 mBundle、开启绿色通道 greenChannel 、跳转动画 optionsCompat

public final class Postcard extends RouteMeta {

// Base

private Uri uri;

private Object tag; // A tag prepare for some thing wrong.

private Bundle mBundle; // Data to transform

private int flags = -1; // 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;

}

复制代码

Postcardnavigation() 方法又会调用到 _ARouter 的以下方法来完成 Activity 的跳转。该方法逻辑上并不复杂,注释也写得很清楚了

final class _ARouter {

/**

  • Use router navigation.

  • @param context Activity or null.

  • @param postcard Route metas

  • @param requestCode RequestCode

  • @param callback cb

*/

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {

PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);

if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {

// Pretreatment failed, navigation canceled.

//用于执行跳转前的预处理操作,可以通过 onPretreatment 方法的返回值决定是否取消跳转

return null;

}

try {

LogisticsCenter.completion(postcard);

} catch (NoRouteFoundException ex) {

//没有找到匹配的目标类

//下面就执行一些提示操作和事件回调通知

logger.warning(Consts.TAG, ex.getMessage());

if (debuggable()) {

// Show friendly tips for user.

runInMainThread(new Runnable() {

@Override

public void run() {

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 {

// No callback for this invoke, then we use the global degrade service.

DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);

if (null != degradeService) {

degradeService.onLost(context, postcard);

}

}

return null;

}

if (null != callback) {

//找到了匹配的目标类

callback.onFound(postcard);

}

if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.

//没有开启绿色通道,那么就还需要执行所有拦截器

//外部可以通过拦截器实现:控制是否允许跳转、更改跳转参数等逻辑

interceptorService.doInterceptions(postcard, new InterceptorCallback() {

/**

  • Continue process

  • @param postcard route meta

*/

@Override

public void onContinue(Postcard postcard) {

//拦截器允许跳转

_navigation(context, postcard, requestCode, callback);

}

/**

  • Interrupt process, pipeline will be destory when this method called.

  • @param exception Reson of interrupt.

*/

@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(context, postcard, requestCode, callback);

}

return null;

}

//由于本例子的目标页面是 Activity,所以只看 ACTIVITY 即可

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:

// Build intent

//Destination 就是指向目标 Activity 的 class 对象

final Intent intent = new Intent(currentContext, postcard.getDestination());

//塞入携带的参数

intent.putExtras(postcard.getExtras());

// Set flags.

int flags = postcard.getFlags();

if (-1 != flags) {

intent.setFlags(flags);

} else if (!(currentContext instanceof Activity)) { // Non activity, need less one flag.

intent.setFlags(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;

··· //省略其它类型判断

}

return null;

}

}

复制代码

navigation 方法的重点在于 LogisticsCenter.completion(postcard) 这一句代码。在讲 ARouter 初始化流程的时候有讲到:等到后续需要跳转到 groupaccount 的页面时,就会再来反射调用 ARouter$$Group$$accountloadInto 方法,即按需加载,等到需要的时候再来获取详细的路由对应信息

completion 方法就是用来获取详细的路由对应信息的。该方法会通过 postcard 携带的 path 和 group 信息从 Warehouse 取值,如果值不为 null 的话就将信息保存到 postcard 中,如果值为 null 的话就抛出 NoRouteFoundException

/**

  • Completion the postcard by route metas

  • @param postcard Incomplete postcard, should complete by this method.

*/

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) { //为 null 说明目标类不存在或者是该 group 还未加载过

Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta.

if (null == groupMeta) {

//groupMeta 为 null,说明 postcard 的 path 对应的 group 不存在,抛出异常

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 {

if (ARouter.debuggable()) {

logger.debug(TAG, String.format(Locale.getDefault(), “The group [%s] starts loading, trigger by [%s]”, postcard.getGroup(), postcard.getPath()));

}

//会执行到这里,说明此 group 还未加载过,那么就来反射加载 group 对应的所有 path 信息

//获取后就保存到 Warehouse.routes

IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();

iGroupInstance.loadInto(Warehouse.routes);

//移除此 group

Warehouse.groupsIndex.remove(postcard.getGroup());

if (ARouter.debuggable()) {

logger.debug(TAG, String.format(Locale.getDefault(), “The group [%s] has already been loaded, trigger by [%s]”, postcard.getGroup(), postcard.getPath()));

}

} catch (Exception e) {

throw new HandlerException(TAG + “Fatal exception when loading group meta. [” + e.getMessage() + “]”);

}

//重新执行一遍

completion(postcard); // Reload

}

} else {

//拿到详细的路由信息了,将这些信息存到 postcard 中

postcard.setDestination(routeMeta.getDestination());

postcard.setType(routeMeta.getType());

postcard.setPriority(routeMeta.getPriority());

postcard.setExtra(routeMeta.getExtra());

//省略一些和本例子无关的代码

···

}

}

复制代码

五、跳转到 Activity 并注入参数

ARouter 也支持在跳转到 Activity 的同时向目标页面自动注入参数

在跳转的时候指定要携带的键值对参数:

ARouter.getInstance().build(RoutePath.USER_HOME)

.withLong(RoutePath.USER_HOME_PARAMETER_ID, 20)

.withString(RoutePath.USER_HOME_PARAMETER_NAME, “leavesC”)

.navigation()

object RoutePath {

const val USER_HOME = “/account/userHome”

const val USER_HOME_PARAMETER_ID = “userHomeId”

const val USER_HOME_PARAMETER_NAME = “userName”

}

复制代码

在目标页面通过 @Autowired 注解修饰变量。注解可以同时声明其 name 参数,用于和传递的键值对中的 key 对应上,这样 ARouter 才知道应该向哪个变量赋值。如果没有声明 name 参数,那么 name 参数就默认和变量名相等

这样,在我们调用 ARouter.getInstance().inject(this) 后,ARouter 就会自动完成参数的赋值

package github.leavesc.user

/**

  • 作者:leavesC

  • 时间:2020/10/4 14:05

  • 描述:

  • GitHub:https://github.com/leavesC

*/

@Route(path = RoutePath.USER_HOME)

class UserHomeActivity : AppCompatActivity() {

@Autowired(name = RoutePath.USER_HOME_PARAMETER_ID)

@JvmField

var userId: Long = 0

@Autowired

@JvmField

var userName = “”

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_user_home)

ARouter.getInstance().inject(this)

tv_hint.text = “$userId $userName”

}

}

复制代码

ARouter 实现参数自动注入也需要依靠注解处理器生成的辅助文件来实现,即会生成以下的辅助代码:

package github.leavesc.user;

/**

  • DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */

public class UserHomeActivity A R o u t e r ARouter ARouterAutowired implements ISyringe {

//用于实现序列化和反序列化

private SerializationService serializationService;

@Override

public void inject(Object target) {

serializationService = ARouter.getInstance().navigation(SerializationService.class);

UserHomeActivity substitute = (UserHomeActivity)target;

substitute.userId = substitute.getIntent().getLongExtra(“userHomeId”, substitute.userId);

substitute.userName = substitute.getIntent().getStringExtra(“userName”);

}

}

复制代码

因为在跳转到 Activity 时携带的参数也是需要放到 Intent 里的,所以 inject 方法也只是帮我们实现了从 Intent 取值然后向变量赋值的逻辑而已,这就要求相应的变量必须是 public 的,这就是在 Kotlin 代码中需要同时向变量加上 @JvmField注解的原因

现在来看下 ARouter 是如何实现参数自动注入的,其起始方法就是:ARouter.getInstance().inject(this),其最终会调用到以下方法

final class _ARouter {

static void inject(Object thiz) {

AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build(“/arouter/service/autowired”).navigation());

if (null != autowiredService) {

autowiredService.autowire(thiz);

}

}

}

复制代码

ARouter 通过控制反转的方式拿到 AutowiredService 对应的实现类 AutowiredServiceImpl的实例对象,然后调用其 autowire 方法完成参数注入

由于生成的参数注入辅助类的类名具有固定的包名和类名,即包名和目标类所在包名一致,类名是目标类类名+ $$ARouter$$Autowired,所以在 AutowiredServiceImpl 中就可以根据传入的 instance 参数和反射来生成辅助类对象,最终调用其 inject 方法完成参数注入

@Route(path = “/arouter/service/autowired”)

public class AutowiredServiceImpl implements AutowiredService {

private LruCache<String, ISyringe> classCache;

private List blackList;

@Override

public void init(Context context) {

classCache = new LruCache<>(66);

blackList = new ArrayList<>();

}

@Override

public void autowire(Object instance) {

String className = instance.getClass().getName();

try {

//如果在白名单中了的话,那么就不再执行参数注入

if (!blackList.contains(className)) {

ISyringe autowiredHelper = classCache.get(className);

if (null == autowiredHelper) { // No cache.

autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();

}

//完成参数注入

autowiredHelper.inject(instance);

//缓存起来,避免重复反射

classCache.put(className, autowiredHelper);

}

} catch (Exception ex) {

//如果参数注入过程抛出异常,那么就将其加入白名单中

blackList.add(className); // This instance need not autowired.

}

}

}

复制代码

六、控制反转

上一节所讲的跳转到 Activity 并自动注入参数属于依赖注入的一种,ARouter 同时也支持控制反转:通过接口来获取其实现类实例

例如,假设存在一个 ISayHelloService 接口,我们需要拿到其实现类实例,但是不希望在使用的时候和特定的实现类 SayHelloService 绑定在一起从而造成强耦合,此时就可以使用 ARouter 的控制反转功能,但这也要求 ISayHelloService 接口继承了 IProvider 接口才行

/**

  • 作者:leavesC

  • 时间:2020/10/4 13:49

  • 描述:

  • GitHub:https://github.com/leavesC

*/

interface ISayHelloService : IProvider {

fun sayHello()

}

@Route(path = RoutePath.SERVICE_SAY_HELLO)

class SayHelloService : ISayHelloService {

override fun init(context: Context) {

}

override fun sayHello() {

Log.e(“SayHelloService”, “$this sayHello”)

}

}

复制代码

在使用的时候直接传递 ISayHelloService 的 Class 对象即可,ARouter 会将 SayHelloService以单例模式的形式返回,无需开发者手动去构建 SayHelloService 对象,从而达到解耦的目的

ARouter.getInstance().navigation(ISayHelloService::class.java).sayHello()

复制代码

和实现 Activity 跳转的时候一样,ARouter 也会自动生成以下几个文件,包含了路由表的映射关系

package com.alibaba.android.arouter.routes;

/**

  • DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */

public class ARouter G r o u p Group Groupaccount implements IRouteGroup {

@Override

public void loadInto(Map<String, RouteMeta> atlas) {

atlas.put(“/account/sayHelloService”, RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, “/account/sayhelloservice”, “account”, null, -1, -2147483648));

}

}

/**

  • DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */

public class ARouter P r o v i d e r s Providers Providersuser implements IProviderGroup {

@Override

public void loadInto(Map<String, RouteMeta> providers) {

providers.put(“github.leavesc.user.ISayHelloService”, RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, “/account/sayHelloService”, “account”, null, -1, -2147483648));

}

}

/**

  • DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */

public class ARouter R o o t Root Rootuser implements IRouteRoot {

@Override

public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {

routes.put(“account”, ARouter G r o u p Group Groupaccount.class);

}

}

复制代码

这里再来看下其具体的实现原理

在讲初始化流程的时候有讲到,LogisticsCenter 实现了扫描特定包名路径拿到所有自动生成的辅助文件的逻辑。所以,最终 Warehouse 中就会在初始化的时候拿到以下数据

Warehouse.groupsIndex:

  • account -> class com.alibaba.android.arouter.routes.ARouter$$Group$$account

Warehouse.providersIndex:

  • github.leavesc.user.ISayHelloService -> RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayHelloService", "account", null, -1, -2147483648)

ARouter.getInstance().navigation(ISayHelloService::class.java) 最终会中转调用到 _ARouter的以下方法

protected T navigation(Class<? extends T> service) {

try {

//从 Warehouse.providersIndex 取值拿到 RouteMeta 中存储的 path 和 group

Postcard postcard = LogisticsCenter.buildProvider(service.getName());

// Compatible 1.0.5 compiler sdk.

// Earlier versions did not use the fully qualified name to get the service

if (null == postcard) {

// No service, or this service in old version.

postcard = LogisticsCenter.buildProvider(service.getSimpleName());

}

if (null == postcard) {

return null;

}

//重点

LogisticsCenter.completion(postcard);

return (T) postcard.getProvider();

} catch (NoRouteFoundException ex) {

logger.warning(Consts.TAG, ex.getMessage());

return null;

}

}

复制代码

LogisticsCenter.completion(postcard) 方法的流程和之前讲解的差不多,只是在获取对象实例的时候同时将实例缓存起来,留待之后复用,至此就完成了控制反转的流程了

/**

  • 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());

switch (routeMeta.getType()) {

case PROVIDER: // if the route is provider, should find its instance

// Its provider, so it must implement IProvider

//拿到 SayHelloService Class 对象

Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();

IProvider instance = Warehouse.providers.get(providerMeta);

if (null == instance) { // There’s no instance of this provider

//instance 等于 null 说明是第一次取值

//那么就通过反射构建 SayHelloService 对象,然后将之缓存到 Warehouse.providers 中

//所以通过控制反转获取的对象在应用的整个生命周期内只会有一个实例

IProvider provider;

try {

provider = providerMeta.getConstructor().newInstance();

provider.init(mContext);

Warehouse.providers.put(providerMeta, provider);

instance = provider;

} catch (Exception e) {

throw new HandlerException("Init provider failed! " + e.getMessage());

}

}

//将获取到的实例存起来

postcard.setProvider(instance);

postcard.greenChannel(); // Provider should skip all of interceptors

break;

case FRAGMENT:

postcard.greenChannel(); // Fragment needn’t interceptors

default:

break;

}

}

复制代码

七、拦截器

ARouter 的拦截器对于某些需要控制页面跳转流程的业务逻辑来说是十分有用的功能。例如,用户如果要跳转到个人资料页面时,我们就可以通过拦截器来判断用户是否处于已登录状态,还未登录的话就可以拦截该请求,然后自动为用户打开登录页面

我们可以同时设定多个拦截器,每个拦截器设定不同的优先级

/**

  • 作者:leavesC

  • 时间:2020/10/5 11:49

  • 描述:

  • GitHub:https://github.com/leavesC

*/

@Interceptor(priority = 100, name = “啥也不做的拦截器”)

class NothingInterceptor : IInterceptor {

override fun init(context: Context) {

}

override fun process(postcard: Postcard, callback: InterceptorCallback) {

//不拦截,任其跳转

callback.onContinue(postcard)

}

}

@Interceptor(priority = 200, name = “登陆拦截器”)

class LoginInterceptor : IInterceptor {

override fun init(context: Context) {

}

override fun process(postcard: Postcard, callback: InterceptorCallback) {

if (postcard.path == RoutePath.USER_HOME) {

//拦截

callback.onInterrupt(null)

//跳转到登陆页

ARouter.getInstance().build(RoutePath.USER_LOGIN).navigation()

} else {

//不拦截,任其跳转

callback.onContinue(postcard)

}

}

}

复制代码

这样,当我们执行 ARouter.getInstance().build(RoutePath.USER_HOME).navigation() 想要跳转的时候,就会发现打开的其实是登录页 RoutePath.USER_LOGIN

来看下拦截器是如何实现的

对于以上的两个拦截器,会生成以下的辅助文件。辅助文件会拿到所有我们自定义的拦截器实现类并根据优先级高低存到 Map 中

package com.alibaba.android.arouter.routes;

/**

  • DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */

public class ARouter I n t e r c e p t o r s Interceptors Interceptorsuser implements IInterceptorGroup {

@Override

public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {

interceptors.put(100, NothingInterceptor.class);

interceptors.put(200, LoginInterceptor.class);

}

}

复制代码

而这些拦截器一样是会在初始化的时候,通过LogisticsCenter.init方法存到 Warehouse.interceptorsIndex

/**

  • LogisticsCenter init, load all metas in memory. Demand initialization

*/

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {

···

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);

}

}

···

}

复制代码

然后,在 _ARouternavigation 方法中,如何判断到此次路由请求没有开启绿色通道模式的话,那么就会将此次请求转交给 interceptorService,让其去遍历每个拦截器

final class _ARouter {

/**

  • Use router navigation.

  • @param context Activity or null.

  • @param postcard Route metas

  • @param requestCode RequestCode

  • @param callback cb

*/

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {

···

if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.

//遍历拦截器

interceptorService.doInterceptions(postcard, new InterceptorCallback() {

/**

  • Continue process

  • @param postcard route meta

*/

@Override

public void onContinue(Postcard postcard) {

_navigation(context, postcard, requestCode, callback);

}

/**

  • Interrupt process, pipeline will be destory when this method called.

  • @param exception Reson of interrupt.

*/

@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(context, postcard, requestCode, callback);

}

return null;

}

}

复制代码

interceptorService 变量属于 InterceptorService 接口类型,该接口的实现类是 InterceptorServiceImpl,ARouter内部在初始化的过程中也是根据控制反转的方式来拿到 interceptorService 这个实例的

InterceptorServiceImpl 的主要逻辑是:

  1. 在第一次获取 InterceptorServiceImpl 实例的时候,其 init 方法会马上被调用,该方法内部会交由线程池来执行,通过反射生成每个拦截器对象,并调用每个拦截器的 init 方法来完成拦截器的初始化,并将每个拦截器对象都存到 Warehouse.interceptors 中。如果初始化完成了,则唤醒等待在 interceptorInitLock 上的线程

  2. 当拦截器逻辑被触发,即 doInterceptions 方法被调用时,如果此时第一个步骤还未执行完的话,则会通过 checkInterceptorsInitStatus()方法等待第一个步骤执行完成。如果十秒内都未完成的话,则走失败流程直接返回

  3. 在线程池中遍历拦截器列表,如果有某个拦截器拦截了请求的话则调用 callback.onInterrupt方法通知外部,否则的话则调用 callback.onContinue() 方法继续跳转逻辑

@Route(path = “/arouter/service/interceptor”)

public class InterceptorServiceImpl implements InterceptorService {

private static boolean interceptorHasInit;

private static final Object interceptorInitLock = new Object();

@Override

public void init(final Context context) {

LogisticsCenter.executor.execute(new Runnable() {

@Override

public void run() {

if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {

//遍历拦截器列表,通过反射构建对象并初始化

for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {

Class<? extends IInterceptor> interceptorClass = entry.getValue();

try {

IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();

iInterceptor.init(context);

//存起来

Warehouse.interceptors.add(iInterceptor);

} catch (Exception ex) {

throw new HandlerException(TAG + “ARouter init interceptor error! name = [” + interceptorClass.getName() + “], reason = [” + ex.getMessage() + “]”);

}

}

interceptorHasInit = true;

logger.info(TAG, “ARouter interceptors init over.”);

synchronized (interceptorInitLock) {

interceptorInitLock.notifyAll();

}

}

}

});

}

@Override

public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {

if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {

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 {

_excute(0, interceptorCounter, postcard);

interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);

if (interceptorCounter.getCount() > 0) { // Cancel the navigation this time, if it hasn’t return anythings.

//大于 0 说明此次请求被某个拦截器拦截了,走失败流程

callback.onInterrupt(new HandlerException(“The interceptor processing timed out.”));

} else if (null != postcard.getTag()) { // Maybe some exception in the tag.

callback.onInterrupt(new HandlerException(postcard.getTag().toString()));

} 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 _excute(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();

_excute(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 excute over with fatal exception.

postcard.setTag(null == exception ? new HandlerException(“No message.”) : exception.getMessage()); // save the exception message for backup.

counter.cancel();

// Be attention, maybe the thread in callback has been changed,

// then the catch block(L207) will be invalid.

// The worst is the thread changed to main thread, then the app will be crash, if you throw this exception!

// if (!Looper.getMainLooper().equals(Looper.myLooper())) { // You shouldn’t throw the exception if the thread is main thread.

// throw new HandlerException(exception.getMessage());

// }

}

});

}

}

private static void checkInterceptorsInitStatus() {

synchronized (interceptorInitLock) {

while (!interceptorHasInit) {

try {

interceptorInitLock.wait(10 * 1000);

} catch (InterruptedException e) {

throw new HandlerException(TAG + “Interceptor init cost too much time error! reason = [” + e.getMessage() + “]”);

}

}

}

}

}

复制代码

八、注解处理器

通篇读下来,读者应该能够感受到注解处理器在 ARouter 中起到了很大的作用,依靠注解处理器生成的辅助文件,ARouter 才能完成参数自动注入等功能。这里就再来介绍下 ARouter 关于注解处理器的实现原理

APT(Annotation Processing Tool) 即注解处理器,是一种注解处理工具,用来在编译期扫描和处理注解,通过注解来生成 Java 文件。即以注解作为桥梁,通过预先规定好的代码生成规则来自动生成 Java 文件。此类注解框架的代表有 ButterKnife、Dragger2、EventBus

Java API 已经提供了扫描源码并解析注解的框架,开发者可以通过继承 AbstractProcessor 类来实现自己的注解解析逻辑。APT 的原理就是在注解了某些代码元素(如字段、函数、类等)后,在编译时编译器会检查 AbstractProcessor 的子类,并且自动调用其 process() 方法,然后将添加了指定注解的所有代码元素作为参数传递给该方法,开发者再根据注解元素在编译期输出对应的 Java 代码

关于 APT 技术的原理和应用可以看这篇文章:Android APT 实例讲解

ARouter 源码中和注解处理器相关的 module 有两个:

  • arouter-annotation。Java Module,包含了像 Autowired、Interceptor 这些注解以及 RouteMeta 等 JavaBean

  • arouter-compiler。Android Module,包含了多个 AbstractProcessor 的实现类用于生成代码

这里主要来看 arouter-compiler,这里以自定义的拦截器 NothingInterceptor 作为例子

package github.leavesc.user

/**

  • 作者:leavesC

  • 时间:2020/10/5 11:49

  • 描述:

  • GitHub:https://github.com/leavesC

*/

@Interceptor(priority = 100, name = “啥也不做的拦截器”)

class NothingInterceptor : IInterceptor {

override fun init(context: Context) {

}

override fun process(postcard: Postcard, callback: InterceptorCallback) {

//不拦截,任其跳转

callback.onContinue(postcard)

}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

结尾

如何才能让我们在面试中对答如流呢?

答案当然是平时在工作或者学习中多提升自身实力的啦,那如何才能正确的学习,有方向的学习呢?为此我整理了一份Android学习资料路线:

这里是一份BAT大厂面试资料专题包:

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

cessor** 的子类,并且自动调用其 process() 方法,然后将添加了指定注解的所有代码元素作为参数传递给该方法,开发者再根据注解元素在编译期输出对应的 Java 代码

关于 APT 技术的原理和应用可以看这篇文章:Android APT 实例讲解

ARouter 源码中和注解处理器相关的 module 有两个:

  • arouter-annotation。Java Module,包含了像 Autowired、Interceptor 这些注解以及 RouteMeta 等 JavaBean

  • arouter-compiler。Android Module,包含了多个 AbstractProcessor 的实现类用于生成代码

这里主要来看 arouter-compiler,这里以自定义的拦截器 NothingInterceptor 作为例子

package github.leavesc.user

/**

  • 作者:leavesC

  • 时间:2020/10/5 11:49

  • 描述:

  • GitHub:https://github.com/leavesC

*/

@Interceptor(priority = 100, name = “啥也不做的拦截器”)

class NothingInterceptor : IInterceptor {

override fun init(context: Context) {

}

override fun process(postcard: Postcard, callback: InterceptorCallback) {

//不拦截,任其跳转

callback.onContinue(postcard)

}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-VnUymBOa-1713295623199)]

[外链图片转存中…(img-ldsKkTLD-1713295623200)]

[外链图片转存中…(img-52Axxc9B-1713295623201)]

[外链图片转存中…(img-TysbHzxT-1713295623202)]

[外链图片转存中…(img-iq1OLkdA-1713295623202)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

结尾

如何才能让我们在面试中对答如流呢?

答案当然是平时在工作或者学习中多提升自身实力的啦,那如何才能正确的学习,有方向的学习呢?为此我整理了一份Android学习资料路线:

[外链图片转存中…(img-kne3hnVQ-1713295623203)]

这里是一份BAT大厂面试资料专题包:

[外链图片转存中…(img-LiZaxuy6-1713295623204)]

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值