█微信号█ 13★710097773 █供卵试管婴儿██代孕选性别生男孩 ██试管包出生██代孕男孩██代孕包出生███代孕选性别██试管婴儿███代孕生男孩█████试管婴儿代孕生男孩███供卵试管婴儿代孕███
前言
当前Android的路由库实在太多了,刚开始的时候想为什么要用路由表的库,用Android原生的Scheme码不就好了,又不像iOS只能类依赖,后面越深入就越发现当时想的太简单了,后面看到Retrofit和OKHttp,才想到页面请求本质和网络请求不是一样吗,终于业界最简单高效的路由方案1.0出来了
背景
什么是路由
根据路由表
将页面请求
分发到指定页面
使用场景
- App接收到一个通知,点击通知打开App的某个页面
- 浏览器App中点击某个链接打开App的某个页面
- 运营活动需求,动态把原生的页面替换成H5页面
- 打开页面需要某些条件,先验证完条件,再去打开那个页面
- 不合法的打开App的页面被屏蔽掉
- H5打开链接在所有平台都一样,方便统一跳转
- App存在就打开页面,不存在就去下载页面下载,只有Google的App Link支持
为什么要有路由
Android原生已经支持AndroidManifest
去管理App跳转,为什么要有路由库,这可能是大部分人接触到Android各种Router库不太明白的地方,这里我讲一下我的理解
- 显示Intent:项目庞大以后,类依赖耦合太大,不适合组件化拆分
- 隐式Intent:协作困难,调用时候不知道调什么参数
- 每个注册了Scheme的Activity都可以直接打开,有安全风险
- AndroidMainfest集中式管理比较臃肿
- 无法动态修改路由,如果页面出错,无法动态降级
- 无法动态拦截跳转,譬如未登录的情况下,打开登录页面,登录成功后接着打开刚才想打开的页面
- H5、Android、iOS地址不一样,不利于统一跳转
怎么样的路由才算好路由
路由说到底还是为了解决开发者遇到的各种奇葩需求,使用简单、侵入性低、维护方便是首要条件,不影响你原来的代码,写入代码也很少,这里就要说说我的OkDeepLink
的五大功能了,五大功能瞬间击中你的各种痛点,早点下班不是梦。
- 编译时注解,实现静态路由表,不再需要在臃肿的
AndroidManifest
中找到那个Actvity写Scheme和Intent Filter - 异步拦截器,实现动态路由,安全拦截、动态降级难不倒你
- 模仿
Retrofit
接口式调用,实现方式用apt
,不耗性能,参数调用不再是问题 - Hook
OnActivityResult
,支持RxJava响应式调用,不再需要进行requestCode判断 - 参数依赖注入,自动保存,不再需要手动写
onSaveInstance
、onCreate(SaveInstace)
、onNewIntent(Intent)
、getQueryParamer
具体使用见OkDeepLink
详细比较
大部分路由库都用Apt(编译时注解)生成路由表,然后用路由表转发到指定页面
方案对比 | OkDeepLink | Airbnb DeepLinkDispatch | 阿里 ARouter | 天猫 统跳协议 | ActivityRouter |
---|---|---|---|---|---|
路由注册 | 注解式接口注册 | 每个module都要手动注册 | 每个module的路由表都要类查找 | AndroidManiFest配置 | 每个module都要手动注册 |
路由查找 | 路由表 | 路由表 | 路由表 | 系统Intent | 路由表 |
路由分发 | Activity转发 | Activity转发 | Activity转发 | Activity转发 | Activity转发 |
动态替换 | Rxjava实现异步拦截器 | 不支持 | 线程等待 | 不支持 | 不支持 |
动态拦截 | Rxjava实现异步拦截器 | 不支持 | 线程等待 | 不支持 | 主线程 |
安全拦截 | Rxjava实现异步拦截器 | 不支持 | 线程等待 | 不支持 | 主线程 |
方法调用 | 接口 | 手动拼装 | 手动拼装 | 手动拼装 | 手动拼装 |
参数获取 | Apt依赖注入,支持所有类型,不需要在Activity的onCreate 中手动调用get方法 |
参数定义在path,不利于多人协作 | Apt依赖注入,但是要手动调用get方法 | 手动调用 | 手动调用 |
结果返回 | Rxjava回调 | onActivityResult | onActivityResult | onActivityResult | onActivityResult |
Module接入不同App | 支持 | 不支持 | 支持 | 不支持 | 支持 |
其实说到底,路由的本质就是注册再转发,围绕着转发可以进行各种操作,拦截,替换,参数获取等等,其他Apt、Rxjava说到底都只是为了方便使用出现的,这里你会发现各种路由库反而为了修复各种工具带来的问题,出现了原来没有的问题,譬如DeepLinkDispatch为了解决Apt没法汇总所有Module路由,每个module都要手动注册,ARouter为了解决Apt没法汇总所有Module路由,通过类操作耗时,才出现分组的概念。
原理分析
定义路由
我这边是完全按照URL规范了,这里要说一下,现在好多方法是把参数定义在path里面的,虽然这样做,有不需要额外传参数的好处,但是这样路由就没有那么灵活,调试起来就没有那么方便了。
建议有好几款app的公司,host都一样,只有scheme不一样,这样只要替换Scheme就能实现降级,维护也简单。
路由注册
AndroidManifest
里面的acitivity
声明scheme码是不安全的,所有App都可以打开这个页面,这里就产生有两种方式去注册,
- 注解产生路由表,通过
DispatchActivity
转发 AndroidManifest
注册,将其export=fasle
,但是再通过DispatchActivity转发Intent,天猫就是这么做的,比上面的方法的好处是路由查找都是系统调用,省掉了维护路由表的过程,但是AndroidManifest配置还是比较不方便的
我现在还是采用了注解,后面我会结合两种方法,将注解自动修改AndroidManifest,对于接入方是没有变动的,方法已经找到了,用自定义Lint
扫描出注解相关的Activity,然后用processManifestTask修改Manifest,有个demo了,后面会接入。
生成路由表
譬如通过Apt把这段代码
public interface SampleService {
@Path("/main") @Activity(MainActivity.class) void startMainActivity(@Query("key") String key); }
生成
@After("execution(* okdeeplink.DeepLinkClient.init(..))")
public void init() { DeepLinkClient.addAddress(new Address("/main", MainActivity.class)); }
初始化路由表
这里就要提一下使用Apt会造成每个module都要手动注册
DeepLinkDispatch是这么做的
@DeepLinkModule
public class SampleModule {
}
@DeepLinkHandler({ SampleModule.class, LibraryDeepLinkModule.class })
public class DeepLinkActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); DeepLinkDelegate deepLinkDelegate = new DeepLinkDelegate( new SampleModuleLoader(), new LibraryDeepLinkModuleLoader()); deepLinkDelegate.dispatchFrom(this); finish(); } }
ARouter是通过类查找,就比较耗时了,所以他又加入了分组的概念,按需加载
/**
* 通过指定包名,扫描包下面包含的所有的ClassName
*
* @param context U know
* @param packageName 包名
* @return 所有class的集合
*/
public static List<String> getFileNameByPackageName(Context context, String packageName) throws PackageManager.NameNotFoundException, IOException { List<String> classNames = new ArrayList<>(); for (String path : getSourcePaths(context)) { DexFile dexfile = null; try { if (path.endsWith(EXTRACTED_SUFFIX)) { //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache" dexfile = DexFile.loadDex(path, path + ".tmp", 0); } else { dexfile = new DexFile(path); } Enumeration<String> dexEntries = dexfile.entries(); while (dexEntries.hasMoreElements()) { String className = dexEntries.nextElement(); if (className.contains(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) { } } } } Log.d("ARouter", "Filter " + classNames.size() + " classes by packageName <" + packageName + ">"); return classNames; }
ActivityRouter就比较巧妙了,通过Stub项目,其他地方都是provide的,只有主工程里面用Apt生成RouterInit类,虽然还是要写module
的注解
// RouterInit
if (hasModules) {
debug("generate modules RouterInit");
generateModulesRouterInit(moduleNames);
} else if (!hasModule) {
debug("generate default RouterInit"); generateDefaultRouterInit(); }
天猫 统跳协议 是最简单的,转发一下Intent就可以,但是这样就没法享受注解的好处了。
而我用aspectj
解决了这个问题,会自动汇总所有module的路由省略了这些多余的代码,或者有谁知道用Apt自生怎么解决,请联系我一下。
@After("execution(* okdeeplink.DeepLinkClient.init(..))")
public void init() { DeepLinkClient.addAddress(new Address("/main", MainActivity.class)); }
路由查找
路由查找就是查找路由表对应的页面,值得提起的就是因为要适应Module接入不同App,Scheme要自动适应,路由表其实是Path—》Activity,这样的话内部跳转的时候ARouterUri是没有的。而我这边是有的,我组装了一个内部的Uri,这样拦截器不会有影响。
public Request buildRequest(Intent sourceIntent) {
if (sourceIntent == null) { return null; } Intent newIntent = new Intent(sourceIntent); Uri uri = newIntent.getData(); addNewTaskFlag(newIntent); if (uri != null) { addBundleQuery(newIntent, uri); Address entry = new DeepLinkClient(context).matchUrl(uri.toString()); if (entry == null || entry.getActivityClass() == null) { return new Request(newIntent, this).setDeepLink(false); } newIntent.setComponent(new ComponentName(context, entry.getActivityClass())); return new Request(newIntent, this); } return new Request(newIntent, this).setDeepLink(false); }
路由分发
现在所有路由方案分发都是用Activity
做分发的,这样做会有这几个缺点
- 每次都要启动一个Activity,而Activity就算不写任何代码启动都要0.1秒
- 如果是异步等待的话,Activiy要在合适时间
finish
,不然会有一层透明的页面阻挡操作
对于第一个问题,有两个方法
- QQ音乐是把
DispatchActivity
设为SingleInstacne
,但是这样的话,动画会奇怪,堆栈也会乱掉,后退会有一层透明的页面阻挡操作 DispatchActivity
只在外部打开的时候调用
我选择了第二种
对于第二个问题,有两个方法
DispatchActivity
再把Intent转发到Service
,再finish,这种方法唯一的缺陷是拦截器里面的context是Servcie的activity,就没发再拦截器里面弹出对话框了。DispatchActivity
在打开和错误的时候finish
,如果activity
已经finish了,就用application的context去转发路由
我选择了第二种
public void dispatchFrom(Intent intent) {
new DeepLinkClient(this) .buildRequest(intent) .dispatch() .subscribe(new Subscriber<Request>() { @Override public void onCompleted() {