mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-24 20:00:37 +00:00
207 lines
7.5 KiB
Markdown
207 lines
7.5 KiB
Markdown
# UIWebView加载网页内容
|
||
|
||
可以通过本地文件、url等方式。
|
||
|
||
```objective-c
|
||
NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
|
||
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:htmlPath]];
|
||
[self.webView loadRequest:request];
|
||
```
|
||
|
||
## Native调用JavaScript
|
||
|
||
Native调用JS是通过UIWebView的stringByEvaluatingJavaScriptFromString 方法实现的,该方法返回js脚本的执行结果。
|
||
|
||
```objective-c
|
||
[webView stringByEvaluatingJavaScriptFromString:@"Math.random();"];
|
||
```
|
||
|
||
实际上就是调用了网页的Window下的一个对象。如果我们需要让native端调用js方法,那么这个js方法必须在window下可以访问到。
|
||
|
||
|
||
## JavaScript调用Native
|
||
|
||
反过来,JavaScript调用Native,并没有现成的API可以调用,而是间接地通过一些其它手段来实现。UIWebView有个代理方法:在UIWebView内发起的任何网络请求都可以通过delegate函数在Native层得到通知。由此思路,我们就可以在UIWebView内发起一个自定义的网络请求,通常是这样的格式:**jsbridge://methodName?param1=value1¶m2=value2...**
|
||
|
||
在UIWebView的delegate函数中,我们判断请求的scheme,如果request.URL.scheme是jsbridge,那么就不进行网页内容的加载,而是去执行相应的方法。方法名称就是request.URL.host。参数可以通过request.URL.query得到。
|
||
|
||
问题来了??
|
||
|
||
发起这样1个网络请求有2种方式。1:location.href .2:iframe。通过location.href有个问题,就是如果js多次调用原生的方法也就是location.href的值多次变化,Native端只能接受到最后一次请求,前面的请求会被忽略掉。
|
||
|
||
使用ifrmae方式,以调用Native端的方法。
|
||
|
||
```javascript
|
||
var iFrame;
|
||
iFrame = document.createElement("iframe");
|
||
iFrame.style.height = "1px";
|
||
iFrame.style.width = "1px";
|
||
iFrame.style.display = "none";
|
||
iFrame.src = url;
|
||
document.body.appendChild(iFrame);
|
||
setTimeout(function(){
|
||
iFrame.remove();
|
||
},100);
|
||
```
|
||
|
||
举个🌰:
|
||
|
||
需求:
|
||
|
||
原生端提供一个UIWebView,加载一个网页内容。还有1个按钮,按钮点击一下网页增加一段段落文本。网页上有2个输入框,用户输入数字,点击按钮,js将用户输入的参数告诉native端,native去执行加法,计算完成后将结果返回给js
|
||
|
||
```html
|
||
<html>
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=utf8">
|
||
<script language="javascript">
|
||
function loadURL(url) {
|
||
var iFrame;
|
||
iFrame = document.createElement("iframe");
|
||
iFrame.style.height = "1px";
|
||
iFrame.style.width = "1px";
|
||
iFrame.style.display = "none";
|
||
iFrame.src = url;
|
||
document.body.appendChild(iFrame);
|
||
setTimeout(function () {
|
||
iFrame.remove();
|
||
}, 100);
|
||
}
|
||
|
||
|
||
function receiveValue(value) {
|
||
alert("从原生拿到加法结果为:" + value);
|
||
}
|
||
|
||
function check() {
|
||
var par1 = document.getElementById("par1").value;
|
||
var par2 = document.getElementById("par2").value;
|
||
loadURL("JSBridge://plus?par1=" + par1 + "&par2=" + par2);
|
||
}
|
||
|
||
</script>
|
||
</head>
|
||
|
||
<body>
|
||
<input type="text" placeholder="请输入数字" id="par1" /> +
|
||
<input type="text" placeholder="请输入数字" id="par2" />
|
||
<input type="button" value="=" onclick="check()" />
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
```objective-c
|
||
-(void)addContentToWebView{
|
||
NSString *jsString = @" var pNode = document.createElement(\"p\"); pNode.innerText = \"我是由原生代码调用js后将一段文件添加到html上,也就是注入\";document.body.appendChild(pNode);";
|
||
[self.webView stringByEvaluatingJavaScriptFromString:jsString];
|
||
}
|
||
|
||
|
||
-(NSInteger)plusparm:(NSInteger)par1 parm2:(NSInteger)par2{
|
||
return par1 + par2;
|
||
}
|
||
|
||
|
||
#pragma mark -- UIWebViewDelegate
|
||
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
|
||
NSURL *url = request.URL;
|
||
NSString *scheme = url.scheme;
|
||
NSString *method = url.host;
|
||
NSString *parms = url.query;
|
||
NSArray *pars = [parms componentsSeparatedByString:@"&"];
|
||
NSInteger par1 = [[pars[0] substringFromIndex:5] integerValue];
|
||
NSInteger par2 = [[pars[1] substringFromIndex:5] integerValue];
|
||
if ([scheme isEqualToString:@"jsbridge"]) {
|
||
//发现scheme是JSBridge,那么就是自定义的URLscheme,不去加载网页内容而拦截去处理事件。
|
||
|
||
if ([method isEqualToString:@"plus"]) {
|
||
NSInteger result = [self plusparm:par1 parm2:par2];
|
||
[self.webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"receiveValue(%@);",@(result)]];
|
||
}
|
||
|
||
return NO;
|
||
}
|
||
return YES;
|
||
}
|
||
```
|
||
|
||
|
||
## Android 端如何与 JS 通信(2种方法)
|
||
|
||
- webview.loadUrl()
|
||
- Webview.evaluateJavascript()
|
||
|
||
> 2者区别:
|
||
>
|
||
> 1. loadUrl() 会刷新页面,evaluateJavascript() 则不会刷新页面,效率高
|
||
> 2. loadUrl() 得不到 JS 的返回值;evaluateJavascrip() 则可以获取返回值
|
||
> 3. evaluateJavascrip() 在 Android 4.4 之后才可以使用
|
||
|
||
注意:Android 可以直接调用 JS 的 alert() 方法是因为 alert 方法直接挂载在 window 对象上。但是 Native 与 JS 可能不止一个方法、多个方法多个属性去访问,这样都直接挂载在 window 对象上不是明智之举。因为后期维护很不方便。所以我们在 Native 和 JS 之间会设置一个桥接对象,像一个中间层一样,让2端互调。
|
||
|
||
Android 需要在页面加载完,也就是 webview 的 onPageFinished 方法中写调用逻辑,否则不会执行
|
||
|
||
```java
|
||
webView.loadUrl("javascript:callJsFunction('soloname')")
|
||
webView.evaluateJavascript("javascript:callJsFunction('soloname')"
|
||
```
|
||
|
||
|
||
### JS 如何与 Android 通信
|
||
|
||
- 通过 Webview 的 addJavascriptInterface() 进行对象映射
|
||
- 通过 WebviewClient 的 shouldOverrideUrlLoading() 方法回调拦截 Url
|
||
- 通过 webChromeClient 的 onJsAlert()、onJSPrompt() 方法回调拦截 JS 对话框 alert()、confirm()、prompt() 等消息
|
||
|
||
第一种最简洁,但是在 Android 4.2 以下存在漏洞。
|
||
|
||
实验:Android webview 上跑一个网页,点击网页的按钮,让 Native 弹出一个字符串。
|
||
|
||
```vue
|
||
methods: {
|
||
showAndroidToast() {
|
||
$App.showToast("哈哈,我是js调用的")
|
||
}
|
||
}
|
||
```
|
||
|
||
```
|
||
public class JsJavaBridge {
|
||
|
||
private Activity activity;
|
||
private WebView webView;
|
||
|
||
public JsJavaBridge(Activity activity, WebView webView) {
|
||
this.activity = activity;
|
||
this.webView = webView;
|
||
}
|
||
|
||
@JavascriptInterface
|
||
public void onFinishActivity() {
|
||
activity.finish();
|
||
}
|
||
|
||
@JavascriptInterface
|
||
public void showToast(String msg) {
|
||
ToastUtils.show(msg);
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
然后通过 webview 设置 Android 类与 JS 代码的映射
|
||
|
||
```
|
||
webView.addJavascriptInterface(new JsJavaBridge(this, tbsWebView), "$App");
|
||
```
|
||
|
||
这里将类 JsJavaBridge 在 JS 中映射为了 $App,所以在 Vue 中可以这样调用 `$App.showToast("哈哈,我是js调用的")`。
|
||
|
||
|
||
|
||
## 同步和异步问题
|
||
|
||
js调用native是通过在一个网页上插入一个iframe,这个iframe插入完了就完了,执行的结果需要native另外调用stringByEvaluatingJavaScriptString 方法通知js。这明显是1个异步的调用。而stringByEvaluatingJavaScriptString方法会返回执行js脚本的结果。本质上是一个同步调用
|
||
|
||
所以js call native是异步,native call js是同步。
|