什么是 hybrid
hybrid:混合使用 Native 和 web 技术开发,即前端和客户端的混合开发。核心是快速迭代,无需审核。
主流技术框架
- web 渲染:Cordova(前身是 PhoneGap)
- 原生渲染:React Native、Weex
- 混合渲染:微信小程序
webview:是 app 的一个组件,用于加载内嵌的 h5 页面,即一个小型的浏览器内核
file 协议:加载本地资源文件(http(s) 协议则是网络请求,加载网络资源)
具体实现:
前端的静态页面交给客户端,以文件形式存储在 app 中,客户端在 webview 中使用 file 协议加载静态资源文件。
使用 NA:体验要求极致,变化不频繁(新闻资讯类 app 的首页)
使用 hybrid:体验要求高,变化频繁(新闻资讯类 app 的新闻详情页)
使用 h5:体验无要求,不常用(如活动、反馈、举报页面)
hybrid 和 h5 的比较
开发运维成本高,适用于产品的稳定功能,体验要求高,迭代频繁(产品型);
h5 适用于单次的活动页面或不常用页面(运营型);
hybrid 更新上线流程
上传静态资源包到服务端,客户端每次启动检查包文件版本是否更新,下载新的包文件并解压修改成新的版本号。
(服务端的版本和 zip 包维护,更新 zip 包之前,先对比版本号。)
js 和客户端通讯:JSBridge
JSBridge 是实现 web 端和 native 端双向通讯的一种机制,以 javascript 引擎或者 webview 容器为媒介,通过约定协议进行通讯。
而 Schema 协议是前端和客户端通讯的约定(形式就是常见的 url)。
window['_invoke_scan_callback_'] = function(result){alert(result)
}
var iframe = document.createElement('iframe');
iframe.style.display = none;
iframe.src = 'weixin://dl/scan?k1=v1&k2=v2&callback=_invoke_scan_callback_';
var body = document.body || document.getElementsByTagsName('body')[0];
body.appendChild('iframe');
setTimeout(function(){body.removeChild(iframe);
iframe = null;
})
window.invoke.share({title: 'xxx', content: 'xxx'}, function(result){if(result.errno === 0){alert('分享成功~~~~')
} else {
// 分享失败
alert(result.message)
}
})
(function (window, undefined) {
// 调用 schema 的封装
function _invoke(action, data, callback) {
// 拼装 schema 协议
var schema = 'myapp://utils/' + action
// 拼接参数
schema += '?a=a'
var key
for (key in data) {if (data.hasOwnProperty(key)) {schema += '&' + key + data[key]
}
}
// 处理 callback
var callbackName = ''if (typeof callback ==='string') {callbackName = callback} else {callbackName = action + Date.now()
window[callbackName] = callback
}
schema += 'callback=callbackName'
// 触发
var iframe = document.createElement('iframe')
iframe.style.display = 'none'
iframe.src = schema // 重要!var body = document.body
body.appendChild(iframe)
setTimeout(function () {body.removeChild(iframe)
iframe = null
})
}
// 暴露到全局变量
window.invoke = {share: function (data, callback) {_invoke('share', data, callback)
}
// ...
}
})(window)
// invoke.js 内置到客户端,客户端每次启动 webview 都默认执行。实现本地加载
1. 拦截 webView 请求的 URL Schema
兼容性好,但不直观、url 长度有限制
开源实现:JsBridge
通讯的基本形式:调用能力,传递参数,监听回调
2. 向 webView 注入 JS api
简单直观,Android 4.2+ 版本
开源实现:DsBridge
function showNativeDialog(showBtn, text){if(showBtn === 'showBtn1'){
// 一般是创建 iframe 元素节点,通过设定 src 属性发起请求
window.alert(`jsBridge://showNativeDialog?text=${text}拦截 URLSchema`);
}else {
// 向 webView 注入 JS api 方法通讯
window.NativeBridge.showNativeDialog(`${text} 向 webView 注入 JS api`);
}
}
客户端获取内容,然后 JS 通讯拿到内容,再渲染。
JSbridge 实现 Demo
1.web 端点击按钮,弹出 Native 端弹出框,显示 web 端 input 内容
<div class="ipt">
<input type="text" id="editText" placeholder="请输入内容">
</div>
// 1. 拦截 URLSchema
<button id="showBtn1">show Native Dialog(拦截 URLSchema)</button>
<hr>
// 2. 注入 JS api
<button id="showBtn2">show Native Dialog(向 webView 注入 JS api)</button>
function showNativeDialog(showBtn, text){if(showBtn === 'showBtn1'){
// 一般是创建 iframe 元素节点,通过设定 src 属性发起请求
window.alert(`jsBridge://showNativeDialog?text=${text}拦截 URLSchema`);
}else {
// 调用原生端的弹窗方法
window.NativeBridge.showNativeDialog(`${text} 向 webView 注入 JS api`);
}
}
private WebView webView;
private MainActivity self = this;
webView.setWebChromeClient(new WebChromeClient(){
// 拦截 webView 请求的 URL Schema 实现 jsBridge,重写对象拦截弹窗(开源实现 JsBridge)@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {if (!message.startsWith("jsBridge://")){return super.onJsAlert(view, url, message, result);
}
// 符合自定义 url Schema,执行 native 弹窗方法
String text = message.substring(message.indexOf("=") + 1);
self.showNativeDialog(text);
result.confirm();
return true;
}
});
// 显示原生弹窗
private void showNativeDialog (String text) {new AlertDialog.Builder(this).setMessage(text).create().show();
}
// 向 webView 注入 JS api(开源实现 DsBridge)webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");
class NativeBridge {
private Context ctx;
NativeBridge(Context ctx){this.ctx = ctx;}
@JavascriptInterface
public void showNativeDialog (String text) {new AlertDialog.Builder(ctx).setMessage(text).create().show();
}
}
2.Native 端点击按钮,弹出 web 端弹出框,显示 Native 端 input 内容
showBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 拿到 native 输入的内容,并调用显示 web 弹窗的方法
String iptValue = editText.getText().toString();
self.showWebDialog(iptValue);
}
});
// 显示 web 弹窗
private void showWebDialog (String text) {String jsCode = String.format("window.showWebDialog('%s')", text);
webView.evaluateJavascript(jsCode, null);
}
window.showWebDialog = text => alert(text);
dsbridge 开源库实现一个小 demo
1.web 端发送原生 http 请求
2. 原生端实现换肤功能
// 1. native 端定义 JsApi 类,实现 nativeRequest 方法。用来接收 web 发送过来的 url 字符串,获取地址然后发送原生 http 请求。public class JsApi{
@JavascriptInterface
public void nativeRequest(Object params, CompletionHandler handler) {
try {String url = ((JSONObject)params).getString("url");
String data = request(url);
handler.complete(data);
} catch (Exception e) {handler.complete(e.getMessage());
e.printStackTrace();}
}
private String request(String urlSpec) throws Exception {HttpURLConnection connection = (HttpURLConnection) new URL(urlSpec).openConnection();
connection.setRequestMethod("GET");
InputStream inputStream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuffer result = new StringBuffer();
String line;
while ((line = reader.readLine()) != null){result.append(line);
}
// 将 result 转为字符串(http 请求返回的内容)
return result.toString();}
}
// web 端发送 url 信息,并返回获取的请求内容。sendBtn.addEventListener("click", e => {dsBridge.call("nativeRequest", { url: urlText.value}, data => {response.textContent = data;})
});
2. 定义 changeTheme 方法,改变状态栏、标题栏、导航栏、web 页面的背景颜色。然后使用原生的菜单栏,调用 changeTheme 方法。private void changeTheme (int color){
// 状态栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
getWindow().setStatusBarColor(color);
// 标题栏
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(color));
// 导航栏
getWindow().setNavigationBarColor(color);
// web 网页
dwebView.callHandler("changeTheme", new Object[]{color});
}
//web 端改变背景色
dsBridge.register("changeTheme", color => {
// andriod 0xFFFF0000 ARGB
// web RGB/RGBA
document.body.style.backgroundColor = '#' + (color & 0x00FFFFFF).toString(16);
})
demo 上传地址:github 地址 https://github.com/NidusP/hybrid-demo
简单了解一些 hybrid 开发的知识,JSbridge 作为 web 端与 native 端的沟通桥梁,其中 schema 协议和 http(s)与 file 协议一样,规定了一种通讯方式,这种基本形式(调用能力,传递参数,监听回调)与 ajax 一样,十分容易理解。