IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> ARouter源码详解,零基础学习android编程 -> 正文阅读

[移动开发]ARouter源码详解,零基础学习android编程

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

复制代码

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

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)

}

}

复制代码

生成的辅助文件:

package com.alibaba.android.arouter.routes;

import com.alibaba.android.arouter.facade.template.IInterceptor;

import com.alibaba.android.arouter.facade.template.IInterceptorGroup;

import github.leavesc.user.NothingInterceptor;

import java.lang.Class;

import java.lang.Integer;

import java.lang.Override;

import java.util.Map;

/**

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

}

}

复制代码

那么,生成的辅助文件我们就要包含以下几个元素:

  1. 包名

  2. 导包

  3. 注释

  4. 实现类及继承的接口

  5. 包含的方法及方法参数

  6. 方法体

  7. 修饰符

如果通过硬编码的形式,即通过拼接字符串的方式来生成以上代码也是可以的,但是这样会使得代码不好维护且可读性很低,所以 ARouter 是通过 JavaPoet 这个开源库来生成代码的。JavaPoet是 square 公司开源的 Java 代码生成框架,可以很方便地通过其提供的 API 来生成指定格式(修饰符、返回值、参数、函数体等)的代码

拦截器对应的 AbstractProcessor 子类就是 InterceptorProcessor,其主要逻辑是:

  1. 在 process 方法中通过 RoundEnvironment 拿到所有使用了 @Interceptor 注解进行修饰的代码元素 elements,然后遍历所有 item

  2. 判断每个 item 是否继承了 IInterceptor 接口,是的话则说明该 item 就是我们要找的拦截器实现类

  3. 获取每个 item 包含的 @Interceptor 注解对象,根据我们为之设定的优先级 priority,将每个 item 按顺序存到 interceptors 中

  4. 如果存在两个拦截器的优先级相同,那么就抛出异常

  5. 将所有拦截器按顺序存入 interceptors 后,通过 JavaPoet 提供的 API 来生成包名、导包、注释、实现类等多个代码元素,并最终生成一个完整的类文件

@AutoService(Processor.class)

@SupportedAnnotationTypes(ANNOTATION_TYPE_INTECEPTOR)

public class InterceptorProcessor extends BaseProcessor {

//用于保存拦截器,按照优先级高低进行排序

private Map<Integer, Element> interceptors = new TreeMap<>();

private TypeMirror iInterceptor = null;

@Override

public synchronized void init(ProcessingEnvironment processingEnv) {

super.init(processingEnv);

iInterceptor = elementUtils.getTypeElement(Consts.IINTERCEPTOR).asType();

logger.info(">>> InterceptorProcessor init. <<<");

}

/**

  • {@inheritDoc}

  • @param annotations

  • @param roundEnv

*/

@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

if (CollectionUtils.isNotEmpty(annotations)) {

//拿到所有使用了 @Interceptor 进行修饰的代码元素

Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Interceptor.class);

try {

parseInterceptors(elements);

} catch (Exception e) {

logger.error(e);

}

return true;

}

return false;

}

/**

  • Parse tollgate.

  • @param elements elements of tollgate.

*/

private void parseInterceptors(Set<? extends Element> elements) throws IOException {

if (CollectionUtils.isNotEmpty(elements)) {

logger.info(">>> Found interceptors, size is " + elements.size() + " <<<");

// Verify and cache, sort incidentally.

for (Element element : elements) {

//判断使用了 @Interceptor 进行修饰的代码元素是否同时实现了 com.alibaba.android.arouter.facade.template.IInterceptor 这个接口

//两者缺一不可

if (verify(element)) { // Check the interceptor meta

logger.info("A interceptor verify over, its " + element.asType());

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-11 15:50:25  更:2021-12-11 15:52:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:47:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码