最近公司项目开发中涉及到了大量的混合开发,这里开一个系列,把开发中的经验和遇到的问题和大家分享下
讲到移动端的混合开发,绕不开的一个话题就是原生和Js的交互,关于iOS、Android怎么和js交互,网上的资料很多,这里先简单介绍几个方法。
js部分
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<script>
function iOSShowDialog()
{
alert("iOS btn");
//showDialog 为iOS内定义好的方法
var params = {"title":"js 调用 iOS", "type":0, "callBack":"nativeCallBack"};
var paramsStr = JSON.stringify(params);
alert("iOS params:" + paramsStr);
window.webkit.messageHandlers.showDialog.postMessage(paramsStr);
}
function androidShowDialog()
{
alert("Android btn");
var params = {"title":"js 调用 Android", "type":1, "callBack":"nativeCallBack"};
var paramsStr = JSON.stringify(params);
alert("iOS params:" + paramsStr);
androidProxy.showDialog(paramsStr);
}
function nativeCallBack(paramsStr)
{
// var str:String = paramsStr;
var params = JSON.parse(paramsStr);
document.getElementById("info").innerHTML = params.message;
}
</script>
<body>
<body>
<div>
<button id="iOSBtn" onclick="iOSShowDialog()">click me call iOS show dialog</button>
</div>
<div>
<button id="AndroidBtn" onclick="androidShowDialog()">click me call Android show dialog</button>
</div>
<div>
<textarea id="info">...</textarea>
</div>
</body>
</body>
</html>
简单说一下,js里一共定义了三个方法,前两个iOSShowDialog,androidShowDialog
看名字应该就能猜出来,是分别用来调用iOS和Android原生的方法的,第三个方法nativeCallBack
是用来给原生回调的。
这里稍微多说一点,虽然js调用原生的方法是可以有返回值的,但是在设计这个构架时,还是建议使用异步回调的方式。因为在js和原生的通信场景中,有很多是异步流程,最典型的就是js向原生要一个数据用来在页面中展示,但是这个数据原生是需要通过服务器请求返回的。这个场景里,很明显函数返回值是没有办法满足需求的。
页面刷出来是这个样子:
js准备完毕
iOS部分
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// 创建配置
let config = WKWebViewConfiguration()
// 创建UserContentController(提供JavaScript向webView发送消息的方法)
let userContent = WKUserContentController()
// 添加消息处理,注意:self指代的对象需要遵守WKScriptMessageHandler协议,结束时需要移除
userContent.add(self, name: "showDialog");
// 将UserConttentController设置到配置文件
config.userContentController = userContent;
webView = WKWebView(frame: CGRect(x:0, y:0, width:self.view.bounds.width, height:300), configuration: config);
// let url = Bundle.main.url(forResource: "demo", withExtension: "html");
let url = URL(string: "http://你的域名/demo.html");
webView?.navigationDelegate = self;
webView?.uiDelegate = self;
// webView?.loadFileURL(url!, allowingReadAccessTo: Bundle.main.bundleURL);
webView?.load(URLRequest(url: url!));
self.view.addSubview(webView!);
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if(message.name == "showDialog")
{
let paramsStr = message.body as! String;
let paramsData = paramsStr.data(using: .utf8);
let params = try! JSONSerialization.jsonObject(with: paramsData!, options: .allowFragments) as! Dictionary<String, Any>;
let title = params["title"] as! String;
let type = params["type"] as! Int;
let callBack = params["callBack"] as! String;
let alert = UIAlertController(title: title, message: "type is "+type.description, preferredStyle: .alert);
alert.addAction(UIAlertAction(title: "确定", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil);
let callBackParams = ["message":"iOS的回调来了"];
let callBackParamsStrData = try! JSONSerialization.data(withJSONObject: callBackParams, options: []);
let callBackParamsStr = String(data: callBackParamsStrData, encoding: .utf8);
let callBackFunc = callBack+"("+"'\(callBackParamsStr!)'"+")";
webView?.evaluateJavaScript(callBackFunc, completionHandler: { (item, error) in
print(item, error);
})
}
}
这里只上两个核心的方法,iOS我没有用UIWebView+JavascriptCore,而是直接用了WKWebView,毕竟WKWebView效率和内存消耗好很多。但是WKWebView貌似不支持JavascriptCore,它有自己的js交互方式,并且也很简单:
// 创建配置
let config = WKWebViewConfiguration()
// 创建UserContentController(提供JavaScript向webView发送消息的方法)
let userContent = WKUserContentController()
// 添加消息处理,注意:self指代的对象需要遵守WKScriptMessageHandler协议,结束时需要移除
userContent.add(self, name: "showDialog");
// 将UserConttentController设置到配置文件
config.userContentController = userContent;
这里注意一下userContent.add(self, name: "showDialog");
这里的是向H5注入方法的过程,因此showDialog必须和js里调用的方法名一致。并且,第一个参数填入的对象必须遵守WKScriptMessageHandler
接口。
再来看响应,
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
刚才说了,userContent
传入的对象必须遵守WKScriptMessageHandler
接口,上面的函数就是接口里必须实现的方法。
if(message.name == "showDialog")
message对象的name属性是方法名
let paramsStr = message.body as! String;
message 对象的body是方法的参数
再来看回调,我在js传给iOS的json里添加了一个字段callBack,因此,回调只需要将callBack字段里的方法名取出,拼装成一个函数调用语句,通过WKWebView调用js的方法,调一下就可以了。
let callBackParams = ["message":"iOS的回调来了"];
let callBackParamsStrData = try! JSONSerialization.data(withJSONObject: callBackParams, options: []);
let callBackParamsStr = String(data: callBackParamsStrData, encoding: .utf8);
let callBackFunc = callBack+"("+"'\(callBackParamsStr!)'"+")";
webView?.evaluateJavaScript(callBackFunc, completionHandler: { (item, error) in
print(item, error);
})
注意这句,let callBackFunc = callBack+"("+"'\(callBackParamsStr!)'"+")";
刚才说了,拼装成一句函数调用语句。刚开始,我拼出来的是这样的:"nativeCallBack(["message":"iOS的回调来了"])"
乍一看,没毛病,但始终报错,后来才意识到,我在js的回调方法里定义的参数是一个String,也就是说,我调用的语句参数必须有引号包围。所以,正确的应该是这样的:"nativeCallBack( ' ["message":"iOS的回调来了"] ' )"
嗯,没毛病!
Android部分
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = mWebView.getSettings();
// 设置与Js交互的权限
webSettings.setJavaScriptEnabled(true);
// 通过addJavascriptInterface()将Java对象映射到JS对象
//参数1:Javascript对象名
//参数2:Java对象名
// 加载JS代码
// 格式规定为:file:///android_asset/文件名.html
mWebView.post(new Runnable() {
@Override
public void run() {
// mWebView.loadUrl("file:///android_asset/demo.html");
mWebView.loadUrl("http://你的域名/demo.html");
mWebView.addJavascriptInterface(new AndroidtoJs(mWebView), "androidProxy");//AndroidtoJS类对象映射到js的test对象
}
});
}
@JavascriptInterface
public void showDialog(String dataStr)
{
JSONObject jsonObject = null;
try {
jsonObject = new JSONObject(dataStr);
String title = jsonObject.getString("title");
int type = jsonObject.getInt("type");
Toast.makeText(AppApplication.getInstance(), title + "type:" + type, Toast.LENGTH_LONG).show();
final String callBack = jsonObject.getString("callBack");
Map<String,String> callBackParams = new HashMap<>();
callBackParams.put("message", "这是从Android原生返回的数据");
JSONObject paramsJsonObjct = new JSONObject(callBackParams);
final String paramsJsonObjctStr = paramsJsonObjct.toString();
Log.d("AndroidtoJs",paramsJsonObjctStr);
wView.post(new Runnable() {
@Override
public void run() {
wView.evaluateJavascript("javascript:" + callBack + "('" + paramsJsonObjctStr + "')", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
}
});
}
});
} catch (JSONException e) {
e.printStackTrace();
}
}
Android相对简单点,直接用WebView就行,方式其实和iOS类似,向H5注入一个对象,对象中定义的方法就可以背js调用。mWebView.addJavascriptInterface(new AndroidtoJs(mWebView), "androidProxy");//AndroidtoJS类对象映射到js的test对象
注意,androidProxy就是你在js里调用的对象名,AndroidtoJs是在Android项目里定义的对象,里面封装了需要暴露给js的方法public void showDialog(String dataStr)
。这个方法前一定要加上@JavascriptInterface
回调也很简单,
wView.post(new Runnable() {
@Override
public void run() {
wView.evaluateJavascript("javascript:" + callBack + "('" + paramsJsonObjctStr + "')", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
}
});
这里有一点注意的地方,Android规定调用webUiew对象的方法必须在同一个线程,不然会报错。因此,在调用webView.loadUrl和webView.evaluateJavascript时,都加上了webView.post(new Runnable() {}
尾声
第一篇就写这些,有什么问题望大家多多指教。