ARouter源码详解

  1. 在运行阶段,应用通过 path 来发起请求,ARouter 根据 path 从 Map 中取值,从而拿到目标类

三、初始化

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 对应的路由信息表,其主要逻辑是:

  1. 如果当前开启了 debug 模式或者通过本地 SP 缓存判断出 app 的版本前后发生了变化,那么就重新获取全局路由信息,否则就从使用之前缓存到 SP 中的数据

  2. 获取全局路由信息是一个比较耗时的操作,所以 ARouter 就通过将全局路由信息缓存到 SP 中来实现复用。但由于在开发阶段开发者可能随时就会添加新的路由表,而每次发布新版本正常来说都是会加大应用的版本号的,所以 ARouter 就只在开启了 debug 模式或者是版本号发生了变化的时候才会重新获取路由信息

  3. 获取到的路由信息中包含了在 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 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()));

}

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)

}

}

}

《设计思想解读开源框架》

第一章、 热修复设计

  • 第一节、 AOT/JIT & dexopt 与 dex2oat

  • 第二节、 热修复设计之 CLASS_ISPREVERIFIED 问题

  • 第三节、热修复设计之热修复原理

  • 第四节、Tinker 的集成与使用(自动补丁包生成)

    第二章、 插件化框架设计

  • 第一节、 Class 文件与 Dex 文件的结构解读

  • 第二节、 Android 资源加载机制详解

  • 第三节、 四大组件调用原理

  • 第四节、 so 文件加载机制

  • 第五节、 Android 系统服务实现原理

    第三章、 组件化框架设计

  • 第一节、阿里巴巴开源路由框——ARouter 原理分析

  • 第二节、APT 编译时期自动生成代码&动态类加载

  • 第三节、 Java SPI 机制

  • 第四节、 AOP&IOC

  • 第五节、 手写组件化架构

    第四章、图片加载框架

  • 第一节、图片加载框架选型

  • 第二节、Glide 原理分析

  • 第三节、手写图片加载框架实战

    第五章、网络访问框架设计

  • 第一节、网络通信必备基础

  • 第二节、OkHttp 源码解读

  • 第三节、Retrofit 源码解析

    第六章、 RXJava 响应式编程框架设计

  • 第一节、链式调用

  • 第二节、 扩展的观察者模式

  • 第三节、事件变换设计

  • 第四节、Scheduler 线程控制

    第七章、 IOC 架构设计

  • 第一节、 依赖注入与控制反转

  • 第二节、ButterKnife 原理上篇、中篇、下篇

  • 第三节、Dagger 架构设计核心解密

    第八章、 Android 架构组件 Jetpack

  • 第一节、 LiveData 原理

  • 第二节、 Navigation 如何解决 tabLayout 问题

  • 第三节、 ViewModel 如何感知 View 生命周期及内核原理

  • 第四节、 Room 架构方式方法

  • 第五节、 dataBinding 为什么能够支持 MVVM

  • 第六节、 WorkManager 内核揭秘

  • 第七节、 Lifecycles 生命周期


    本文包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

9690)]

第一章、 热修复设计

  • 第一节、 AOT/JIT & dexopt 与 dex2oat

  • 第二节、 热修复设计之 CLASS_ISPREVERIFIED 问题

  • 第三节、热修复设计之热修复原理

  • 第四节、Tinker 的集成与使用(自动补丁包生成)

    [外链图片转存中…(img-fJjE8hPF-1714480979691)]

    第二章、 插件化框架设计

  • 第一节、 Class 文件与 Dex 文件的结构解读

  • 第二节、 Android 资源加载机制详解

  • 第三节、 四大组件调用原理

  • 第四节、 so 文件加载机制

  • 第五节、 Android 系统服务实现原理

    [外链图片转存中…(img-YZUhZnYg-1714480979691)]

    第三章、 组件化框架设计

  • 第一节、阿里巴巴开源路由框——ARouter 原理分析

  • 第二节、APT 编译时期自动生成代码&动态类加载

  • 第三节、 Java SPI 机制

  • 第四节、 AOP&IOC

  • 第五节、 手写组件化架构

    [外链图片转存中…(img-inNyekMa-1714480979691)]

    第四章、图片加载框架

  • 第一节、图片加载框架选型

  • 第二节、Glide 原理分析

  • 第三节、手写图片加载框架实战

    [外链图片转存中…(img-gqlamx63-1714480979692)]

    第五章、网络访问框架设计

  • 第一节、网络通信必备基础

  • 第二节、OkHttp 源码解读

  • 第三节、Retrofit 源码解析

    [外链图片转存中…(img-CAkkpVIO-1714480979692)]

    第六章、 RXJava 响应式编程框架设计

  • 第一节、链式调用

  • 第二节、 扩展的观察者模式

  • 第三节、事件变换设计

  • 第四节、Scheduler 线程控制

    [外链图片转存中…(img-LCOKs1XR-1714480979692)]

    第七章、 IOC 架构设计

  • 第一节、 依赖注入与控制反转

  • 第二节、ButterKnife 原理上篇、中篇、下篇

  • 第三节、Dagger 架构设计核心解密

    [外链图片转存中…(img-QznxTf27-1714480979692)]

    第八章、 Android 架构组件 Jetpack

  • 第一节、 LiveData 原理

  • 第二节、 Navigation 如何解决 tabLayout 问题

  • 第三节、 ViewModel 如何感知 View 生命周期及内核原理

  • 第四节、 Room 架构方式方法

  • 第五节、 dataBinding 为什么能够支持 MVVM

  • 第六节、 WorkManager 内核揭秘

  • 第七节、 Lifecycles 生命周期

    [外链图片转存中…(img-DRbGQs7H-1714480979693)]
    本文包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…
    [外链图片转存中…(img-nOmtfmXh-1714480979693)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 20
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值