3 router 框架优化

3.1 apt 生成代码量过大问题优化

思考框架本身,其实可以发现仅有 router 映射表是需要根据注解编译生成的,其他的全部代码都是固定代码,完全可以 sdk 中直接编码提供。反过来思考为何当初 sdk 开发需要编写繁重的 apt 生成代码,去生成这些固定的逻辑,可以发现 htrouterdispatch-process 工程是一个纯 java 工程,部分纯 java 类的提供在 htrouterdispatch。由于无法引用 Android 类,同时期望业务层接口能完美隐藏内部实现,为此和 Android 相关的类,索性全部由 apt 生成。

  1. apply plugin: 'java' // 使用 apply plugin: 'com.android.library' 编译报错
  2. sourceCompatibility = JavaVersion.VERSION_1_7
  3. targetCompatibility = JavaVersion.VERSION_1_7
  4. dependencies {
  5. compile project (':htrouterdispatch')
  6. compile 'com.google.auto.service:auto-service:1.0-rc2'
  7. compile 'com.squareup:javapoet:1.0.0'
  8. }

为了解决这里的问题,我们可以稍微降低对实现封装的隐藏程度,修改初始化接口,需要业务层将 router 映射表显式的传入。修改后就能发现仅有 HTRouterTable 里面的映射表接口需要 apt 生成,而其余的代码均可通过直接编码。

  1. HTRouterManager.init();

  2. HTRouterManager.init(HTRouterTable.pageRouters(),
  3. HTRouterTable.methodRouters(),
  4. HTRouterTable.interceptors());

HTRouterTable.methodRouters() 和 HTRouterTable.interceptors() 先忽略,后续解释

新建了一个 Android Library htrouter,引用工程 htrouterdispatch,app 工程修改引用 htrouter

经过优化,router 跳转的逻辑代码可通过直接编码方式实现,普通 Android 开发也能轻松修改其中的逻辑,同时 apt 生成的类从 6 个直接减少至 1 个 HTRouterTable。若出现业务层代码编译错误导致 apt 生成失败,最终导致编译器提示 HTRouterTable not found,可仅需注释掉初始化代码即可。

  1. /**
  2. * 用于用户启动Activity或者通过URL获得可以跳转的目标
  3. */
  4. public final class HTRouterTable {
  5. public static final String HT_URL_PARAMS_KEY = "ht_url_params_map";
  6. private static final List<HTRouterEntry> PAGE_ROUTERS = new LinkedList<HTRouterEntry>();
  7. private static final List<HTInterceptorEntry> INTERCEPTORS = new LinkedList<HTInterceptorEntry>();
  8. private static final List<HTMethodRouterEntry> METHOD_ROUTERS = new LinkedList<HTMethodRouterEntry>();
  9. public static List<HTRouterEntry> pageRouters() {
  10. if (PAGE_ROUTERS.isEmpty()) {
  11. PAGE_ROUTERS.add(new HTRouterEntry("com.netease.yanxuan.module.home.category.activity.CategoryPushActivity", "yanxuan://homepage_categoryl2", 0, 0, false));
  12. ...
  13. }
  14. return PAGE_ROUTERS;
  15. }
  16. public static List<HTInterceptorEntry> interceptors() {
  17. if (INTERCEPTORS.isEmpty()) {
  18. PAGE_ROUTERS.add(new HTRouterEntry("com.netease.yanxuan.module.home.recommend.activity.TagActivity", "yanxuan://tag", 0, 0, false));
  19. ...
  20. }
  21. return INTERCEPTORS;
  22. }
  23. public static List<HTMethodRouterEntry> methodRouters() {
  24. if (METHOD_ROUTERS.isEmpty()) {
  25. {
  26. List<Class> paramTypes = new ArrayList<Class>();
  27. paramTypes.add(Context.class);
  28. paramTypes.add(String.class);
  29. paramTypes.add(int.class);
  30. METHOD_ROUTERS.add(new HTMethodRouterEntry("http://www.you.163.com/jumpA", "com.netease.hearttouch.example.JumpUtil", "jumpA", paramTypes));
  31. }
  32. ...
  33. }
  34. return METHOD_ROUTERS;
  35. }
  36. }

3.2 拦截器优化

3.2.1 优化前临时方案

针对登录拦截需求,当时的临时解决方案如下:

  1. 路由注解添加 needLogin 字段
  2. 并修改 apt 生成代码,使 HTRouterEntry 记录 needLogin 信息
  3. 提供 RouterUtil.startActivity 将目标页面的跳转构建成一个 runnable 传入,在登录成功回调中执行 runnable
  1. @HTRouter(url = {PreemptionActivateActivity.ROUTER_URL}, needLogin = true)
  2. public class PreemptionActivateActivity extends Activity {
  3. ...
  4. }
  1. public static boolean startActivity(final Context context, final String schemeUrl,
  2. final Intent sourceIntent, final boolean isFinish) {
  3. return doStartActivity(context, schemeUrl, new Runnable() {
  4. @Override
  5. public void run() {
  6. HTRouterManager.startActivity(context, schemeUrl, sourceIntent, isFinish);
  7. }
  8. });
  9. }
  10. private static boolean doStartActivity(final Context context, final String schemeUrl,
  11. final Runnable runnable) {
  12. if (HTRouterManager.isUrlRegistered(schemeUrl)) {
  13. HTRouterEntry entry = HTRouterManager.findRouterEntryByUrl(schemeUrl);
  14. if (entry == null) {
  15. return false;
  16. }
  17. if (entry.isNeedLogin() && !UserInfo.isLogin()) {
  18. LoginActivity.setOnLoginResultListener(new OnLoginResultListener() {
  19. @Override
  20. public void onLoginSuccess() {
  21. runnable.run();
  22. }
  23. @Override
  24. public void onLoginFail() {
  25. // do nothing
  26. }
  27. });
  28. LoginActivity.start(context);
  29. }
  30. return true;
  31. }
  32. return false;
  33. }

可以发现这种处理方式并不通用,同时需要业务层代码全部修改调用方式,未修改的接口还是可能出现以未登录态进入需要登录的页面(这种情况也确实在后面发生过,后来我们要求前端跳转之前,先通过 jsbridge 唤起登录页面(⊙﹏⊙)b)。我们需要一种通用规范的方式处理拦截逻辑,同时能适用各种场景,也能规避业务层的错误。

3.2.2 拦截器优化和设计

为避免业务层绕过拦截器直接调用到 HTRouterManager,将 HTRouterManager.startActivity 等接口修改为 package 引用范围,此外新定义 HTRouterCall 作为对外接口类。

  1. public class HTRouterCall implements IRouterCall {
  2. ...
  3. }
  1. public interface IRouterCall {
  2. // 继续路由跳转
  3. void proceed();
  4. // 继续路由跳转
  5. void cancel();
  6. // 获取路由参数
  7. HTRouterParams getParams();
  8. }

定义拦截器 interface 如下:

  1. public interface IRouterInterceptor {
  2. void intercept(IRouterCall call);
  3. }

总结拦截的需求场景,归纳拦截场景为 3 种:

  1. 全局拦截 → 全局拦截器

    全局拦截器,通过静态接口设置添加

    1. public static void addGlobalInterceptors(IRouterInterceptor... interceptors) {
    2. Collections.addAll(sGlobalInterceptors, interceptors);
    3. }

    登录拦截需求可以理解是一个全局的需求,全部的 Activity 跳转都需要判断是否需要唤起登录页面。

    1. public class LoginRouterInterceptor implements IRouterInterceptor {
    2. @Override
    3. public void intercept(final IRouterCall call) {
    4. HTDroidRouterParams params = (HTDroidRouterParams) call.getParams();
    5. HTRouterEntry entry = HTRouterManager.findRouterEntryByUrl(params.url);
    6. if (entry == null) {
    7. call.cancel();
    8. return;
    9. }
    10. if (entry.isNeedLogin() && !UserInfo.isLogin()) {
    11. LoginActivity.setOnLoginResultListener(new OnLoginResultListener() {
    12. @Override
    13. public void onLoginSuccess() {
    14. call.proceed();
    15. }
    16. @Override
    17. public void onLoginFail() {
    18. call.cancel();
    19. }
    20. });
    21. LoginActivity.start(params.getContext());
    22. } else {
    23. call.proceed();
    24. }
    25. }
    26. }

    登录拦截效果

  2. 业务页面固定拦截 → 注解拦截器

    上面剩余的 7 个 switch-case 拦截,可以理解为特定业务页面唤起都必须进入的一个拦截处理,分别定义 7 个拦截器类,同样通过注解的方式标记。

    以 yanxuan://category 为例子

    1. @HTRouter(url = {"yanxuan://category", "yanxuan://categoryl2"})
    2. public class CategoryL2Activity extends Activity {
    3. ...
    4. }

    对应的注解拦截器

    1. @HTRouter(url = {"yanxuan://category"})
    2. public class CategoryL2Intercept implements IRouterInterceptor {
    3. @Override
    4. public void intercept(IRouterCall call) {
    5. HTRouterParams routerParams = call.getParams();
    6. Uri uri = Uri.parse(routerParams.url);
    7. // routerParams.url 添加额外参数
    8. Uri.Builder builder = uri.buildUpon();
    9. ...
    10. routerParams.url = builder.build().toString();
    11. call.proceed();
    12. }
    13. }

    apt 生成拦截器初始化代码

    1. public static List<HTInterceptorEntry> interceptors() {
    2. if (INTERCEPTORS.isEmpty()) {
    3. ...
    4. INTERCEPTORS.add(new HTInterceptorEntry("yanxuan://category", new CategoryL2Intercept()));
    5. ...
    6. }
    7. return INTERCEPTORS;
    8. }

    HTRouterTable

  1. 业务页面动态拦截

    比如 onClick 方法内执行路由跳转时,需要弹窗提示用户是否继续跳转,其他场景跳转并不需要这个弹窗,这种场景的拦截器我们认为是动态拦截

    1. HTRouterCall.newBuilder(data.schemeUrl)
    2. .context(mContext)
    3. .interceptors(new IRouterInterceptor() {
    4. @Override
    5. public void intercept(final IRouterCall call) {
    6. Log.i("TEST", call.toString());
    7. AlertDialog dialog = new AlertDialog.Builder(mContext)
    8. .setTitle("alert")
    9. .setMessage("是否继续")
    10. .setPositiveButton("继续", new DialogInterface.OnClickListener() {
    11. @Override
    12. public void onClick(DialogInterface dialog, int which) {
    13. call.proceed();
    14. }
    15. })
    16. .setNegativeButton("取消", new DialogInterface.OnClickListener() {
    17. @Override
    18. public void onClick(DialogInterface dialog, int which) {
    19. call.cancel();
    20. }
    21. }).create();
    22. dialog.show();
    23. }
    24. })
    25. .build()
    26. .start();

优先级:动态拦截器 > 注解拦截器 > 全局拦截器

3.3 sdk 页面 router 支持

我们接入了七鱼、HTImagePick 等 sdk,这些 sdk 也有自己的页面,而这部分页面并不能通过前面的路由方式打开,其原因如下:

    1. 我们不能修改他们的代码
    2. apt 处理的注解仅能针对引入 apt 的 app 工程
    3. 对应的页面唤起需要通过 sdk 提供的特殊接口唤起

      1. public static void openYsf(Context context, String url, String title, String custom) {
      2. ConsultSource source = new ConsultSource(url, title, custom);
      3. Unicorn.openServiceActivity(context, // 上下文
      4. title, // 聊天窗口的标题
      5. source // 咨询的发起来源,包括发起咨询的url,title,描述信息等
      6. );
      7. }

      七鱼客服页面唤起

  • public void openImagePick(Context context, ArrayList<PhotoInfo> photoInfos, boolean multiSelectMode, int maxPhotoNum, String title) { HTPickParamConfig paramConfig = new HTPickParamConfig(HTImageFrom.FROM_LOCAL, null, photoInfos, multiSelectMode, maxPhotoNum, title); HTImagePicker.INSTANCE.start(context, paramConfig, this); }

基于此,只需要提供对方法的 router 调用,就能支持 sdk 中的页面路由跳转。具体用法示例如下

    1. 通过 HTMethodRouter 注解标记跳转方法(非静态方法需实现 getInstance 单例)

      1. public class JumpUtil {
      2. private static final String TAG = "JumpUtil";
      3. private static JumpUtil sInstance = null;
      4. public static JumpUtil getInstance() {
      5. if (sInstance == null) {
      6. synchronized (JumpUtil.class) {
      7. if (sInstance == null) {
      8. sInstance = new JumpUtil();
      9. }
      10. }
      11. }
      12. return sInstance;
      13. }
      14. private JumpUtil() {
      15. }
      16. @HTMethodRouter(url = {"http://www.you.163.com/jumpA"}, needLogin = true)
      17. public void jumpA(Context context, String str, int i) {
      18. String msg = "jumpA called: str=" + str + "; i=" + i;
      19. Log.i(TAG, msg);
      20. if (context != null) {
      21. Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
      22. }
      23. }
      24. @HTMethodRouter(url = {"http://www.you.163.com/jumpB"})
      25. public static void jumpB(Context context, String str, int i) {
      26. String msg = "jumpB called: str=" + str + "; i=" + i;
      27. Log.i(TAG, msg);
      28. if (context != null) {
      29. Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
      30. }
      31. }
      32. @HTMethodRouter(url = {"http://www.you.163.com/jumpC"})
      33. public void jumpC() {
      34. Log.i(TAG, "jumpC called");
      35. }
      36. }
    2. 方法路由触发逻辑

      除了设置动画、是否关闭当前页面等参数,这里方法路由的调用方式和页面路由完全一致,同样支持 needLogin 字段,同样支持全局拦截器、注解拦截器、动态拦截器

      1. // JUMPA 按钮点击
      2. public void onMethodRouter0(View v) {
      3. HTRouterCall.call(MainActivity.this, "http://www.you.163.com/jumpA?a=lilei&b=10");
      4. }
      5. // JUMPB 按钮点击
      6. public void onMethodRouter1(View v) {
      7. HTRouterCall.call(MainActivity.this, "http://www.you.163.com/jumpB?a=hanmeimei&b=10");
      8. }
      9. // JUMPC 按钮点击
      10. public void onMethodRouter2(View v) {
      11. HTRouterCall.call(MainActivity.this, "http://www.you.163.com/jumpC");
      12. }
    3. 结果示例

3.4 main dex 优化处理

这里的处理逻辑较为简单,仅需修改类引用为类名字符串,后续跳转时通过反射获取类

  1. public static List<HTRouterEntry> routers() {
  2. if (ROUTERS.isEmpty()) {
  3. ...
  4. ROUTERS.add(new HTRouterEntry("com.netease.yanxuan.module.subject.SubjectActivity", "yanxuan://subject", 0, 0, false));
  5. ...
  6. }
  7. return ROUTERS;
  8. }

4 总结

通过优化拦截器,解决登录拦截问题,优化子模块和全局代码划分;通过提供方法路由,解决 sdk 页面的路由跳转问题;通过区分路由表生成代码和其他跳转逻辑,优化 apt 代码生成逻辑的复杂性和和维护性;通过修改路由表对类的直接引用,解决 main-dex 问题。

除此之外,路由框架并未对 module 子工程的 Activity 做路由集成,严选当前也没做更进一步的业务组件化。后续有需求进一步补充文章。

相关阅读:严选 Android 路由框架优化(上篇)

本文来自网易云社区,经作者张云龙授权发布。

原文地址:严选 Android 路由框架优化(下篇)

更多网易研发、产品、运营经验分享请访问网易云社区

严选 Android 路由框架优化(下篇)的更多相关文章

  1. 严选 Android 路由框架优化(上篇)

    0 背景 早前严选 Android 工程,使用原生 Intent 方式做页面跳转,为规范参数传递,做了编码规范,使用静态方法的方式唤起 Activity public static void star ...

  2. Android 路由框架ARouter最佳实践

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/76165252 本文出自[赵彦军的博客] 一:什么是路由? 说简单点就是映射页面跳转 ...

  3. Android路由框架-ARouter详解

    文章大纲 一.页面路由基本介绍1.什么是页面路由2.为什么要使用页面路由二.页面路由框架ARouter介绍1.常用功能介绍2.常见应用场景三.源码下载四.参考文章   一.页面路由基本介绍 1.什么是 ...

  4. WMRouter:美团外卖Android开源路由框架

    WMRouter是一款Android路由框架,基于组件化的设计思路,功能灵活,使用也比较简单. WMRouter最初用于解决美团外卖C端App在业务演进过程中的实际问题,之后逐步推广到了美团其他App ...

  5. Vue 2.x + Webpack 3.x + Nodejs 多页面项目框架(下篇——多页面VueSSR+热更新Server)

    Vue 2.x + Webpack 3.x + Nodejs 多页面项目框架(下篇--多页面VueSSR+热更新Server) @(HTML/JS) 这是Vue多页面框架系列文章的第二篇,上一篇(纯前 ...

  6. 2017年Android百大框架排行榜

    框架:提供一定能力的小段程序 >随意转载,标注作者"金诚"即可 >本文已授权微信公众号:鸿洋(hongyangAndroid)原创首发. >本文已经开源到Gith ...

  7. android 优秀框架整理

    程序员界有个神奇的网站,那就是github,这个网站集合了一大批优秀的开源框架,极大地节省了开发者开发的时间,在这里我进行了一下整理,这样可以使我们在使用到时快速的查找到,希望对大家有所帮助! 1. ...

  8. 2017年Android百大框架排行榜(转)

    一.榜单介绍 排行榜包括四大类: 单一框架:仅提供路由.网络层.UI层.通信层或其他单一功能的框架 混合开发框架:提供开发hybrid app.h5与webview结合能力.web app能力的框架 ...

  9. Android应用性能优化(转)

    人类大脑与眼睛对一个画面的连贯性感知其实是有一个界限的,譬如我们看电影会觉得画面很自然连贯(帧率为24fps),用手机当然也需要感知屏幕操作的连贯性(尤其是动画过度),所以Android索性就把达到这 ...

随机推荐

  1. codeforce 980B - Marlin(构造)

    Marlin time limit per test 1 second memory limit per test 256 megabytes input standard input output ...

  2. MySQL 优化器

    (system@127.0.0.1:3306) [trunk]> show variables like '%performance_sch%';+----------------------- ...

  3. jQuery笔记——选择器

    jQuery 最核心的组成部分就是:选择器引擎.它继承了 CSS 的语法,可以对 DOM 元 素的标签名.属性名.状态等进行快速准确的选择,并且不必担心浏览器的兼容性 常规选择器 根据id选择元素就是 ...

  4. 编写一个jQuery的扩展方法(插件)

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  5. Python Twisted系列教程9:第二个小插曲,Deferred

    作者:dave@http://krondo.com/a-second-interlude-deferred/ 译者:杨晓伟(采用意译) 可以从这里从头来阅读这个系列 更多关于回调的知识 稍微停下来再思 ...

  6. K12协同开发在做常见问题时候遇到的问题

    一.在做常见问题的时候遇到的问题 在后端处理数据的时候是通过serialize来实现的,从数据库中查出自己想要的数据,直接返回数据. 在前端发送ajax请求获取数据并且在页面上以好看的形式渲染. 1. ...

  7. Hadoop Serialization(third edition)hadoop序列化详解(最新版) (1)

    初学java的人肯定对java序列化记忆犹新.最开始很多人并不会一下子理解序列化的意义所在.这样子是因为很多人还是对java最底层的特性不是特别理解,当你经验丰富,对java理解更加深刻之后,你就会发 ...

  8. ZooKeeper集群搭建过程

    ZooKeeper集群搭建过程 提纲 1.ZooKeeper简介 2.ZooKeeper的下载和安装 3.部署3个节点的ZK伪分布式集群 3.1.解压ZooKeeper安装包 3.2.为每个节点建立d ...

  9. OSCache简介

    一.简介 Cache是一种用于提高系统响应速度.改善系统运行性能的技术.尤其是在Web应用中,通过缓存页面的输出结果,可以很显著的改善系统运行性能. OSCache标记库由OpenSymphony设计 ...

  10. 修改Tomcat可支持get传参方式的url长度,get形式

    maxHttpHeaderSize="8192"加在 <Connector port="8081" maxHttpHeaderSize="314 ...