H5 配合原生开发 App

JS 和 Android

  • 原生调用 JS
    4.4 版本之前
java 复制代码
// mWebView = new WebView(this); //当前webview对象
// 通过loadUrl方法进行调用 参数通过字符串的方式传递
mWebView.loadUrl("javascript: 方法名('参数1,参数2...')");

//也可以在UI线程中运行
runOnUiThread(new Runnable() {
    @Override
    public void run() {
        // 通过loadUrl方法进行调用 参数通过字符串的方式传递
        mWebView.loadUrl("javascript: 方法名('参数1,参数2...')");
        // 安卓中原生的弹框
        Toast.makeText(Activity名.this, "调用方法...", Toast.LENGTH_SHORT).show();
    }
});

4.4 版本之后

java 复制代码
// 通过异步的方式执行js代码,并获取返回值
mWebView.evaluateJavascript("javascript: 方法名('参数1,参数2...')", new ValueCallback<String>() {
    @Override
    // 这个方法会在执行完毕之后触发, 其中value就是js代码执行的返回值(如果有的话)
    public void onReceiveValue(String value) {

    }
});
  • JS 调用Android
    安卓配置:
java 复制代码
// Android4.2版本以上,本地方法要加上注解@JavascriptInterface,否则无法使用
private Object getJSBridge(){
    // 实例化新对象
    Object insertObj = new Object(){
        @JavascriptInterface
        // 对象内部的方法1
        public String foo(){
            // 返回 字符串 foo
            return "foo";
        }
        @JavascriptInterface
        // 对象内部的方法2 需要接收一个参数
        public String foo2(final String param){
            // 返回字符串foo2拼接上传入的param
            return "foo2:" + param;
        }
    };
    // 返回实例化的对象
    return insertObj;
}

// 获取webView的设置对象,方便后续修改
WebSettings webSettings = mWebView.getSettings();
// 设置Android允许JS脚本,必须要!!!
webSettings.setJavaScriptEnabled(true);
// 暴露一个叫做JSBridge的对象到webView的全局环境
mWebView.addJavascriptInterface(getJSBridge(), "JSBridge");

在 web 页面中调用

js 复制代码
//调用方法一
window.JSBridge.foo(); //返回:'foo'
//调用方法二
window.JSBridge.foo2('test');//返回:'foo2:test'

JS 和 IOS

  • 原生调用 JS
swift 复制代码
class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {
    // 加载完毕会触发(类似于Vue的生命周期钩子)
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // 类似于console.log()
        print("触发啦");
        // wkWebView调用js代码,其中doSomething()会被当做js解析
        webView.evaluateJavaScript("doSomething()");
    }
}
  • JS 调用 IOS
  1. JS 部分
js 复制代码
window.webkit.messageHandlers.方法名.postMessage(数据)
  1. iOS 部分注册监听
swift 复制代码
wkWebView.configuration.userContentController.add(self, name: 方法名)
  1. iOS 部分遵守协议相关方法
swift 复制代码
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    // message.body 就是传递过来的数据
    print("传来的数据为", message.body)
}

url scheme(互通协议)

web 调用

js 复制代码
<input class="ios" type="button" value="使用iframe加载url">

// 加载url 通过iframe 设置URL 目的是让ios拦截
function loadUrl(url) {
  // 创建iframe
  const iframe = document.createElement('iframe');
  // 设置url
  iframe.src = url;
  // 设置尺寸(不希望他被看到)
  iframe.style.height = 0;
  iframe.style.width = 0;
  // 添加到页面上
  document.body.appendChild(iframe);
  // 加载了url之后他就没用了
  // 移除iframe
  iframe.parentNode.removeChild(iframe);
}

document.querySelector('.ios').onclick = function () {
  loadUrl('taobao://click');
}

IOS 监听

js 复制代码
// 拦截url
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    // 获取url
    let url = navigationAction.request.url?.absoluteString;
    if(url=="taobao://click"){
        print("调用系统功能");
        decisionHandler(.cancel);
    }else{
        decisionHandler(.allow);
    }
}

HyBridApp

  • 开发框架
  1. 提供前端运行环境
  2. 实现前端和原生交互
  3. 封装原生功能,提供插件机制

加载优化

  • 骨架屏
html 复制代码
<style>
  .shell .placeholder-block{
    display: block;
    height: 5em;
    background: #ccc;
    margin: 1em;
  }
  .novel {
    height: 5em;
    background-color: yellowgreen;
  }
</style>
</head>
<body>
<div class="shell">
  <div class="placeholder-block"></div>
</div>
</body>
</html>
<script>
setTimeout(()=>{
  // 移除 占位dom元素
  document.querySelector('.shell').innerHTML = ''
  // 创建数据的dom元素 添加到页面上
  let p = document.createElement('p')
  p.innerHTML = '黑马程序员'
  p.className = 'novel'
  document.querySelector('.shell').appendChild(p)
},3000)
</script>

webview

java 复制代码
import UIKit
import WebKit

class ViewController: UIViewController, WKNavigationDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        // 创建webView
        var webView = WKWebView(frame: self.view.bounds)
        // 设置自己为WebView的代理
        webView.navigationDelegate = self
        // 添加到页面上
        self.view.addSubview(webView)

        // 创建URL对象
        var url = URL(string: "https://www.baidu.com")
        // 创建URLRequest对象
        var request = URLRequest(url: url!)
        // 加载URL
        webView.load(request)
    }
}

JSBridge

  • 设计思想
  1. JS 向原生发送消息
  2. 原生向 JS 发送消息
javascript 复制代码
window.JSBridge = {
    invoke: function(action, params, callback) {
        // 生成唯一回调ID
        const callbackId = 'cb_' + Date.now();
        // 存储回调函数
        window[callbackId] = callback;
        
        // 构建标准化消息
        const msg = {
            action: action,
            params: params || {},
            callbackId: callbackId
        };
        
        // 根据平台调用不同原生桥
        if (isIOS()) {
            window.webkit.messageHandlers.nativeBridge.postMessage(JSON.stringify(msg));
        } else if (isAndroid()) {
            window.android.postMessage(JSON.stringify(msg));
        }
    },
    // 原生调用此方法来回调结果
    receiveMessage: function(msg) {
        const { callbackId, result, error } = msg;
        const callback = window[callbackId];
        if (callback) {
            if (error) {
                callback(null, error); // 错误回调
            } else {
                callback(result, null); // 成功回调
            }
            // 执行后删除回调,避免内存泄漏
            delete window[callbackId];
        }
    }
};

// 使用示例:调用原生相机
JSBridge.invoke('takePhoto', { quality: 'high' }, (result, error) => {
    if (error) {
        console.error('拍照失败:', error);
    } else {
        console.log('照片路径:', result.imagePath);
    }
});

解释:

  1. 前端调用 JSBridge.invoke 时:存储回调函数,生成唯一的 callbackId(如 cb_1725000000000),确保每个回调能被唯一识别;把回调函数挂载到 window 对象上 (即 window[callbackId] = 回调函数),相当于 "暂时存档",避免函数被垃圾回收。

  2. 前端向原生发送 "带回调 ID 的消息",然后根据平台(iOS/Android)把消息发给原生,此时原生收到的是 "操作指令 + 回调 ID"

  3. 原生执行操作(如调用相机),原生接收到消息后,解析出 actionparams,执行对应的原生逻辑

  • iOS:调用 UIImagePickerController(系统相机接口),按 quality: 'high' 配置拍照质量;
  • Android:调用 CameraCameraX 接口,同样按参数执行拍照。 这个阶段完全在原生环境(Objective-C/Swift 或 Java/Kotlin)中运行,与前端 JS 无关。
  1. 原生将 "结果 + 回调 ID" 回传给前端
    原生执行完操作后(无论成功 / 失败),会构建一个 "结果消息",包含:callbackId: 'cb_1725000000000'(必须和前端传过来的一致,才能找到对应的回调); result: { imagePath: '/var/mobile/.../photo.jpg' }(成功时的结果,如照片路径); 或 error: '用户取消拍照'(失败时的错误信息)。

然后原生会主动调用前端 JSBridge 预留的 receiveMessage 方法,把 "结果消息" 传回去。

  1. 前端 receiveMessage 执行回调函数
  • 解析原生传过来的消息,提取 callbackIdresulterror

  • 通过 callbackId 找到之前挂载在 window 上的回调函数(即 window['cb_1725000000000']);

  • 执行回调函数:

    • 成功:调用 callback(result, null)(如打印照片路径);
    • 失败:调用 callback(null, error)(如打印 "用户取消拍照");
  • 执行完后删除 window[callbackId],避免内存泄漏。

到这一步,回调函数才真正在前端 JS 环境中执行,完成整个跨端通信闭环。

相关推荐
再学一点就睡13 小时前
初探 React Router:为手写路由筑牢基础
前端·react.js
悟空聊架构13 小时前
5 分钟上手!Burp 插件「瞎越」一键批量挖垂直越权
前端
炒毛豆13 小时前
vue3+antd实现华为云OBS文件拖拽上传详解
开发语言·前端·javascript
Pu_Nine_913 小时前
Axios 实例配置指南
前端·笔记·typescript·axios
红尘客栈214 小时前
Shell 编程入门指南:从基础到实战2
前端·chrome
前端大卫15 小时前
Vue 和 React 受控组件的区别!
前端
Hy行者勇哥15 小时前
前端代码结构详解
前端
练习时长一年15 小时前
Spring代理的特点
java·前端·spring
水星记_16 小时前
时间轴组件开发:实现灵活的时间范围选择
前端·vue
2501_9301247016 小时前
Linux之Shell编程(三)流程控制
linux·前端·chrome