2024年安卓最新关于阿里推出的路由框架ARouter源码解析,vivo安卓开发面试

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上我搜集整理的2019-2021BATJ 面试真题解析,我把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节。

节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960全网最全Android开发笔记》

《379页Android开发面试宝典》

历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?

1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析

资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图

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

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

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

// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {

// application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());

// }

return true;

}

这里实际上调用到了 LogisticsCenter.init

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

mContext = context;

executor = tpe;

try {

long startInit = System.currentTimeMillis();

Set routerMap;

// 获取存储 ClassName 集合的 routerMap(debug 模式下每次都会拿最新的)

if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {

logger.info(TAG, “Run with debug mode or new install, rebuild router map.”);

// 根据指定的 packageName 获取 package 下的所有 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();

}

} else {

logger.info(TAG, “Load router map from cache.”);

// release 模式下,已经缓存了 ClassName 列表

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

// 遍历 ClassName

for (String className : routerMap) {

if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {

// 发现是 Root,加载类构建对象后通过 loadInto 加载进 Warehouse.groupsIndex

((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);

} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {

// 发现是 Interceptor,加载类构建对象后通过 loadInto 加载进 Warehouse.interceptorsIndex

((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);

} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {

// 发现是 ProviderGroup,加载类构建对象后通过 loadInto 加载进 Warehouse.providersIndex

((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);

}

}

// …

} catch (Exception e) {

throw new HandlerException(TAG + “ARouter init logistics center exception! [” + e.getMessage() + “]”);

}

}

这里主要有如下几步:

1.获取 com.alibaba.android.arouter.routes 下存储 ClassName 的集合 routerMap

2.若为 debug 模式或之前没有解析过 routerMap,则通过 ClassUtils.getFileNameByPackageName 方法对指定 package 下的所有 ClassName 进行解析并存入 SP。

3.若并非 debug 模式,并且之前已经解析过,则直接从 SP 中取出。(debug 每次都需要更新,因为类会随着代码的修改而变动)

4.遍历 routerMap 中的 ClassName

  • 如果是 RouteRoot,则加载类构建对象后通过 loadInto 加载进 Warehouse.groupsIndex

  • 如果是 InterceptorGroup,则加载类构建对象后通过 loadInto 加载进 Warehouse.interceptorsIndex。

  • 如果是 ProviderGroup,则加载类构建对象后通过 loadInto 加载进 Warehouse.providersIndex。

解析 ClassName

我们先看看 ClassUtils.getFileNameByPackageName 是如何对指定 package 下的 ClassName 集合进行解析的:

public static Set getFileNameByPackageName(Context context, final String packageName) {

final Set classNames = new HashSet<>();

// 通过 getSourcePaths 方法获取 dex 文件 path 集合

List paths = getSourcePaths(context);

// 通过 CountDownLatch 对 path 的遍历处理进行控制

final CountDownLatch parserCtl = new CountDownLatch(paths.size());

// 遍历 path,通过 DefaultPoolExecutor 并发对 path 进行处理

for (final String path : paths) {

DefaultPoolExecutor.getInstance().execute(new Runnable() {

@Override

public void run() {

// 加载 path 对应的 dex 文件

DexFile dexfile = null;

try {

if (path.endsWith(EXTRACTED_SUFFIX)) {

// zip 结尾通过 DexFile.loadDex 进行加载

dexfile = DexFile.loadDex(path, path + “.tmp”, 0);

} else {

// 否则通过 new DexFile 加载

dexfile = new DexFile(path);

}

// 遍历 dex 中的 Entry

Enumeration dexEntries = dexfile.entries();

while (dexEntries.hasMoreElements()) {

// 如果是对应的 package 下的类,则添加其 className

String className = dexEntries.nextElement();

if (className.startsWith(packageName)) {

classNames.add(className);

}

}

} catch (Throwable ignore) {

Log.e(“ARouter”, “Scan map file in dex files made error.”, ignore);

} finally {

if (null != dexfile) {

try {

dexfile.close();

} catch (Throwable ignore) {

}

}

parserCtl.countDown();

}

}

});

}

// 所有 path 处理完成后,继续向下走

parserCtl.await();

Log.d(Consts.TAG, “Filter " + classNames.size() + " classes by packageName <” + packageName + “>”);

return classNames;

}

这里的步骤比较简单,主要是如下的步骤:

1.通过 getSourcePaths 方法获取 dex 文件的 path 集合。

2.创建了一个 CountDownLatch 控制 dex 文件的并行处理,以加快速度。

3.遍历 path 列表,通过 DefaultPoolExecutor 对 path 并行处理。

4.加载 path 对应的 dex 文件,并对其中的 Entry 进行遍历,若发现了对应 package 下的 ClassName,将其加入结果集合。

5.所有 dex 处理完成后,返回结果。

关于 getSourcePaths 如何获取到的 dex 集合这里就不纠结了,因为我们的关注点不在这里。

Warehouse 实际上就是仓库的意思,它存放了 ARouter 自动生成的类(RouteRoot、InterceptorGroup、ProviderGroup)的信息

我们先看看 Warehouse 类究竟是怎样的:

class Warehouse {

// 保存 RouteGroup 对应的 class 以及 RouteMeta

static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();

static Map<String, RouteMeta> routes = new HashMap<>();

// 保存 Provider 以及 RouteMeta

static Map<Class, IProvider> providers = new HashMap<>();

static Map<String, RouteMeta> providersIndex = new HashMap<>();

// 保存 Interceptor 对应的 class 以及 Inteceptor

static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>(“More than one interceptors use same priority [%s]”);

static List interceptors = new ArrayList<>();

static void clear() {

routes.clear();

groupsIndex.clear();

providers.clear();

providersIndex.clear();

interceptors.clear();

interceptorsIndex.clear();

}

}

可以发现 Warehouse 就是一个纯粹用来存放信息的仓库类,它的数据的实际上是通过上面的几个自动生成的类在 loadInto 中对 Warehouse 主动填入数据实现的。

例如我们打开一个自动生成的 IRouteRoot 的实现类:

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

@Override

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

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

}

}

可以看到,它在 groupsIndex 中对这个 RouteRoot 中的 IRouteGroup 进行了注册,也就是向 groupIndex 中注册了 Route Group 对应的 IRouteGroup 类。其他类也是一样,通过自动生成的代码将数据填入 Map 或 List 中。

可以发现,初始化过程主要完成了对自动生成的路由相关类 RouteRoot、Interceptor、ProviderGroup的加载,对它们通过反射构造后将信息加载进了 Warehouse 类中

路由跳转

Postcard 的创建

下面我们看看路由的跳转是如何实现的,我们先看到 ARouter.build 方法:

public Postcard build(String path) {

return _ARouter.getInstance().build(path);

}

它转调到了 _ARouter 的 build 方法:

protected Postcard build(String path) {

if (TextUtils.isEmpty(path)) {

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

} else {

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

if (null != pService) {

path = pService.forString(path);

}

return build(path, extractGroup(path));

}

}

它首先通过 ARouter.navigation 获取到了 PathReplaceService,它需要用户进行实现,若没有实现会返回 null,若有实现则调用了它的 forString 方法传入了用户的 Route Path 进行路径的预处理。

最后转调到了 build(path, group),group 通过 extractGroup 得到:

private String extractGroup(String path) {

if (TextUtils.isEmpty(path) || !path.startsWith(“/”)) {

throw new HandlerException(Consts.TAG + “Extract the default group failed, the path must be start with ‘/’ and contain more than 2 ‘/’!”);

}

try {

String defaultGroup = path.substring(1, path.indexOf(“/”, 1));

if (TextUtils.isEmpty(defaultGroup)) {

throw new HandlerException(Consts.TAG + “Extract the default group failed! There’s nothing between 2 ‘/’!”);

} else {

return defaultGroup;

}

} catch (Exception e) {

logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage());

return null;

}

}

extractGroup 实际上就是对字符串处理,取出 Route 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);

}

}

build(path, group) 方法同样也会尝试获取到 PathReplaceService 并对 path 进行预处理。之后通过 path 与 group 构建了一个 Postcard 类:

public Postcard(String path, String group) {

this(path, group, null, null);

}

public Postcard(String path, String group, Uri uri, Bundle bundle) {

setPath(path);

setGroup(group);

setUri(uri);

this.mBundle = (null == bundle ? new Bundle() : bundle);

}

这里最终调用到了 PostCard(path, group, uri, bundle),这里只是进行了一些参数的设置。

之后,如果我们调用 withIntwithDouble 等方法,就可以进行参数的设置。例如withInt方法:

public Postcard withInt(@Nullable String key, int value) {

mBundle.putInt(key, value);

return this;

}

它实际上就是在对 Bundle 中设置对应的 key、value。

最后我们通过 navigation 即可实现最后的跳转:

public Object navigation() {

return navigation(null);

}

public Object navigation(Context context) {

return navigation(context, null);

}

public Object navigation(Context context, NavigationCallback callback) {

return ARouter.getInstance().navigation(context, this, -1, callback);

}

public void navigation(Activity mContext, int requestCode) {

navigation(mContext, requestCode, null);

}

public void navigation(Activity mContext, int requestCode, NavigationCallback callback) {

ARouter.getInstance().navigation(mContext, this, requestCode, callback);

}

通过如上的 navigation 可以看到,实际上它们都是最终调用到 ARouter.navigation 方法,在没有传入 Context 时会使用 Application 初始化的 Context,并且可以通过 NavigationCallback 对 navigation 的过程进行监听。

public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {

return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);

}

ARouter 仍然只是将请求转发到了_ARouter

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

try {

// 通过 LogisticsCenter.completion 对 postcard 进行补全

LogisticsCenter.completion(postcard);

} catch (NoRouteFoundException ex) {

// …

}

if (null != callback) {

callback.onFound(postcard);

}

// 如果设置了 greenChannel,会跳过所有拦截器的执行

if (!postcard.isGreenChannel()) {

// 没有跳过拦截器,对 postcard 的所有拦截器进行执行

interceptorService.doInterceptions(postcard, new InterceptorCallback() {

@Override

public void onContinue(Postcard postcard) {

_navigation(context, postcard, requestCode, callback);

}

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

}

上面的代码主要有以下步骤:

  • 通过 LogisticsCenter.completion 对 postcard 进行补全。

  • 如果 postcard 没有设置 greenChannel,则对 postcard 的拦截器进行执行,执行完成后调用 _navigation 方法真正实现跳转。

  • 如果 postcard 设置了 greenChannel,则直接跳过所有拦截器,调用 _navigation 方法真正实现跳转。

Postcard 的补全

我们看看 LogisticsCenter.completion 是如何实现 postcard 的补全的:

public synchronized static void completion(Postcard postcard) {

if (null == postcard) {

throw new NoRouteFoundException(TAG + “No postcard!”);

}

// 通过 Warehouse.routes.get 尝试获取 RouteMeta

RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());

if (null == routeMeta) {

// 若 routeMeta 为 null,可能是并不存在,或是还没有加载进来

// 尝试获取 postcard 的 RouteGroup

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

if (null == groupMeta) {

throw new NoRouteFoundException(TAG + “There is no route match the path [” + postcard.getPath() + “], in group [” + postcard.getGroup() + “]”);

} else {

// …

// 如果找到了对应的 RouteGroup,则将其加载进来并重新调用 completion 进行补全

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

iGroupInstance.loadInto(Warehouse.routes);

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

// …

completion(postcard); // Reload

}

} else {

// 如果找到了对应的 routeMeta,将它的信息设置进 postcard 中

postcard.setDestination(routeMeta.getDestination());

postcard.setType(routeMeta.getType());

postcard.setPriority(routeMeta.getPriority());

postcard.setExtra(routeMeta.getExtra());

Uri rawUri = postcard.getUri();

// 将 uri 中的参数设置进 bundle 中

if (null != rawUri) {

Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);

Map<String, Integer> paramsType = routeMeta.getParamsType();

if (MapUtils.isNotEmpty(paramsType)) {

// Set value by its type, just for params which annotation by @Param

for (Map.Entry<String, Integer> params : paramsType.entrySet()) {

setValue(postcard,

params.getValue(),

params.getKey(),

resultMap.get(params.getKey()));

}

// Save params name which need auto inject.

postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));

}

// Save raw uri

postcard.withString(ARouter.RAW_URI, rawUri.toString());

}

// 对于 provider 和 fragment,进行特殊处理

switch (routeMeta.getType()) {

case PROVIDER:

// 如果是一个 provider,尝试从 Warehouse 中查找它的类并构造对象,然后将其设置到 provider

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

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

// provider 和 fragment 都会跳过拦截器

postcard.greenChannel();

break;

case FRAGMENT:

// provider 和 fragment 都会跳过拦截器

postcard.greenChannel();

default:

break;

}

}

}

这个方法主要完成了对 postcard 的信息与 Warehouse 的信息进行结合,以补全 postcard 的信息,它的步骤如下:

1.通过 Warehouse.routes.get 根据 path 尝试获取 RouteMeta 对象。

2.若获取不到 RouteMeta 对象,可能是不存在或是还没有进行加载(第一次都未加载),尝试获取 RouteGroup 调用其 loadInto 方法将 RouteMeta 加载进 Warehouse,最后调用 completion 重新尝试补全 。

3.将 RouteMeta 的信息设置到 postcard 中,其中会将 rawUri 的参数设置进 Bundle。

4.对于 Provider 和 Fragment 特殊处理,其中 Provider 会从 Warehouse 中加载并构造它的对象,然后设置到 postcardProvider 和 Fragment 都会跳过拦截器。

RouteGroup 的 loadInto 仍然是自动生成的,例如下面就是一些自动生成的代码:

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

atlas.put(“/homework/commit”, RouteMeta.build(RouteType.ACTIVITY, HomeworkCommitActivity.class, “/homework/commit”, “homework”, null, -1, -2147483648));

// …

最后

都说三年是程序员的一个坎,能否晋升或者提高自己的核心竞争力,这几年就十分关键。

技术发展的这么快,从哪些方面开始学习,才能达到高级工程师水平,最后进阶到Android架构师/技术专家?我总结了这 5大块;

我搜集整理过这几年阿里,以及腾讯,字节跳动,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。

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

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

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

a> atlas) {

atlas.put(“/homework/commit”, RouteMeta.build(RouteType.ACTIVITY, HomeworkCommitActivity.class, “/homework/commit”, “homework”, null, -1, -2147483648));

// …

最后

都说三年是程序员的一个坎,能否晋升或者提高自己的核心竞争力,这几年就十分关键。

技术发展的这么快,从哪些方面开始学习,才能达到高级工程师水平,最后进阶到Android架构师/技术专家?我总结了这 5大块;

我搜集整理过这几年阿里,以及腾讯,字节跳动,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

[外链图片转存中…(img-vzgtPXCg-1715104326362)]

[外链图片转存中…(img-zUqYQt2g-1715104326362)]

[外链图片转存中…(img-ich0rTmq-1715104326363)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值