赞
踩
目前市面上大多数手游一般都会内嵌WebView用于显示社区、H5活动等页面。WebView技术对于大多数人来说并不陌生,但要做好WebView和游戏客户端的交互需求并不是一个简单的事,一不小心就可能踩坑。
Unity中显示网页的方案可以使用现成的插件,也可以自己实现。 自己实现需要分别在Android端和iOS端实现WebView功能,然后打包成插件放入Unity的Assets\Plugins
目录中,Unity端通过API调用插件代码即可使用WebView。以Android端为例,典型的插件方案实现流程:首先插件中自定义Dialog,在Dialog中定义好对外提供的接口(如:loadUrl等),导出jar/aar包放到Assets\Plugins\Android
目录,Unity端调用。常规的插件代码如下:
public class WebWiewDialog extends DialogFragment { private WebView mWebView; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view=inflater.inflate(R.layout.layout_webview,false); mWebView=view.findViewById(R.id.webview); initWebSettings(mWebView); return view; } //对外暴露的接口 public void loadUrl(String url){ if(mWebView!=null){ mWebView.loadUrl(url); } } private void initWebSettings(WebView webView){ //配置项 WebSettings webSettings=webView.getSettings(); //5.0以上开启混合模式加载 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); } //允许js代码 webSettings.setJavaScriptEnabled(true); //设置UA webSettings.setUserAgentString(...); //自定义WebViewClient webView.setWebViewClient(...); //自定义WebChromeClient webView.setWebChromeClient(...); //注册js映射对象,方便进行交互 webView.addJavascriptInterface(new AndroidJavaScriptObj(), "android"); ... } .... }
这个问题可以分解成两个部分:Unity和Android/iOS原生代码进行交互、Android/iOS原生代码与H5进行交互。
Unity调用Android端原生代码,一般通过调用API,以反射的方式实现。
var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
//反射获取WebWiewDialog
AndroidJavaObject mWebView = unityClass.GetStatic<AndroidJavaObject>("currentActivity").Call<AndroidJavaObject>("getWebView");
//反射调用方法
mWebView.Call("loadUrl","http://...");
iOS端则比较方便,直接导入方法调用即可。
[DllImport("__Internal")]
public static extern void loadUrl(string url);
Android/iOS端如果需要传递数据给Unity端的话,都需要使用UnitySendMessage
方法。
UnityPlayer.UnitySendMessage(name, "方法名", "参数");
原生代码与H5进行交互通常需要使用js技术。这里也涉及到两个部分:原生调用js代码,js调用原生代码。二者的沟通桥梁通过WebView的API来实现。
Android端调用JS的方式有两种:WebView.loadUrl("javascript:函数名")
和WebView.evaluateJavascript("JS代码",回调接口)
。前者通过给WebView加载JS的方式去调用,但是无法拿到函数的调用返回值。后者的话可以方便的获取函数返回值,且执行效率高,但是这个API是在Android4.4新增的,使用时务必注意项目支持的最低Android版本,以免引起兼容问题。
JS调用Android端有三种方式:
通过WebView
的addJavascriptInterface()
进行对象映射。
public class AndroidJavaScriptObj extends Object {
@JavascriptInterface
public int parseCode(String msg) {
int code =0;
System.out.println("JS调用了Android的parseCode方法");
...
return code;
}
}
//注册对象
webView.addJavascriptInterface(new AndroidJavaScriptObj(), "android");
JS端通过window.注册对象名.注册方法()
即可调用Android端的代码。
优点是使用起来非常简单,可以方便的拿到函数返回值。缺点是此方式在Android4.2之前存在远程代码执行漏洞。
通过 WebViewClient
的shouldOverrideUrlLoading ()
方法回调拦截 url。
此方式需要约定好url协议格式,比如:custom://xxx?k1=v1&k2=v2,尤其需要注意url中的特殊字符会被自动编码的问题。
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
if ( uri.getScheme().equals("custom")) {
//做相应处理
...
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
JS端通过document.location
给Android端传递数据
document.location = "custom://xxx?k1=v1&k2=v2";
此方法的缺点是只能单向传递数据,无法拿到返回值,且使用稍微复杂,需要注意编码转义问题。
通过 WebChromeClient
的onJsPrompt()
方法回调拦截JS对话框prompt()
消息,此方式同样需要约定好通信协议,但协议比较自由,不强制使用uri的格式。无需关心编码转义问题。
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
Uri uri = Uri.parse(message);//message格式自由,这里以Uri举例,但并不强制使用Uri格式
if ( uri.getScheme().equals("自定义协议")) {
//做相应处理
...
result.confirm(返回值);
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
JS端通过prompt("...")
调用即可,可以方便的拿到调用返回值。
以上三种方式在使用上各有优缺点,需要结合自己的项目的实际需求和版本兼容情况做出方案选择。
iOS中有两个WebView控件:WKWebView、UIWebView。其中UIWebView性能较差,已经不建议使用,以下以WKWebView举例。
iOS端调用JS的方式有两种:
使用stringByEvaluatingJavaScriptFromString
方法。此方法会同步返回结果,如果在JS中执行了耗时操作的话,可能会造成UI阻塞。
NSString *result = [webView stringByEvaluatingJavaScriptFromString:jsStr];
使用evaluateJavaScript
方法,异步返回执行结果。
[self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSLog(@"%@----%@",result, error);
}];
JS调用iOS端有两种原生方式:
iOS端使用WKNavigationDelegate中的代理方法,拦截自定义的URL来实现JS调用OC方法。同Android原理一样,通常需要拦截约定协议的url来实现,需要注意url编码问题。JS端通过document.location
调用给iOS端传递数据,
-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{ NSURL *URL = navigationAction.request.URL; NSString *scheme = [URL scheme]; if ([scheme isEqualToString:@"自定义协议"]) { //执行操作 ... //返回执行结果(可选) NSString *jsStr = ...; [self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) { NSLog(@"%@----%@",result, error); }]; decisionHandler(WKNavigationActionPolicyCancel); return; } decisionHandler(WKNavigationActionPolicyAllow); }
通过MessageHandler来交互,此方式使用起来方便,不易出错。初始化WkWebView前需要通过addScriptMessageHandler
注册MessageHandler,可以注册多个。
UIView *unityView = UnityGetGLViewController().view;
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.userContentController = [WKUserContentController new];
//注册MessageHandler
[config.userContentController addScriptMessageHandler:self name:@"消息名"];
WKWebView *webView = [[WKWebView alloc] initWithFrame:unityView.frame configuration:config];
然后实现WKScriptMessageHandler
协议中的didReceiveScriptMessage
。
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { if ([message.name isEqualToString:@"消息名"]) { NSString *value1 = [message.body valueForKey:@"key1"]; NSDictionary *value2 = [message.body valueForKey:@"key2"]; //执行函数操作 .... //返回执行结果(可选) NSString *jsStr = ...; [self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) { NSLog(@"%@----%@",result, error); }]; } }
JS端调用方式为:window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
//JS端调用
var data = {
"key1": "value1",
"key2": {
}
};
window.webkit.messageHandlers.消息名.postMessage(data);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。