Files
knowledge-kit/Chapter1 - iOS/1.10.md
2020-02-25 17:46:51 +08:00

207 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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&param2=value2...**
在UIWebView的delegate函数中我们判断请求的scheme如果request.URL.scheme是jsbridge那么就不进行网页内容的加载而是去执行相应的方法。方法名称就是request.URL.host。参数可以通过request.URL.query得到。
问题来了??
发起这样1个网络请求有2种方式。1:location.href .2iframe。通过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是同步。