UDN-企业互联网技术人气社区

板块导航

浏览  : 1272
回复  : 0

[干货] 客户端路由动态配置——可能是最简单的热更新方案

[复制链接]
呵呵燕的头像 楼主
发表于 2016-12-4 12:54:46 | 显示全部楼层 |阅读模式
  Abstract

  说到热更新,大多数人的第一印象肯定是AndFix或者HotFix等热更新框架。但是一来这些框架学习成本较高,坑较多,二来对于大的模块更新支持不好。所以在实际开发功能中,对于一些紧急的功能上线或者bug修复,使用H5页面替换原生页面是一个较为简单和方便的方案。本文主要讲解如何使用路由动态配置思想实现App内任意页面的替换。同时会给出一个Demo工程,方便大家学习和实现。

  Url规范

  首先为了实现原生页面和H5页面之间的任意跳转,我们需要定义一套统一的Url规范。我们知道一个Url主要由Schema、Host、path以及QueryParameter等构成。以下分条给出定义。

  Schema

  为了实现从浏览器直接跳入App,我建议使用私有Schema。同时用不同的Schema来表示不同的动作。以下是我定义的两个Schema。

  dynamic: 用此Schema的url表示如果能用原生页面打开,则用原生页面打开,如果没有,则经过一些转化变成http或者https协议连接使用WebView打开。主要用来上线一些原生没有的页面。比如老版本没有这个页面就用h5页面替换,新版本还是用原生的。

  dynamicWeb: 用此Schema的url表示总是用WebView打开。主要用来替换原生的一些页面。比如某个运营活动明天就上线了,但是临时更版肯定来不及了,就临时把某一个页面都替换为H5页面。

  Path

  Path包括Host部分来区分某一个页面。某些同学可能会问,在H5那边Host是用来表示服务器域名的,这不就不兼容了吗,如果手机没有安装App,用我们这种规范定义的url肯定就无法打开了。这确实是一个问题,但是我想如果为了兼容H5在客户端页面的url中加上服务器的域名感觉也很奇怪。所以作为一个客户端程序员来说,这样比较方便,就暂且这么定。而为了解决没有安装客户端的情况,需要给H5页面的跳转链接加上备选链接。为了解决客户端原生页面跳转H5页面的情况,需要为我们定义的Url加上H5服务器的域名。这是本方案不太优雅的地方,各位有什么建议欢迎提出。

  QueryParameter 所有的参数都放在QueryParameter中。对于一些Object,就只能序列化一下也放在QueryParameter中了。

  路由框架

  为了方便的通过Url打开Activity或者WebView,我们需要一个路由框架。这里我推荐我开源的一个路由框架 AndRouter。AndRouter会根据Url的Schema选择不同的方式来打开这个url。同时它提供了ActivityRouter(将以activity为Schema的url转为Intent并打开对应Activity)的实现和BrowserRouter(用浏览器打开以http和https为Schema的url)的实现。为了让AndRouter支持我们自定义的dynamic和dynamicWeb为Schema的连接,我们需要自定义两个Router如下。

  1.   public class DynamicRouter implements IRouter {

  2.   private static final String DEFAULT_SCHEME = "dynamic";

  3.   ...

  4.   protected boolean open(Context context, IRoute route){

  5.   String routeUrl = route.getUrl();

  6.   boolean isSuccess = false;

  7.   if(canOpenTheRoute(route)) {

  8.   //首先尝试用原生的Activity打开,如果无法打开,则使用WebView打开

  9.   try {

  10.   Uri uri = Uri.parse(routeUrl)

  11.   .buildUpon()

  12.   .scheme("activity")

  13.   .build();

  14.   //尝试Activity打开

  15.   if (!Router.open(context, uri.toString())) {

  16.   //失败,换用WebView打开,但是需要给Url加上H5域名

  17.   String path = UrlConfig.BASE_URL + UrlUtils.getHost(routeUrl);

  18.   Timber.i("path : %s", path);

  19.   String url = UrlUtils.addQueryParameters(path, UrlUtils.getParameters(routeUrl));

  20.   isSuccess = Router.open(context, url);

  21.   } else {

  22.   isSuccess = true;

  23.   }

  24.   } catch (Exception e) {

  25.   Timber.e(e, "");

  26.   }

  27.   }

  28.   return isSuccess;

  29.   }

  30.   ...

  31.   }
复制代码


  DynamicWebRouter 同理,不过不会尝试用Activity打开。

  1.   public class DynamicWebRouter extends BaseRouter {

  2.   private static final String DEFAULT_SCHEMA = "dynamicWeb";

  3.   ...

  4.   protected boolean open(Context context, IRoute route){

  5.   boolean isSuccess = false;

  6.   if(canOpenTheRoute(route)){

  7.   //强制使用WebViewActivity打开

  8.   String routeUrl = route.getUrl();

  9.   try {

  10.   String host = UrlUtils.getHost(routeUrl);

  11.   String path = UrlConfig.BASE_URL + host;

  12.   Timber.i("path %s", path);

  13.   String url = UrlUtils.addQueryParameters(path, UrlUtils.getParameters(routeUrl));

  14.   isSuccess = Router.open(context, url);

  15.   } catch (Exception e){

  16.   Timber.e(e, "");

  17.   }

  18.   }

  19.   return isSuccess;

  20.   }

  21.   ...

  22.   }
复制代码


  给路由框架加上以上两个Router的实现,然后AndRouter就支持打开我们自定义的Schema Url了。

  1.   Router.initActivityRouter(getContext());

  2.   Router.addRouter(new WebRouter());

  3.   Router.addRouter(new DynamicRouter());

  4.   Router.addRouter(new DynamicWebRouter());
复制代码


  ### 路由动态刷新 为了让后台控制客户端的页面跳转,App在初始化的时候需要同步一下动态路由表接口,并缓存下来,每次需要做页面跳转的时候去表里查一下有没有动态配置项,如果有,则需要使用动态路由来做页面跳转。为了方便大家测试,我写了一个Spring项目,用来提供该接口,该项目同时也在github上开源了 RouterSender。

  1.   //客户端更新路由表

  2.   void refreshRouter(){

  3.   service.getRouters()

  4.   .enqueue(new Callback>() {

  5.   @Override

  6.   public void onResponse(Call> call, Response> response) {

  7.   if(response != null && response.body() != null) {

  8.   //RouterCache 用来缓存路由表

  9.   RouterCache.updateRouter(response.body());

  10.   } else {

  11.   Timber.w("路由更新失败");

  12.   }

  13.   }

  14.   @Override

  15.   public void onFailure(Call> call, Throwable t) {

  16.   Timber.e(t, "路由更新失败");

  17.   }

  18.   });

  19.   }
复制代码


  路由选择

  在每次跳转的时候进行的路由选择代码如下:

  1.   btn2.setOnClickListener(new View.OnClickListener() {

  2.   @Override

  3.   public void onClick(View v) {

  4.   Map map = new HashMap();

  5.   map.put(TwoActivity.KEY_NAME, "Tango");

  6.   //如果有动态配置,则用动态url代开,某则用activity://three 对应的页面打开 并带上map中的参数

  7.   RouterTry.tryOpenOr(MainActivity.this, RouterCache.getRoute(KEY_ACTION_TWO), "activity://three", map);

  8.   }

  9.   });
复制代码


  网页到App的统一入口

  为了能够从网页跳入原生页面,我们设置了一个统一的Activity入口,不管是从浏览器跳入App还是从WebView跳到某一个原生页面,都从该Activity中转。这主要是为了方便做一些过滤,以及实施一些安全策略。

  1.   public class RouterDispatchActivity extends Activity {

  2.   @Override

  3.   protected void onCreate(Bundle savedInstanceState) {

  4.   super.onCreate(savedInstanceState);

  5.   Intent intent = getIntent();

  6.   Uri uri = intent.getData();

  7.   Router.open(RouterDispatchActivity.this, uri.toString());

  8.   finish();

  9.   }

  10.   }
复制代码


  该Activity需要添加如下Intent-filter

  1.                 <intent-filter>
  2.                 <action android:name="android.intent.action.VIEW"/>
  3.                 <category android:name="android.intent.category.DEFAULT"/>
  4.                 <category android:name="android.intent.category.BROWSABLE"/>
  5.                 <data android:scheme="dynamic"/>
  6.                 <data android:scheme="dynamicWeb"/>
  7.             </intent-filter>
复制代码


  同时我们需要给WebView设置WebViewClient拦截网页内部的页面跳转,在发现是我们自定义协议链接,使用上面的RouterDispatchActivity来打开该Url。

  1.   private class CaptureWebViewClient extends WebViewClient {

  2.   ...

  3.   @Override

  4.   public boolean shouldOverrideUrlLoading(WebView view, String url) {

  5.   if(TextUtils.equals(UrlUtils.getScheme(url), "dynamic") || TextUtils.equals(UrlUtils.getScheme(url), "dynamicWeb")) {

  6.   Intent intent = new Intent();

  7.   intent.setAction(Intent.ACTION_VIEW);

  8.   intent.setData(Uri.parse(url));

  9.   mContext.startActivity(intent);

  10.   return true;

  11.   } else {

  12.   return false;

  13.   }

  14.   }

  15.   }
复制代码


  可能安全隐患

  外部网页通过自定义Schema的链接在App内部打开一些恶意网页,引导用户进行一些危险性操作(比如输入用户名和密码)

  参数泄露以及跨站攻击

  Intent Schema Url攻击

  规避策略

  在UrlDispathActivity中对Url进行过滤和安全处理。不过我感觉问题不大,这里只是给大家提供个思路,大家可以想想会不会有问题。

  总结

  以上即为我的路由动态配置思路和实现。如有问题和建议,欢迎提出。

  Demo项目地址

  Android实现Demo工程

  [服务器实现] (https://github.com/beautifulSoup/RouterSender)

原文作者:佚名  来源:开发者头条

相关帖子

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部