2024年安卓最新WMRouter:美团外卖Android开源路由框架,大厂算法面试

如何成为Android高级架构师!

架构师必须具备抽象思维和分析的能力,这是你进行系统分析和系统分解的基本素质。只有具备这样的能力,架构师才能看清系统的整体,掌控全局,这也是架构师大局观的形成基础。 你如何具备这种能力呢?一是来自于经验,二是来自于学习。

架构师不仅要具备在问题领域上的经验,也需要具备在软件工程领域内的经验。也就是说,架构师必须能够准确得理解需求,然后用软件工程的思想,把需求转化和分解成可用计算机语言实现的程度。经验的积累是需要一个时间过程的,这个过程谁也帮不了你,是需要你去经历的。

但是,如果你有意识地去培养,不断吸取前人的经验的话,还是可以缩短这个周期的。这也是我整理架构师进阶此系列的始动力之一。


成为Android架构师必备知识技能

对应导图的学习笔记(由阿里P8大牛手写,我负责整理成PDF笔记)

部分内容展示

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

  • 目录
  • 热修复设计
  • 插件化框架设计

    《360°全方面性能优化》
  • 设计思想与代码质量优化
  • 程序性能优化

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

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

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

在Android中也提供了android.net.Uri工具类用于处理URI,Android中URI常用的几个部分主要是scheme、host、path和query。

Android中的Intent跳转

在Android中的Intent跳转,分为显式跳转和隐式跳转两种。

显式跳转即指定ComponentName(类名)的Intent跳转,一般通过Bundle传参,示例代码如下:

Intent intent = new Intent(context, TestActivity.class);
intent.putExtra(“param”, “value”)
startActivity(intent);

隐式跳转即不指定ComponentName的Intent跳转,通过IntentFilter找到匹配的组件,IntentFilter支持action、category和data的匹配,其中data就是URI。例如下面的代码,会启动系统默认的浏览器打开网页:

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(“http://www.meituan.com”))
startActivity(intent);

Activity通过Manifest配置IntentFilter,例如下面的配置可以匹配所有形如demo_scheme://demo_host/***的URI。

URI跳转

在美团外卖C端早期开发过程中,产品希望通过后台下发URI控制客户端跳转指定页面,从而实现灵活的运营配置。外卖App采用了Native+H5的混合开发模式,Native页面定义了专用的URI,而H5页面则使用HTTP/HTTPS链接在专门的WebView容器中加载,两种链接的跳转逻辑不同,实现起来比较繁琐。

Native页面的URI跳转最开始使用的是Android原生的IntentFilter,通过隐式跳转启动,但是这种方式存在灵活性差、功能缺失、Bug多等问题。例如:

  1. 从外部(浏览器、微信等)跳转外卖的URI时,系统会直接打开相应的Activity,而没有经过欢迎页的正常启动流程,一些代码逻辑可能没有执行,例如定位逻辑。

  2. 有很多页面在打开前需要确保用户先登录或先定位,每个页面都写一遍判断登录、定位的逻辑非常麻烦,提高了开发维护成本。

  3. 运营人员可能会配错URI,页面跳转失败,有些跳转的地方没有做try-catch处理,会产生Crash;有些地方虽然加了try-catch,但跳转失败后没有任何响应,用户体验差;跳转失败没有监控,不能及时发现和解决线上业务异常。

为了解决上述问题,我们希望有一个Android的URI分发组件,可以根据URI中不同的scheme、host、path,进行不同的处理,同时能够在页面跳转过程中进行更灵活的干预。调研发现,现有的一些Android路由组件主要都是在解决多工程之间解耦的问题,而URI往往只支持通过path分发,页面跳转的配置也不够灵活,难以满足实际需要。于是我们决定自行设计实现。

核心设计思路

下图展示了WMRouter中URI分发机制的核心设计思路。借鉴网络请求的机制,WMRouter中的每次URI跳转视为发起一个UriRequest;URI跳转请求被WMRouter逐层分发给一系列的UriHandler进行处理;每个UriHandler处理之前可以被UriInterceptor拦截,并插入一些特殊操作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

页面跳转来源

常见的页面跳转来源如下:

  1. 来自App内部Native页面的跳转
  2. 来自App内Web容器的跳转,即H5页面发起的跳转
  3. 从App外通过URI唤起App的跳转,例如来自浏览器、微信等
  4. 从通知中心Push唤起App的跳转

对于来自App内部和Web容器的跳转,我们把所有跳转代码统一改成调用WMRouter处理,而来自外部和Push通知的跳转则全部使用一个独立的中转Activity接收,再调用WMRouter处理。

UriRequest

UriRequest中包含Context、URI和Fields,其中Fields为HashMap<String, Object>,可以通过Key存放任意数据。简单起见,UriRequest类同时承担了Response的功能,跳转请求的结果,也会被保存到Fields中。Fields可以根据需要自定义,其中一些常见字段举例如下:

  • Intent的Extra参数,Bundle类型
  • 用于startActivityForResult的RequestCode,int类型
  • 用于overridePendingTransition方法的页面切换动画资源,int[]类型
  • 本次跳转结果的监听器,OnCompleteListener类型

每次URI跳转请求会有一个ResultCode(类似HTTP请求的ResponseCode),表示跳转结果,也存放在Fields中。常见Code如下,用户也可以自定义Code:

  • 200:跳转成功
  • 301:重定向到其他URI,会再次跳转
  • 400:请求错误,通常是Context或URI为空
  • 403:禁止跳转,例如跳转白名单以外的HTTP链接、Activity的exported为false等
  • 404:找不到目标(Activity或UriHandler)
  • 500:发生错误

总结来说,UriRequest用于实现一次URI跳转中所有组件之间的通信功能。

UriHandler

UriHandler用于处理URI跳转请求,可以嵌套从而逐层分发和处理请求。UriHandler是异步结构,接收到UriRequest后处理(例如跳转Activity等),如果处理完成,则调用callback.onComplete()并传入ResultCode;如果没有处理,则调用callback.onNext()继续分发。下面的示例代码展示了一个只处理HTTP链接的UriHandler的实现:

public interface UriCallback {

/**

  • 处理完成,继续后续流程。
    */
    void onNext();

/**

  • 处理完成,终止分发流程。
  • @param resultCode 结果
    */
    void onComplete(int resultCode);
    }

public class DemoUriHandler extends UriHandler {
public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
Uri uri = request.getUri();
// 处理HTTP链接
if (“http”.equalsIgnoreCase(uri.getScheme())) {
try {
// 调用系统浏览器
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(uri);
request.getContext().startActivity(intent);
// 跳转成功
callback.onComplete(UriResult.CODE_SUCCESS);
} catch (Exception e) {
// 跳转失败
callback.onComplete(UriResult.CODE_ERROR);
}
} else {
// 非HTTP链接不处理,继续分发
callback.onNext();
}
}
// …
}

UriInterceptor

UriInterceptor为拦截器,不做最终的URI跳转操作,但可以在最终的跳转前进行各种同步/异步操作,常见操作举例如下:

  • URI跳转拦截,禁止特定的URI跳转,直接返回403(例如禁止跳转非meituan域名的HTTP链接)
  • URI参数修改(例如在HTTP链接末尾添加query参数)
  • 各种中间处理(例如打开登录页登录、获取定位、发网络请求)
  • ……

每个UriHandler都可以添加若干UriInterceptor。在UriHandler基类中,handle()方法先调用抽象方法shouldHandle()判断是否要处理UriRequest,如果需要处理,则逐个执行Interceptor,最后再调用handleInternal()方法进行跳转操作。

public abstract class UriHandler {

// ChainedInterceptor将多个UriInterceptor合并成一个
protected ChainedInterceptor mInterceptor;

public UriHandler addInterceptor(@NonNull UriInterceptor interceptor) {
if (interceptor != null) {
if (mInterceptor == null) {
mInterceptor = new ChainedInterceptor();
}
mInterceptor.addInterceptor(interceptor);
}
return this;
}

public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
if (shouldHandle(request)) {
if (mInterceptor != null) {
mInterceptor.intercept(request, new UriCallback() {
@Override
public void onNext() {
handleInternal(request, callback);
}

@Override
public void onComplete(int result) {
callback.onComplete(result);
}
});
} else {
handleInternal(request, callback);
}
} else {
callback.onNext();
}
}

/**

  • 是否要处理给定的uri。在Interceptor之前调用。
    */
    protected abstract boolean shouldHandle(@NonNull UriRequest request);

/**

  • 处理uri。在Interceptor之后调用。
    */
    protected abstract void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback);
    }
URI的分发与降级

在外卖C端App中的URI分发示意如下图。所有URI跳转都会分发到RootUriHandler,然后根据不同的scheme分发到不同的子Handler。例如waimai协议分发到WmUriHandler,然后进一步根据不同的path分发到子Handler,从而启动相应的Activity;HTTP/HTTPS协议分发到HttpHandler,启动WebView容器;对于其他类型URI(tel、mailto等),前面的几个Handler都无法处理,则会分发到StartUriHandler,尝试使用Android原生的隐式跳转启动系统应用。

每个UriHandler都可以根据实际需要实现降级策略,也可以不作处理继续分发给其他UriHandler。RootUriHandler中提供了一个全局的分发完成事件监听器,当UriHandler处理失败返回异常ResultCode或所有子UriHandler都没有处理时,执行全局降级策略。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

平台化与两端复用

随着外卖C端业务的演进,团队成员扩充了数倍,商超生鲜等垂直品类的拆分,以及异地研发团队的建立,客户端的平台化被提上日程。关于外卖平台化更详细的内容,可参考团队之前已经发布的文章 美团外卖Android平台化架构演进实践

为了满足实际开发需要,在长时间的探索后,逐步形成了如图所示的三层工程结构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

原有的单个工程拆分成多个工程,就不可避免的涉及到多工程之间的耦合问题,主要包括通信问题、复用问题、依赖注入、编译问题,下面详细介绍。

通信问题

当原先的一个工程拆分到各个业务库后,业务库之间的页面需要进行通信,最主要的场景就是页面跳转并通过Intent传递参数。

原先的页面跳转使用显式跳转,Activity之间存在强引用,当Activity被拆分到不同的业务库,业务库不能直接互相依赖,因此需要进行解耦。

利用WMRouter的URI分发机制,刚好可以很容易的解决这个问题。将将所有业务库的Activity注册到WMRouter,各个业务库之间就可以进行页面跳转了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此时WMRouter已经承载了两项功能:

  1. 后台下发的运营URI跳转 (waimai://*)
  2. 内部页面跳转,用于代替原有的显式跳转,实现工程解耦 (wm_router://page/*)

由于后台下发的URI是和产品、运营、H5、iOS等各端统一制定的协议,支持的页面、格式、参数等都不能随意改动,而内部页面跳转使用的URI,则需要根据实际开发需要进行配置,两套URI协议不能兼容,因此使用了不同的scheme。

复用问题与ServiceLoader模块

业务库之间经常需要复用代码。一些通用代码逻辑可以下沉到平台层从而复用,例如业务无关的通用View组件;而有些代码不适合下沉到平台层,例如业务库A要使用业务库B中的某个界面模块,而这个界面模块和业务库B的耦合很紧密。具体到外卖实际业务场景中,商家页在商家休息时会展示推荐商家列表,其样式和首页相同(如图),而两个页面不在一个工程中,商家页希望能直接从首页业务库中获取商家列表的实现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为了解决上述问题,我们调研了解到Java中SPI (Service Provider Interfaces) 的设计思想与java.util.ServiceLoader工具类,还学习到美团平台为了解决类似问题而开发的ServiceLoader组件。

最后

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

img

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
  • 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!

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

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

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

的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!

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

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

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值