公众号:字节数组,但愿对你有所帮助 🤣🤣java
对于 Android Developer 来讲,不少开源库都是属于开发必备的知识点,从使用方式到实现原理再到源码解析,这些都须要咱们有必定程度的了解和运用能力。因此我打算来写一系列关于开源库源码解析和实战演练的文章,初定的目标是 EventBus、ARouter、LeakCanary、Retrofit、Glide、OkHttp、Coil 等七个知名开源库,但愿对你有所帮助 🤣🤣android
系列文章导航:git
路由框架在大型项目中比较常见,特别是在项目中拥有多个 module 的时候。为了实现组件化,多个 module 间的通讯就不能直接以模块间的引用来实现,此时就须要依赖路由框架来实现模块间的通讯和解耦github
而 ARouter 就是一个用于帮助 Android App 进行组件化改造的框架,支持模块间的路由、通讯、解耦api
以上介绍来自于 ARouter 的 Github 官网:README_CN数组
本文就基于其当前(2020/10/04)ARouter 的最新版本,对 ARouter 进行一次全面的源码解析和原理介绍,作到知其然也知其因此然,但愿对你有所帮助 😂😂缓存
dependencies {
implementation 'com.alibaba:arouter-api:1.5.0'
kapt 'com.alibaba:arouter-compiler:1.2.2'
}
复制代码
假设存在一个包含多个 module 的项目,在名为 user 的 module 中存在一个 UserHomeActivity
,其对应的路由路径是 /account/userHome
。那么,当咱们要从其它 module 跳转到该页面时,只须要指定 path 来跳转便可markdown
package github.leavesc.user
/** * 做者:leavesC * 时间:2020/10/3 18:05 * 描述: * GitHub:https://github.com/leavesC */
@Route(path = RoutePath.USER_HOME)
class UserHomeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_home)
}
}
//其它页面使用以下代码来跳转到 UserHomeActivity
ARouter.getInstance().build(RoutePath.USER_HOME).navigation()
复制代码
只根据一个 path,ARouter 是如何定位到特定的 Activity 的呢?这就须要经过在编译阶段生成辅助代码来实现了。咱们知道,想要跳转到某个 Activity,那么就须要拿到该 Activity 的 Class 对象才行。在编译阶段,ARouter 会根据咱们设定的路由跳转规则来自动生成映射文件,映射文件中就包含了 path 和 ActivityClass 之间的对应关系app
例如,对于 UserHomeActivity
,在编译阶段就会自动生成如下辅助文件。能够看到,ARouter$$Group$$account
类中就将 path 和 ActivityClass 做为键值对保存到了 Map 中。ARouter 就是依靠此来进行跳转的框架
package com.alibaba.android.arouter.routes;
/** * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$account 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));
}
}
复制代码
还有一个重点须要注意,就是这类自动生成的文件的包名路径都是 com.alibaba.android.arouter.routes
,且类名前缀也是有特定规则的。虽然 ARouter$$Group$$account
类实现了将对应关系保存到 Map 的逻辑,可是 loadInto
方法仍是须要由 ARouter 在运行时来调用,那么 ARouter 就须要拿到 ARouter$$Group$$account
这个类才行,而 ARouter 就是经过扫描 com.alibaba.android.arouter.routes
这个包名路径来获取全部辅助文件的
ARouter 的基本实现思路就是:
@Route
注解修饰目标类ARouter 通常是经过在 Application 中调用 init
方法来完成初始化的,这里先来看下其初始化流程
/** * 做者:leavesC * 时间:2020/10/4 18:05 * 描述: * GitHub:https://github.com/leavesC */
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
ARouter.openDebug()
ARouter.openLog()
}
ARouter.init(this)
}
}
复制代码
ARouter 类使用了单例模式,ARouter 类只是负责对外暴露能够由外部调用的 API,大部分的实现逻辑仍是转交由 _ARouter
类来完成
public final class ARouter {
private volatile static ARouter instance = null;
private ARouter() {
}
/** * Get instance of router. A * All feature U use, will be starts here. */
public static ARouter getInstance() {
if (!hasInit) {
throw new InitException("ARouter::Init::Invoke init(context) first!");
} else {
if (instance == null) {
synchronized (ARouter.class) {
if (instance == null) {
instance = new ARouter();
}
}
}
return instance;
}
}
/** * Init, it must be call before used router. */
public static void init(Application application) {
if (!hasInit) { //防止重复初始化
logger = _ARouter.logger;
_ARouter.logger.info(Consts.TAG, "ARouter init start.");
//经过 _ARouter 来完成初始化
hasInit = _ARouter.init(application);
if (hasInit) {
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");
}
}
···
}
复制代码
_ARouter
类是包私有权限,也使用了单例模式,其 init(Application)
方法的重点就在于 LogisticsCenter.init(mContext, executor)
final class _ARouter {
private volatile static _ARouter instance = null;
private _ARouter() {
}
protected static _ARouter getInstance() {
if (!hasInit) {
throw new InitException("ARouterCore::Init::Invoke init(context) first!");
} else {
if (instance == null) {
synchronized (_ARouter.class) {
if (instance == null) {
instance = new _ARouter();
}
}
}
return instance;
}
}
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
就实现了前文说的扫描特定包名路径拿到全部自动生成的辅助文件的逻辑,即在进行初始化的时候,咱们就须要加载到当前项目一共包含的全部 group,以及每一个 group 对应的路由信息表,其主要逻辑是:
com.alibaba.android.arouter.routes
这个包下自动生成的辅助文件的全路径,经过判断路径名的前缀字符串,就能够知道该类文件对应什么类型,而后经过反射构建不一样类型的对象,经过调用对象的方法将路由信息存到 Warehouse
的 Map 中。至此,整个初始化流程就结束了public class LogisticsCenter {
/** * LogisticsCenter init, load all metas in memory. Demand initialization */
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
long startInit = System.currentTimeMillis();
//billy.qi modified at 2017-12-06
//load by plugin first
loadRouterMap();
if (registerByPlugin) {
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
Set<String> routerMap;
//若是当前开启了 debug 模式或者经过本地 SP 缓存判断出 app 的版本先后发生了变化
//那么就从新获取路由信息,不然就从使用以前缓存到 SP 中的数据
// 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.
//获取 ROUTE_ROOT_PAKCAGE 包名路径下包含的全部的 ClassName
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
//缓存到 SP 中
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}
//更新 App 的版本信息
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<String>()));
}
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,放在名为 user
的 module 中,按照 ARouter 的要求,在 gradle 文件中为该 module 声明了如下配置,将 projectName 做为参数值。UserHomeActivity 对应的 path 是 /account/userHome
,ARouter 默认会将 path 的第一个单词即 account
做为其 group
javaCompileOptions {
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
}
复制代码
ARouter 在经过注解处理器生成辅助文件的时候,类名就会根据以上信息来生成,因此最终就会生成如下两个文件:
package com.alibaba.android.arouter.routes;
/** * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$user implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("account", ARouter$$Group$$account.class);
}
}
package com.alibaba.android.arouter.routes;
/** * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$account 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));
}
}
复制代码
LogisticsCenter 的 init
方法就会根据文件名的固定前缀 ARouter$$Root$$
扫描到 ARouter$$Root$$user
这个类,而后经过反射构建出该对象,而后经过调用其 loadInto
方法将键值对保存到 Warehouse.groupsIndex
中。等到后续须要跳转到 group
为 account
的页面时,就会再来反射调用 ARouter$$Group$$account
的 loadInto
方法,即按需加载,等到须要用到的时候再来获取详细的路由对应信息
由于对于一个大型的 App 来讲,可能包含几十甚至几百个页面,若是一次性将全部路由信息都加载到内存中,对于内存的压力是比较大的,而用户每次使用可能也只会打开十几个页面,因此这是就实现了按需加载
讲完初始化流程,再来看下 ARouter 实现 Activity 跳转的流程
跳转到 Activity 最简单的方式就是只指定 path:
ARouter.getInstance().build(RoutePath.USER_HOME).navigation()
复制代码
build()
方法会经过 ARouter
中转调用到 _ARouter
的 build()
方法,最终返回一个 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;
}
复制代码
Postcard 的 navigation()
方法又会调用到 _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 初始化流程的时候有讲到:等到后续须要跳转到 group
为 account
的页面时,就会再来反射调用 ARouter$$Group$$account
的 loadInto
方法,即按需加载,等到须要的时候再来获取详细的路由对应信息
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());
//省略一些和本例子无关的代码
···
}
}
复制代码
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$$ARouter$$Autowired 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<String> 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$$Group$$account 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$$Providers$$user 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$$Root$$user implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("account", ARouter$$Group$$account.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> 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$$Interceptors$$user 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);
}
}
···
}
复制代码
而后,在 _ARouter
的 navigation
方法中,如何判断到这次路由请求没有开启绿色通道模式的话,那么就会将这次请求转交给 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
的主要逻辑是:
InterceptorServiceImpl
实例的时候,其 init
方法会立刻被调用,该方法内部会交由线程池来执行,经过反射生成每一个拦截器对象,并调用每一个拦截器的 init
方法来完成拦截器的初始化,并将每一个拦截器对象都存到 Warehouse.interceptors
中。若是初始化完成了,则唤醒等待在 interceptorInitLock
上的线程doInterceptions
方法被调用时,若是此时第一个步骤还未执行完的话,则会经过 checkInterceptorsInitStatus()
方法等待第一个步骤执行完成。若是十秒内都未完成的话,则走失败流程直接返回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 关于注解处理器的实现原理
注解处理器(Annotation Processing Tool)是一种注解处理工具,用来在编译期扫描和处理注解,经过注解来生成 Java 文件。即以注解做为桥梁,经过预先规定好的代码生成规则来自动生成 Java 文件。此类注解框架的表明有 ButterKnife、Dragger二、EventBus 等
Java API 已经提供了扫描源码并解析注解的框架,开发者能够经过继承 AbstractProcessor 类来实现本身的注解解析逻辑。APT 的原理就是在注解了某些代码元素(如字段、函数、类等)后,在编译时编译器会检查 AbstractProcessor 的子类,而且自动调用其 process()
方法,而后将添加了指定注解的全部代码元素做为参数传递给该方法,开发者再根据注解元素在编译期输出对应的 Java 代码
关于 APT 技术的原理和应用能够看这篇文章:Android APT 实例讲解
ARouter 源码中和注解处理器相关的 module 有两个:
这里主要来看 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$$Interceptors$$user implements IInterceptorGroup {
@Override
public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
interceptors.put(100, NothingInterceptor.class);
}
}
复制代码
那么,生成的辅助文件咱们就要包含如下几个元素:
若是经过硬编码的形式,即经过拼接字符串的方式来生成以上代码也是能够的,可是这样会使得代码很差维护且可读性很低,因此 ARouter 是经过 JavaPoet
这个开源库来生成代码的。JavaPoet
是 square 公司开源的 Java 代码生成框架,能够很方便地经过其提供的 API 来生成指定格式(修饰符、返回值、参数、函数体等)的代码
拦截器对应的 AbstractProcessor
子类就是 InterceptorProcessor
,其主要逻辑是:
@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());
Interceptor interceptor = element.getAnnotation(Interceptor.class);
Element lastInterceptor = interceptors.get(interceptor.priority());
if (null != lastInterceptor) { // Added, throw exceptions
//不为 null 说明存在两个拦截器其优先级相等,这是不容许的,直接抛出异常
throw new IllegalArgumentException(
String.format(Locale.getDefault(), "More than one interceptors use same priority [%d], They are [%s] and [%s].",
interceptor.priority(),
lastInterceptor.getSimpleName(),
element.getSimpleName())
);
}
//将拦截器按照优先级高低进行排序保存
interceptors.put(interceptor.priority(), element);
} else {
logger.error("A interceptor verify failed, its " + element.asType());
}
}
// Interface of ARouter.
//拿到 com.alibaba.android.arouter.facade.template.IInterceptor 这个接口的类型抽象
TypeElement type_ITollgate = elementUtils.getTypeElement(IINTERCEPTOR);
//拿到 com.alibaba.android.arouter.facade.template.IInterceptorGroup 这个接口的类型抽象
TypeElement type_ITollgateGroup = elementUtils.getTypeElement(IINTERCEPTOR_GROUP);
/** * Build input type, format as : * * ```Map<Integer, Class<? extends ITollgate>>``` */
//生成对 Map<Integer, Class<? extends IInterceptor>> 这段代码的抽象封装
ParameterizedTypeName inputMapTypeOfTollgate = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(Integer.class),
ParameterizedTypeName.get(
ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(type_ITollgate))
)
);
// Build input param name.
//生成 loadInto 方法的入参参数 interceptors
ParameterSpec tollgateParamSpec = ParameterSpec.builder(inputMapTypeOfTollgate, "interceptors").build();
// Build method : 'loadInto'
//生成 loadInto 方法
MethodSpec.Builder loadIntoMethodOfTollgateBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(tollgateParamSpec);
// Generate
if (null != interceptors && interceptors.size() > 0) {
// Build method body
for (Map.Entry<Integer, Element> entry : interceptors.entrySet()) {
//遍历每一个拦截器,生成 interceptors.put(100, NothingInterceptor.class); 这类型的代码
loadIntoMethodOfTollgateBuilder.addStatement("interceptors.put(" + entry.getKey() + ", $T.class)", ClassName.get((TypeElement) entry.getValue()));
}
}
// Write to disk(Write file even interceptors is empty.)
//包名固定是 PACKAGE_OF_GENERATE_FILE,即 com.alibaba.android.arouter.routes
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(NAME_OF_INTERCEPTOR + SEPARATOR + moduleName) //设置类名
.addModifiers(PUBLIC) //添加 public 修饰符
.addJavadoc(WARNING_TIPS) //添加注释
.addMethod(loadIntoMethodOfTollgateBuilder.build()) //添加 loadInto 方法
.addSuperinterface(ClassName.get(type_ITollgateGroup)) //最后生成的类同时实现了 IInterceptorGroup 接口
.build()
).build().writeTo(mFiler);
logger.info(">>> Interceptor group write over. <<<");
}
}
/** * Verify inteceptor meta * * @param element Interceptor taw type * @return verify result */
private boolean verify(Element element) {
Interceptor interceptor = element.getAnnotation(Interceptor.class);
// It must be implement the interface IInterceptor and marked with annotation Interceptor.
return null != interceptor && ((TypeElement) element).getInterfaces().contains(iInterceptor);
}
}
复制代码
ARouter 的实现原理和源码解析都讲得差很少了,自认仍是讲得挺全面的,那么下一篇就再来进入实战篇吧,本身来动手实现一个简易版本的 ARouter 😂😂