在APP开发过程中,不可避免会遇到html与native进行交互的情况。比如,在微信公众号里看到一篇运营文章,或者在朋友圈点开某条分享的链接,在打开的页面上,文末一般都会有一些引导性的操作。点击这些按钮,往往都会将对应的APP打开并直接进入相关页面。这个体验很令人惊喜,是吗?
再或者,在APP里,点击运营活动的内容或者一些广告,在打开的页面上也有类似的引导操作。这时,点击这些操作,也会像一般的页面跳转一样跳到目标页面。丝毫没有感觉到是Web页面与Native页面的交互。对吧?
上面说的两个现象,其实都是Web页面与APP Native的交互,具体又分为:
1.APP之外的浏览器页面与APP的交互
2.APP内部Web页面与Native页面的交互
如图所示:
如何实现?
首先需要制定一个hybrid协议。再具体点,就是要制定一个Uri的拼接规则。
Web页面的跳转是以打开新的url的方式进行的。
Uri的样式为:scheme://host/path?query=jsonParams, 或者称为:[scheme][authority][path][query=jsonParams],
以我们帮帮项目为例,uri的样式为:
/**
* 请求协议结构
* sherlock-bb://cn.bangbang?json-data
* sherlock-bb(scheme)
* cn.bangbang(host或者authority)
* json-data(query)
*/
可以看出我们的协议里是没有定义path值的。
第二、为支持外部浏览器的调用,需要在Manifest里设置IntentFilter。通常我们会选择主页面的Activity作为被调用的Activity。
其中增加一个Data类型的IntentFilter,对数据进行下限制:
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboard|keyboardHidden|fontScale|touchscreen|mcc|mnc|locale"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="portrait">
<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:host="cn.bangbang"
android:scheme="sherlock-bb" />
</intent-filter>
</activity>
2.1 首先要注意的是,需要将该Activity的启动模式设置为singleTask,原因很明显,在APP已经在后台运行的情况下,主页面不需要被android系统再次创建。
2.2其次,微信浏览器会截断query部分的数据,导致跳转异常。这里通常有两种解决办法:
2.2.1 在微信打开的该Html页面引导用户选择title右上角菜单里的“在浏览器打开”,使用其他浏览器再次打开该html页面,然后在点击引导性的操作。
2.2.2 可以通过业务合作的方式,让微信将html使用的域名加入到微信的白名单中,这样微信就不会对页面里的url做处理了。
3.对于APP内部,WebView与Native的交互,截获url的地方有两处,这里直接贴代码:
class MyWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
String scheme = uri.getScheme();
if (!("http".equals(scheme) || "https".equals(scheme))) {
String auth = uri.getAuthority();
if (MyHtmlLink.HOST.equalsIgnoreCase(auth)) {
String jsonData = MyWebViewUtils.handleUrl(url);
if (!StringUtil.isEmpty(jsonData)) {
procHtmlLinkData(jsonData);
}
return true;
}
}
return super.shouldOverrideUrlLoading(view, url);
}
@Override
public void onLoadResource(WebView view, String url) {
Uri uri = Uri.parse(url);
String scheme = uri.getScheme();
if (MyHtmlLink.SCHEME.equalsIgnoreCase(scheme)) {
view.clearHistory();
String jsonData = MyWebViewUtils.handleUrl(url);
if (!StringUtil.isEmpty(jsonData)) {
procHtmlLinkData(jsonData);
}
} else {
super.onLoadResource(view, url);
}
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
if (handler != null) {
handler.proceed();
}
}
}
3.1 WebView的load方法是个多态方法,不仅可以load一个标准的http打头的url,而且可以传入http的header,如:
public void loadUrl(String url, Map<String, String> additionalHttpHeaders)
而且可以调用js的方法,通知到html页面。如:
mWebView.loadUrl("javascript:cn.wb.appbridge.outer.transFormatedMsg('" + fe_defined_paramSting + "')");
mWebView.loadUrl("javascript:" + fe_defined_method + "('" + paramSting + "')");
其中的fe_defined_paramSting、fe_defined_method、paramSting都是根据业务需要,由开发该html页面的FE同学指定的,APP的同学配合就好。
4.题外话,有个UriMatcher类在处理url类型的switch case时相当好用。用法也很简单:
先创建一个对象:
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
然后将需要case的类型注册一下,这里是可以进行模糊匹配的
public void addURI(String authority, String path, int code)
最后,在需要switch case时,调用match方法,则会返回addURI中注册的match值:code,如果没有匹配到,则返回-1.
public int match(Uri uri)