Android 组件化 —— 路由设计最佳实践

九章 等级 661 0 0

引子

这篇文章会告诉你:

  • 什么是路由,是为了解决什么问题才产生的;

  • 业界现状是怎么样的,我们可以做什么来优化当前的问题;

  • 路由设计思路是怎么样的,该怎么设计比较好;

  • 如何用注解实现路由表;

  • URL的参数如何依赖注入到Activity、Fragement;

  • 如何HookOnActivityResult,不需要再进行requstCode判断;

  • 如何异步拦截路由,实现线程切换,不阻塞页面跳转;

  • 如何用Apt实现Retrofit接口式调用;

  • 如何找到Activity的调用方;

  • 如何实现路由的安全调用;

  • 如何避开Apt不能汇总所有Module路由的问题。

前言

当前Android的路由库实在太多了,刚开始的时候想为什么要用路由表的库,用Android原生的Scheme码不就好了,又不像iOS只能类依赖,后面越深入就越发现当时想的太简单了,后面看到Retrofit和OkHttp,才想到页面请求本质和网络请求不是一样吗,终于业界最简单高效的路由方案1.0出来了。

开源的库后面会放在公司github地址上面。

背景

1

什么是路由

根据路由表将页面请求分发到指定页面。

2

使用场景

  1. App接收到一个通知,点击通知打开App的某个页面

  2. 浏览器App中点击某个链接打开App的某个页面

  3. 运营活动需求,动态把原生的页面替换成H5页面

  4. 打开页面需要某些条件,先验证完条件,再去打开那个页面

  5. 不合法的打开App的页面被屏蔽掉

  6. H5打开链接在所有平台都一样,方便统一跳转

  7. App存在就打开页面,不存在就去下载页面下载,只有Google的App Link支持

3

为什么要有路由

Android原生已经支持AndroidManifest去管理App跳转,为什么要有路由库,这可能是大部分人接触到Android各种Router库不太明白的地方,这里我讲一下我的理解

  • 显示Intent:项目庞大以后,类依赖耦合太大,不适合组件化拆分

  • 隐式Intent:协作困难,调用时候不知道调什么参数

  • 每个注册了Scheme的Activity都可以直接打开,有安全风险

  • AndroidMainfest集中式管理比较臃肿

  • 无法动态修改路由,如果页面出错,无法动态降级

  • 无法动态拦截跳转,譬如未登录的情况下,打开登录页面,登录成功后接着打开刚才想打开的页面

  • H5、Android、iOS地址不一样,不利于统一跳转

4

怎么样的路由才算好路由

路由说到底还是为了解决开发者遇到的各种奇葩需求,使用简单、侵入性低、维护方便是首要条件,不影响你原来的代码,写入代码也很少,这里就要说说我的OkDeepLink的五大功能了,五大功能瞬间击中你的各种痛点,早点下班不是梦。

  • 编译时注解,实现静态路由表,不再需要在臃肿的AndroidManifest中找到那个Actvity写Scheme和Intent Filter

  • 异步拦截器,实现动态路由,安全拦截、动态降级难不倒你

  • 模仿Retrofit接口式调用,实现方式用apt,不耗性能,参数调用不再是问题

  • HookOnActivityResult,支持RxJava响应式调用,不再需要进行requestCode判断

  • 参数依赖注入,自动保存,不再需要手动写onSaveInstance、onCreate(SaveInstace)、onNewIntent(Intent)、getQueryParamer

Android 组件化 —— 路由设计最佳实践

注册路由

Android 组件化 —— 路由设计最佳实践

路由结构图

5

详细比较

大部分路由库都用Apt(编译时注解)生成路由表,然后用路由表转发到指定页面。

Android 组件化 —— 路由设计最佳实践

其实说到底,路由的本质就是注册再转发,围绕着转发可以进行各种操作,拦截,替换,参数获取等等,其他Apt、Rxjava说到底都只是为了方便使用出现的,这里你会发现各种路由库反而为了修复各种工具带来的问题,出现了原来没有的问题,譬如DeepLinkDispatch为了解决Apt没法汇总所有Module路由,每个module都要手动注册,ARouter为了解决Apt没法汇总所有Module路由,通过类操作耗时,才出现分组的概念。

原理分析

Android 组件化 —— 路由设计最佳实践

原理流程图

1

定义路由

Android 组件化 —— 路由设计最佳实践

路由定义

我这边是完全按照URL规范了,这里要说一下,现在好多方法是把参数定义在path里面的,虽然这样做,有不需要额外传参数的好处,但是这样路由就没有那么灵活,调试起来就没有那么方便了。

建议有好几款app的公司,host都一样,只有scheme不一样,这样只要替换Scheme就能实现降级,维护也简单。

2

路由注册

AndroidManifest里面的acitivity声明scheme码是不安全的,所有App都可以打开这个页面,这里就产生有两种方式去注册,

  • 注解产生路由表,通过DispatchActivity转发

  • AndroidManifest注册,将其export=fasle,但是再通过DispatchActivity转发Intent,天猫就是这么做的,比上面的方法的好处是路由查找都是系统调用,省掉了维护路由表的过程,但是AndroidManifest配置还是比较不方便的

我现在还是采用了注解,后面我会结合两种方法,将注解自动修改AndroidManifest,对于接入方是没有变动的,方法已经找到了,用自定义Lint扫描出注解相关的Activity,然后用processManifestTask修改Manifest,有个demo了,后面会接入。

3

生成路由表

譬如通过Apt把这段代码

Android 组件化 —— 路由设计最佳实践

生成

Android 组件化 —— 路由设计最佳实践

4

初始化路由表

这里就要提一下使用Apt会造成每个module都要手动注册

DeepLinkDispatch是这么做的

Android 组件化 —— 路由设计最佳实践

Android 组件化 —— 路由设计最佳实践

ARouter是通过类查找,就比较耗时了,所以他又加入了分组的概念,按需加载

Android 组件化 —— 路由设计最佳实践

ActivityRouter就比较巧妙了,通过Stub项目,其他地方都是provide的,只有主工程里面用Apt生成RouterInit类,虽然还是要写module的注解

Android 组件化 —— 路由设计最佳实践

天猫 统跳协议 是最简单的,转发一下Intent就可以,但是这样就没法享受注解的好处了。

而我用aspectj解决了这个问题,会自动汇总所有module的路由省略了这些多余的代码,或者有谁知道用Apt自生怎么解决,请联系我一下。

Android 组件化 —— 路由设计最佳实践

5

路由查找

路由查找就是查找路由表对应的页面,值得提起的就是因为要适应Module接入不同App,Scheme要自动适应,路由表其实是Path---》Activity,这样的话内部跳转的时候ARouterUri是没有的。而我这边是有的,我组装了一个内部的Uri,这样拦截器不会有影响。

Android 组件化 —— 路由设计最佳实践

6

路由分发

现在所有路由方案分发都是用Activity做分发的,这样做会有这几个缺点

  1. 每次都要启动一个Activity,而Activity就算不写任何代码启动都要0.1秒

  2. 如果是异步等待的话,Activiy要在合适时间finish,不然会有一层透明的页面阻挡操作

对于第一个问题,有两个方法

  1. QQ音乐是把DispatchActivity设为SingleInstacne,但是这样的话,动画会奇怪,堆栈也会乱掉,后退会有一层透明的页面阻挡操作

  2. DispatchActivity只在外部打开的时候调用

我选择了第二种

对于第二个问题,有两个方法

  1. DispatchActivity再把Intent转发到Service,再finish,这种方法唯一的缺陷是拦截器里面的context是Servcie的activity,就没发再拦截器里面弹出对话框了。

  2. DispatchActivity在打开和错误的时候finish,如果activity已经finish了,就用application的context去转发路由

我选择了第二种

Android 组件化 —— 路由设计最佳实践

其实处理透明Activity阻挡操作可以采用取消所有事件变成无感页面的方法,但是还是觉得会影响activity堆栈没有采用这种方案

Android 组件化 —— 路由设计最佳实践

7

结果返回

这里我封装了一个库RxActivityResult去捕获onActivityResult,这样能保正流式调用

譬如拍照可以这样写,先定义一个接口

Android 组件化 —— 路由设计最佳实践

然后这样调用

Android 组件化 —— 路由设计最佳实践

是不是很简单,原理是这样的,通过封装一个RxResultHoldFragment去处理onActivityResult

Android 组件化 —— 路由设计最佳实践

8

动态拦截

拦截器是重中之重,有了拦截器可以做好多事情,可以说之所以要做页面路由,就是为了要实现拦截器。ARouter是用线程等待实现的,但是现在有Rxjava了,可以实现更优美的方式。

先来看一下我做的拦截器的效果.

Android 组件化 —— 路由设计最佳实践

是不是很简单,参考了部分OkHttp的实现思路,加入Rxjava,实现异步拦截。

首先将请求转换成责任链模式RealCallChain,RealCallChain的call方法实际不会执行路由跳转,只有Interceptor里面调用了call.proceed或者call.cancel才会执行.

Android 组件化 —— 路由设计最佳实践

接着处理异步的问题,这里用到了Rxjava的AsyncSubject和BehaviorSubject,

  1. AsyncSubject具有仅释放Observable释放的最后一个数据的特性,作为路由请求的发送器

  2. BehaviorSubject具有一开始就会释放最近释放的数据的特性,作为路由拦截器的发送器

具体实现看核心代码

Android 组件化 —— 路由设计最佳实践

9

方法调用

大部分路由库都是手动拼参数调用路由的,这里模仿了Retrofit接口式调用,受了LiteRouter的启发,不过Retrofit使用了动态代理,我使用的Apt没有性能损耗。

通过Apt生成每个接口的实际方法

譬如把SecondService接口

Android 组件化 —— 路由设计最佳实践

生成

Android 组件化 —— 路由设计最佳实践

然后调用

Android 组件化 —— 路由设计最佳实践

SecondService就生成了。

为了调用方便,直接在Activity或者fragement写这段代码,sampleServive就自动生成了

Android 组件化 —— 路由设计最佳实践

但是如果用到MVP模式,不是在Activity里面调用路由,后面会支持在这些类里面自动注入SampleService,现在先用java代码build

10

参数获取

大部分路由库都是手动获取参数的,这样还要传入参数key比较麻烦,这里模仿了ARouter,不过我支持类型更全一些,支持Bundle支持的所有类型,而且不需要在Acitivty的onCreate调用获取代码。

通过Apt把这段代码

Android 组件化 —— 路由设计最佳实践

生成

Android 组件化 —— 路由设计最佳实践

11

Module接入不同App

这里是参考ARouter把path作为key对应activity,这样接入到其他app中,就自动替换了scheme码了

Android 组件化 —— 路由设计最佳实践

安全

现在有好多人用脚本来打开App,然后干坏事,其实时可以用路由来屏蔽掉.

有三种方法供君选择,不同方法适合不同场景

1

签名屏蔽

就是把所有参数加密成一个数据作为sign参数,然后比对校验,但是这要求加密方法不变,要不然升级了以前的app就打不开了

2

adb打开屏蔽

在android5.1手机上,用adb打开的app它的mReferrer为空

Android 组件化 —— 路由设计最佳实践

3

包名过滤

Android 组件化 —— 路由设计最佳实践

这三种方法,比较适合的还是签名校验为主,adb过滤为副

4

如何解决路由造成的Activity堆栈错乱的问题

activity的launchMode使用不当会照成闪屏页面打开多次的问题,可以参考我这篇文章。

未来展望

路由是一个基础模块,技术难度虽然不是很大,但是如果每个开发都重新踩一遍,性价比就比较低,我希望能把路由相关的所有链路都替你弄好,你可以留着时间去干其他更重要的事情,譬如陪陪家人,逗逗狗什么的。

接下来我会在这几个方面努力,把整条链路补全。

  • 做一个像Swagger的平台,支持一键导出所有路由、二维码打开路由

  • 注解修改AndroidManifest,不再需要路由表

  • 支持路由方法接收器,Url直接打开某个方法,不再局限Activity

如果大家有意见,欢迎联系kingofzqj@gmail.com

参考文献

1

业界做法

  • airbnb开源的页面路由

  • 阿里开源的页面路由

  • 天猫的统跳协议

  • 蘑菇街的页面路由

  • Google App Link

  • 移动DeepLink的前世今生

1

设计方案

  • UrlRouter路由框架的设计

  • 移动端路由层设计

  • 客户端路由动态配置

  • 移动端基于动态路由的架构设计

  • Android组件化通信(多进程)

  • iOS 组件化 —— 路由设计思路分析

  • QQ音乐首页Activity的单例实现

2

个人开发

  • LiteRouter 模仿retrofit,各个业务分根据需求约定好接口,就像一份接口文档一样

  • ActivityRouter

  • ActivityRouter2

  • AndRouter

  • Router

  • Router2

  • router-android

3

安全讨论

  • 如何在Activity中获取调用者

    讨论了android里面原生支持找到路由来源的可能性,分析了referrer是如何产生的

  • LauncherFrom

    提供了一种hook activitythread找到launchedFromPackage的方法,不过也只支持5.0以上

  • 高效过滤Intents

    只有包含特定Package URL的 intent 才会唤起页面

本文转自 https://mp.weixin.qq.com/s/jkDoJkLL2JlScXg7VldBMQ,如有侵权,请联系删除。

收藏
评论区

相关推荐

Android Activity生命周期,启动模式,启动过程详解
前言 接触过Android开发的同学都知道Activity,Activity作为Android四大组件之一,使用频率高。简单来说Activity提供了一个显示界面,让用户进行各种操作,本文主要分为以下三个部分:Activity的生命周期,启动模式,以及Activity的工作过程。文中大部分篇幅来自《Android开发艺术探索》一书,尽管想多以流程或图
Android深入理解Context(二)Activity和Service的Context创建过程
Android框架层 Android深入理解Contextcategories: Android框架层本文首发于微信公众号「刘望舒」 前言上一篇文章我们学习了Context关联类和Application Context的创建过程,这一篇我们接着来学习Activity和Service的Context创建过程。需要注意的是,本篇的知识点会和深入理解四大组件系列的
Android深入四大组件(一)应用程序启动过程(前篇)
Android框架层 Android深入四大组件categories: Android框架层本文首发于微信公众号「后厂技术官」 前言在此前的文章中,我讲过了Android系统启动流程和Android应用进程启动过程,这一篇顺理成章来学习Android 7.0的应用程序的启动过程。分析应用程序的启动过程其实就是分析根Activity的启动过程。<!more 1
Android深入四大组件(二)Service的启动过程
Android框架层 Android深入四大组件categories: Android框架层本文首发于微信公众号「刘望舒」 前言此前我用较长的篇幅来介绍Android应用程序的启动过程(根Activity的启动过程),这一篇我们接着来分析Service的启动过程。建议阅读此篇文章前,请先阅读和这两篇文章。<!more 1.ContextImpl到Activi
Android深入四大组件(六)Android8.0 根Activity启动过程(前篇)
Android框架层 Android深入四大组件categories: Android框架层本文首发于微信公众号「刘望舒」 前言在几个月前我写了和这两篇文章,它们都是基于Android 7.0,当我开始阅读Android 8.0源码时发现应用程序(根Activity)启动过程照Android 7.0有了一些变化,因此又写下了本篇文章,本篇文章照此前的文章不仅
Android深入四大组件(七)Android8.0 根Activity启动过程(后篇)
Android框架层 Android深入四大组件categories: Android框架层本文首发于微信公众号「刘望舒」 前言在几个月前我写了和这两篇文章,它们都是基于Android 7.0,当我开始阅读Android 8.0源码时发现应用程序(根Activity)启动过程照Android 7.0有了一些变化,因此又写下了本篇文章,本篇文章照此前的文章不仅
Android解析ActivityManagerService(二)ActivityTask和Activity栈管理
Android框架层 Android系统服务 ActivityManagerService Android框架层本文首发于微信公众号「刘望舒」 前言关于AMS,原计划是只写一篇文章来介绍,但是AMS功能繁多,一篇文章的篇幅远远不够。这一篇我们接着来学习与AMS相关的ActivityTask和Activity栈管理。 1.ActivityStackActivi
Activity Lifecycle
官方介绍网址:[http://developer.android.com/reference/android/app/Activity.html](https://www.oschina.net/action/GoToLink?url=http%3A%2F%2Fdeveloper.android.com%2Freference%2Fandroid%2Fapp
Activity跳转通过EventBus传值问题
根据阿里发布的Android开发规范:下载地址:[https://102.alibaba.com/downloadFile.do?file=1520478361732/Android\_v9.pdf](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2F102.alibaba.com%2Fdow
Android Service演义
Android Service演义 (本文以Android 5.1为准) 侯亮 1.概述 ======= 在Android平台上,那种持续性工作一般都是由service来执行的。不少初学者总是搞不清service和线程、进程之间的关系,这当然会影响到他们开展具体的开发工作。 其实,简单说起来,service和线程、进程是没什么关系的。我
Android 复选框 以及回显
activity\_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http:/
Android 面试常问七道题
1Android的四大组件以及作用 **Activity**:Activity是Android程序与用户交互的窗口,是Android构造块中最基本的一种,它需要为保持各界面的状态,做很多持久化的事情,妥善管理生命周期以及一些跳转逻辑。 **service**:后台服务于Activity,封装有一个完整的功能逻辑实现,接受上层指令,完成相关的动作,
Android入门:HTML布局中Android程序与JAVASCRIPT的交互
1、JAVASCRIPT调用Android里面的方法 //主函数 public class VMusicActivity extends Activity { @Override public voidonCreate(Bundle savedInstanceState) {
Android面试收集录18 Android Context详解
Activity mActivity =new Activity() 作为Android开发者,不知道你有没有思考过这个问题,Activity可以new吗?Android的应用程序开发采用JAVA语言,Activity本质上也是一个对象,那上面的写法有什么问题呢?估计很多人说不清道不明。Android程序不像Java程序一样,随便创建一个类,写个main(
Rxjava和EventBus对比
总的来说,EventBus是一款针对Android优化的发布/订阅事件总线,主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息。而Rxjava则是一种基于异步数据流的处理方案。如果一个订阅者需要注册多个事件的时候,Rxjava需要一个个单独的注册,而EventBus则可以实现一