说到热更新,大多数人的第一印象肯定是AndFix或者HotFix等热更新框架。但是一来这些框架学习成本较高,坑较多,二来对于大的模块更新支持不好。所以在实际开发功能中,对于一些紧急的功能上线或者bug修复,使用H5页面替换原生页面是一个较为简单和方便的方案。本文主要讲解如何使用路由动态配置思想实现App内任意页面的替换。同时会给出一个 Demo工程 ,方便大家学习和实现。
首先为了实现原生页面和H5页面之间的任意跳转,我们需要定义一套统一的Url规范。我们知道一个Url主要由Schema、Host、path以及QueryParameter等构成。以下分条给出定义。
Path
Path包括Host部分来区分某一个页面。某些同学可能会问,在H5那边Host是用来表示服务器域名的,这不就不兼容了吗,如果手机没有安装App,用我们这种规范定义的url肯定就无法打开了。这确实是一个问题,但是我想如果为了兼容H5在客户端页面的url中加上服务器的域名感觉也很奇怪。所以作为一个客户端程序员来说,这样比较方便,就暂且这么定。而为了解决没有安装客户端的情况,需要给H5页面的跳转链接加上备选链接。为了解决客户端原生页面跳转H5页面的情况,需要为我们定义的Url加上H5服务器的域名。这是本方案不太优雅的地方,各位有什么建议欢迎提出。
为了方便的通过Url打开Activity或者WebView,我们需要一个路由框架。这里我推荐我开源的一个路由框架 AndRouter 。AndRouter会根据Url的Schema选择不同的方式来打开这个url。同时它提供了ActivityRouter(将以activity为Schema的url转为Intent并打开对应Activity)的实现和BrowserRouter(用浏览器打开以http和https为Schema的url)的实现。为了让AndRouter支持我们自定义的dynamic和dynamicWeb为Schema的连接,我们需要自定义两个Router如下。
public class DynamicRouter implements IRouter { private static final String DEFAULT_SCHEME = "dynamic"; ... protected boolean open(Context context, IRoute route){ String routeUrl = route.getUrl(); boolean isSuccess = false; if(canOpenTheRoute(route)) { //首先尝试用原生的Activity打开,如果无法打开,则使用WebView打开 try { Uri uri = Uri.parse(routeUrl) .buildUpon() .scheme("activity") .build(); //尝试Activity打开 if (!Router.open(context, uri.toString())) { //失败,换用WebView打开,但是需要给Url加上H5域名 String path = UrlConfig.BASE_URL + UrlUtils.getHost(routeUrl); Timber.i("path : %s", path); String url = UrlUtils.addQueryParameters(path, UrlUtils.getParameters(routeUrl)); isSuccess = Router.open(context, url); } else { isSuccess = true; } } catch (Exception e) { Timber.e(e, ""); } } return isSuccess; } ... }
DynamicWebRouter 同理,不过不会尝试用Activity打开。
public class DynamicWebRouter extends BaseRouter { private static final String DEFAULT_SCHEMA = "dynamicWeb"; ... protected boolean open(Context context, IRoute route){ boolean isSuccess = false; if(canOpenTheRoute(route)){ //强制使用WebViewActivity打开 String routeUrl = route.getUrl(); try { String host = UrlUtils.getHost(routeUrl); String path = UrlConfig.BASE_URL + host; Timber.i("path %s", path); String url = UrlUtils.addQueryParameters(path, UrlUtils.getParameters(routeUrl)); isSuccess = Router.open(context, url); } catch (Exception e){ Timber.e(e, ""); } } return isSuccess; } ... }
给路由框架加上以上两个Router的实现,然后AndRouter就支持打开我们自定义的Schema Url了。
Router.initActivityRouter(getContext()); Router.addRouter(new WebRouter()); Router.addRouter(new DynamicRouter()); Router.addRouter(new DynamicWebRouter());
### 路由动态刷新 为了让后台控制客户端的页面跳转,App在初始化的时候需要同步一下动态路由表接口,并缓存下来,每次需要做页面跳转的时候去表里查一下有没有动态配置项,如果有,则需要使用动态路由来做页面跳转。为了方便大家测试,我写了一个Spring项目,用来提供该接口,该项目同时也在github上开源了 RouterSender 。
//客户端更新路由表 void refreshRouter(){ service.getRouters() .enqueue(new Callback<Map<String, String>>() { @Override public void onResponse(Call<Map<String, String>> call, Response<Map<String, String>> response) { if(response != null && response.body() != null) { //RouterCache 用来缓存路由表 RouterCache.updateRouter(response.body()); } else { Timber.w("路由更新失败"); } } @Override public void onFailure(Call<Map<String, String>> call, Throwable t) { Timber.e(t, "路由更新失败"); } }); }
在每次跳转的时候进行的路由选择代码如下:
btn2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Map<String, String> map = new HashMap<String, String>(); map.put(TwoActivity.KEY_NAME, "Tango"); //如果有动态配置,则用动态url代开,某则用activity://three 对应的页面打开 并带上map中的参数 RouterTry.tryOpenOr(MainActivity.this, RouterCache.getRoute(KEY_ACTION_TWO), "activity://three", map); } });
为了能够从网页跳入原生页面,我们设置了一个统一的Activity入口,不管是从浏览器跳入App还是从WebView跳到某一个原生页面,都从该Activity中转。这主要是为了方便做一些过滤,以及实施一些安全策略。
public class RouterDispatchActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); Uri uri = intent.getData(); Router.open(RouterDispatchActivity.this, uri.toString()); finish(); } }
该Activity需要添加如下Intent-filter
<intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="dynamic"/> <data android:scheme="dynamicWeb"/> </intent-filter>
同时我们需要给WebView设置WebViewClient拦截网页内部的页面跳转,在发现是我们自定义协议链接,使用上面的RouterDispatchActivity来打开该Url。
private class CaptureWebViewClient extends WebViewClient { ... @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if(TextUtils.equals(UrlUtils.getScheme(url), "dynamic") || TextUtils.equals(UrlUtils.getScheme(url), "dynamicWeb")) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); mContext.startActivity(intent); return true; } else { return false; } } }
在UrlDispathActivity中对Url进行过滤和安全处理。不过我感觉问题不大,这里只是给大家提供个思路,大家可以想想会不会有问题。
以上即为我的路由动态配置思路和实现。如有问题和建议,欢迎提出。
欢迎Follow我的GitHub账号
欢迎关注我的简书账号
欢迎关注我的微薄账号