js 与 App 原生交互
几乎上所有的 App 上面都会有 H5 网页的活动,特别是我们做电商的,我们公司也是这样的。比如 H5 可能需要在 App 里面能获取到用户信息,分享之后的回调需要 App 来告知 H5,从而完成UI的渲染。
我们需要有一套从 App 端很容易注册方法给 js 用,js 也很容易调用 App 的方法。
jsbridge
首先我们的数据通过字符串来传递(json)。
我们可以写一个本地的 js 脚本 在 资源文件下面,来方便我们去记录 js 和 App 间的 注册的方法什么的,作为我们跟 js 交互的中间件。这个中间件主要的作用是记录 js 那边注册的方法、给 js 调用 App 的方法,App 调用 js 的入口。
(function() {
if (window.HuoqiuJavascriptBridge) {
return;
}
var registerFunctions = {};
//set default messageHandler
function init(defaultMessageHandler) {
if (HuoqiuJavascriptBridge.defaultMessageHandler) {
throw new Error('HuoqiuJavascriptBridge.init called twice');
}
HuoqiuJavascriptBridge.defaultMessageHandler = defaultMessageHandler;
}
function registerFunction(functionName, functionHandler) {
registerFunctions[functionName] = functionHandler;
}
function callFunction(functionName, params, callback) {
var message = { functionName: functionName, params: params};
var messageString = JSON.stringify(message);
//console.log(messageString);
if (window.jsbridge) {
var response = window.jsbridge.callFunction(messageString);
if (callback) {
callback(response);
}
return response;
}
return '';
}
function handleMessageFromNative(messageJSON) {
//console.log(messageJSON);
var message = JSON.parse(messageJSON);
var functionHandler = HuoqiuJavascriptBridge.defaultMessageHandler;
if (message.functionName) {
functionHandler = registerFunctions[message.functionName];
}
//查找指定function
try {
return functionHandler(message.params);
} catch (exception) {
if (typeof console != 'undefined') {
console.log("HuoqiuJavascriptBridge: WARNING: javascript handler threw.", message, exception);
}
}
}
var HuoqiuJavascriptBridge = window.HuoqiuJavascriptBridge = {
init: init,
registerHandler: registerFunction,
callHandler: callFunction,
handleMessageFromNative: handleMessageFromNative
};
var doc = document;
var readyEvent = doc.createEvent('Events');
readyEvent.initEvent('HuoqiuJavascriptBridgeReady');
readyEvent.bridge = HuoqiuJavascriptBridge;
doc.dispatchEvent(readyEvent);
})();
大概就是提供了js App 唯一的调用入口,各个方法名 参数 用 json 保存,这样就不用每个方法都写出来,相当于动态添加的。
最后因为 App 的 webview 是需要去加载这段代码的,所以有个时间差,这个时间之后 js 才能去使用 bridge,所以最后发送了一个 ready 事件 告知 js 可以开始使用了。之后 就可以 registerHandler,callHandler 来注册和调用 App 的方法了。
App 端
Android 里面的 @JavascriptInterface 和 evaluateJavascript ( API < 19 用 loadurl)可以来帮助我们完成这些功能。
class HuoqiuJavascriptBridge(private var mWebViewHolder: WebView?, private var mGson: Gson) {
private val JS_HANDLE_MESSAGE_FROM_NATIVE = "javascript:HuoqiuJavascriptBridge.handleMessageFromNative('%s');"
private var registerFunctions: MutableMap<String, CallBack> = HashMap()
init {
if (mWebViewHolder == null)
throw NullPointerException("WebView in HuoqiuJavascriptBridge can not be null!")
mWebViewHolder?.addJavascriptInterface(this, "jsbridge")
}
//don't call this
@JavascriptInterface
fun callFunction(params: String?): String?{
if (!TextUtils.isEmpty(params)) {
var message = mGson.fromJson(params, Message::class.java)
val callback = registerFunctions[message.functionName]
if (callback != null) {
return callback.onCallBack(message.params)
}
//Log.d("function", "${message.functionName} : ${message.params}")
}
return "function_not_found"
}
fun registerFunction(functionName: String, callBack: CallBack) {
registerFunctions[functionName] = callBack
}
fun callFunction(functionName: String, params: String?, callBack: CallBack?) {
val message = Message()
message.params = params
message.functionName = functionName
var messageJson = message.toJson(mGson)
messageJson = messageJson.replace("(\\\\)([^utrn])".toRegex(), "\\\\\\\\$1$2")
messageJson = messageJson.replace("(?<=[^\\\\])(\")".toRegex(), "\\\\\"")
val javascriptCommand = String.format(JS_HANDLE_MESSAGE_FROM_NATIVE, messageJson)
if (Thread.currentThread() === Looper.getMainLooper().thread) {
this.loadJs(javascriptCommand, callBack)
}else{
Log.w("HuoqiuJavascriptBridge", "callFunction should be in main thread !")
}
}
private fun loadJs(script: String, callBack: CallBack?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mWebViewHolder?.evaluateJavascript(script, {
callBack?.onCallBack(it)
})
} else {
mWebViewHolder?.loadUrl(script)
}
}
fun loadHuoqiuJavascriptBridge() {
val jsContent = assetFile2Str(mWebViewHolder?.context, "HuoqiuJavascriptBridge.js")
loadJs("javascript:" + jsContent, null)
}
private fun assetFile2Str(context: Context?, urlStr: String?): String? {
if (context == null || TextUtils.isEmpty(urlStr))
return null
var inputStream : InputStream? = null
try {
inputStream = context.assets.open(urlStr)
val bufferedReader = BufferedReader(InputStreamReader(inputStream!!))
var line: String?
val sb = StringBuilder()
do {
line = bufferedReader.readLine()
if (line != null && !line.matches("^\\s*//.*".toRegex())) {
sb.append(line)
}
} while (line != null)
bufferedReader.close()
inputStream.close()
return sb.toString()
} catch (e: Exception) {
e.printStackTrace()
} finally {
if (inputStream != null) {
try {
inputStream.close()
} catch (e: IOException) {
}
}
}
return null
}
fun onDestroy() {
registerFunctions.clear()
mWebViewHolder = null
}
}
其中 loadHuoqiuJavascriptBridge 方法在 webview 的 pagefinish 里面调用,之后就可以调用 registerFunction callFunction 来注册给 js 和调用 js 的方法了,另外 onDestroy 在 页面 onDestroy 调用。
网友评论