Hybrid App 开发浅谈
缘起智能手机兴起,移动端开发火热起来。起初只有Android和IOS应用,随着H5的问世,以及手机性能的提升,Web App开始斩露头角。一份代码,多端运行。这预示着开发成本的降低,同时也能减少因开发者实现的差异导致Android和IOS应用出现不同的表示。发展如果某一块的功能简单,仅仅是一些页面的展示,那么无用质疑,使用Web App技术现实更优,通过WebView嵌入到原生应用中。...

缘起
智能手机兴起,移动端开发火热起来。起初只有Android和IOS应用,随着H5的问世,以及手机性能的提升,Web App开始斩露头角。
一份代码,多端运行。这预示着开发成本的降低,同时也能减少因开发者实现的差异导致Android和IOS应用出现不同的表示。
发展
如果某一块的功能简单,仅仅是一些页面的展示,那么无用质疑,使用Web App技术现实更优,通过WebView嵌入到原生应用中。
最初公司的网页代码是放在服务端。但是这样导致一个问题,就是进入网页时,会有白屏的现象。后来就提出将网页直接放到原生应用中,因为我们采用的前端后分离的模式,数据全部通过异步请求去获取,所以该方案可以实现。
当然这种将网页放到原生应用中会有一个问题,就是安装包的体积会增大。这个需要根据实际情况去衡量,是否采用这种方案。我们认为相比于体积的增大,白屏的体验更糟糕。
开发
Hybrid App得以发展的核心就是WebView,而开发的重点就在于原生应用和Web App的通信。
Android 与 Web App
如果在Android中使用WebView记得在AndroidManifest.xml中配置网络权限
<uses-permission android:name="android.permission.INTERNET"/>
Android 9.0 默认使用加密连接,这意味着老旧项目在android 9.0 设备上运行,会遇到异常的情况。
在配置中加上下面内容即可,true是否使用明文传输,也就是可以使用http
android:usesCleartextTraffic="true"
Android 调用 JS
Android 调用 JS 有两种方式,都是通过 WebView 的方法:
- webview.loadUrl()
- webview.evaluateJavascript()
二者区别:
- loadUrl() 会刷新页面,evaluateJavascript() 则不会使页面刷新,所以 evaluateJavascript() 的效率更高
- loadUrl() 得不到 js 的返回值,evaluateJavascript() 可以获取返回值
- evaluateJavascript() 在 Android 4.4 之后才可以使用
代码示例:
package com.wit;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class WebActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web);
final WebView webView = findViewById(R.id.wv);
webView.loadUrl("file:///android_asset/index.html");
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
webView.post(new Runnable() {
@Override
public void run() {
webView.evaluateJavascript("javascript:callMe('Hello World!')", new ValueCallback<String>() { // 注意这一行,调用的地方
@Override
public void onReceiveValue(String value) {
// TODO
}
});
}
});
}
});
}
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>你好!</h1>
<script>
window.callMe = function(s) { // 方法需要挂在 window 下面
var title = document.querySelector('h1');
title.innerHTML = s;
}
</script>
</body>
</html>
JS 调用 Android
对于JS调用Android代码的方法有3种:
- 通过 WebView 的 addJavascriptInterface() 进行对象映射
- 通过 WebViewClient 的 shouldOverrideUrlLoading() 方法回调拦截 url
- 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt() 方法回调拦截 JS 对话框 alert()、confirm()、prompt() 消息
代码示例:
package com.wit;
import android.app.Activity;
import android.webkit.JavascriptInterface;
public class JsJavaBridge {
private Activity activity;
JsJavaBridge(Activity activity) {
this.activity = activity;
}
@JavascriptInterface
public void onFinishActivity() {
activity.finish();
}
}
package com.wit;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class WebActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
// webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setJavaScriptEnabled(true); // 注意
webView.addJavascriptInterface(new JsJavaBridge(this), "$wv"); // 注意
// webView.setWebViewClient(new WebViewClient() {
// ...
}
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>你好!</h1>
<button>关闭页面</button>
<script>
window.callMe = function(s) {
var title = document.querySelector('h1');
title.innerHTML = s;
}
var btn = document.querySelector('button');
btn.onclick = function() {
window.$wv.onFinishActivity();
}
</script>
</body>
</html>
IOS 与 Web App
IOS中主要有两种WebView:
- UIWebView
- WKWebView:iOS 8.0之后才推出的新控件
相比与UIWebView,WKWebView存在很多优势:
- 支持更多的HTML5的特性
- 高达60fps滚动刷新频率与内置手势
- 与Safari相容的JavaScript引擎
- 在性能、稳定性方面有很大提升占用内存更少 协议方法及功能都更细致
- 可获取加载进度等
这里主要介绍WKWebView与JS的交互。
IOS 调用 JS
直接使用WebView实例的evaluateJavaScript方法即可。
self.webView.evaluateJavaScript("calledByNative()") { (str, error) in
if error != nil {
print("\(String(describing: error))")
} else {
print(str ?? "")
}
}
这里的calledByNative是JS中挂在window对象的方法。
JS 调用 IOS
首页IOS启动WebView时,可以注入一些方法,通过window.webkit.messageHandlers暴露给JS去调用。
webConfiguratiojn.userContentController.add(self, name: "nativeFun")
这里的nativeFun就是IOS供原生调用的方法。
extension ViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("传来的数据为", message.name, message.body)
// 处理完数据后,可以进行回调
self.webView.evaluateJavaScript("calledByNative()") { (str, error) in
if error != nil {
print("\(String(describing: error))")
} else {
print(str ?? "")
}
}
}
}
window.webkit.messageHandlers.nativeFun.postMessage({
name: 'King'
})
在JS中可以从window.webkit.messageHandlers对象获取方法。
附完整代码
IOS
import UIKit
import WebKit
class ViewController: UIViewController {
@IBOutlet var webView: WKWebView!
override func loadView() {
let webConfiguratiojn = WKWebViewConfiguration()
webConfiguratiojn.userContentController.add(self, name: "nativeFun")
webView = WKWebView(frame: .zero, configuration: webConfiguratiojn)
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let fileURL = Bundle.main.url(forResource: "www/index", withExtension: "html" )
webView.loadFileURL(fileURL!, allowingReadAccessTo:Bundle.main.bundleURL);
}
}
extension ViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("传来的数据为", message.body)
// 处理完数据后,可以进行回调
self.webView.evaluateJavaScript("calledByNative()") { (str, error) in
if error != nil {
print("\(String(describing: error))")
} else {
print(str ?? "")
}
}
}
}
JS
<!DOCTYPE html>
<html>
<head>
<title>测试</title>
<meta charset=UTF-8>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h1 id="title">hello</h1>
<button id="btn">点我</button>
<script>
var title = document.getElementById('title')
var btn = document.getElementById('btn')
btn.onclick = function() {
title.innerHTML = '改变'
window.webkit.messageHandlers.nativeFun.postMessage({
name: 'King'
})
alert('haha')
}
window.calledByNative = function() {
title.innerHTML = "我被原生调用了"
}
</script>
</body>
</html>
参考
所有评论(0)