前端跨平台开发的演变进程

347 阅读5分钟

狭义的跨平台开发指的是跨Android 和 iOS,开发一套代码,可以同时在这两个系统上运行。

跨平台开发模式,随着技术的沉淀和新业务场景的出现,随着时间发展而不断演变,一般业界通用的说法是有三个时代的变迁过程。

第一个时代: Web容器时代

通过原生应用内嵌浏览器控件WebView的方式进行HTML5页面渲染,并定义HTML5和原生代码交互协议。

这个时代的典型的框架包括 Cordova(PhoneGap)、Ionic 和微信小程序。

在Android端,浏览器控件为WebView,在IOS端,浏览器控件为UIWebViewWKWebView

原生与HTML5交互的协议叫作JSBridge。这种既有原生应用代码,又有Web应用代码的开发模式就称为Hybrid模式。

1.web容器时代.svg

第二个时代:泛Web容器时代

web容器方案有生态繁荣、开发体验友好、生产效率高、扩平台的优势,但是它承载着大量web标准的web容器是比较笨重的,所以性能会比原生差。

泛容器时代的解决方案优化了web容器时代加载、解析、渲染流程,只保留了构建移动页面必要的web标准(如flexbox), 同时用原生自带的UI组件替代了浏览器控件,仅保持必要的空间渲染能力,从而使得渲染过程更加简化,也保证了良好的渲染性能。

这个时代的典型框架如React Native、Weex 和快应用,广义的还包括天猫的 Virtual View 等。

2.泛容器时代.svg

第三个时代:自绘引擎时代

泛web容器时代使用原生的UI控件进行界面渲染,解决了不少性能问题,但是也带来新的问题。框架本身要处理大量的平台相关的逻辑,随着系统版本变化和API的变化,还需要处理不同平台的原生控件渲染能力差异,修复各类奇奇怪怪的bug。

Flutter就开辟了一种全新的思路,即从头到尾重写一套跨平台的UI框架,包括渲染逻辑,甚至是开发语言。

3.自渲染时代.svg

渲染引擎依靠跨平台的Skia图形库来实现,Skia引擎会将使用Dart构建的抽象视图结构设计加工成GPU数据,交由OpenGL最终提供给GPU渲染。

开发语言选用的是同时支持JIT(Just-in-Time)和AOT(Ahead-of-Time)的Dart,不仅保证了开发效率,更提升了执行效率。

以上内容参考自极客时间课程Flutter核心技术与实战,作者:陈雨航

核心JSbridge的设计(API注入方式)

在前两个时代中,JSBridge都占据重要的位置,下面以容器时代为例,了解JSbridge的设计。

JSBridge是承载原生应用(Native)与Web应用(JS)之间的桥梁。

JSBridge技术的实现大体可以分为六个步骤。

1.定义全局桥对象

var Android = window.Android || (window.Android = {})

Android官方提供了接口,让JS可以调用Native

在web容器控件初始化的时候注入webView

// 原生安卓代码
// 添加 JavaScript 接口
 webView.addJavascriptInterface(new WebAppInterface(this), "Android");

2.JS调用原生

业务场景如app初始化时,会调用原生的接口拿系统信息。

Android这边定义好命名空间后,JS就可以通过该命名空间将数据传递给原生。

<!-- assets/index.html -->
<!DOCTYPE html>
<html>
    <body>
        <h1>WebView JavaScript Demo</h1>
        <script type="text/javascript">
              function asyncNativeFunction() {
                return new Promise((resolve, reject) => {
                    // 调用 Android 对象的 nativeFunction 方法并传递参数
                    Android.nativeFunction('Hello from JavaScript!', (response) => {
                        if (response.success) {
                            resolve(response.data);
                        } else {
                            reject(new Error(response.error));
                        }
                    });
                });
            }

            async function callAsyncNativeFunction() {
                try {
                    const response = await asyncNativeFunction();
                    console.log("Received response from Native: " + response);
                } catch (error) {
                    console.error("Error:", error);
                }
            }

        </script>
        <button onclick="callAsyncNativeFunction()">Send Message to Android</button>
    </body>
</html>

3.Native接收JS传递过来的参数

关键的代码如下

// 供javascript调用的接口
 @JavascriptInterface
        public void nativeFunction(String message, final ValueCallback<String> callback) {
            // 处理从 JavaScript 传递过来的参数
            System.out.println("Received message from JavaScript: " + message);

            // 调用 JavaScript 的回调函数并传递参数
            webView.post(() -> { 
                // 在webView容器里执行JS代码,执行相应的回调函数
                webView.evaluateJavascript("javascript:callback('" + response + "')", null);
            });
        }

更完整代码如:

package com.example.webviewdemo;

import android.os.Bundle;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        webView = findViewById(R.id.webview);
        webView.setWebViewClient(new WebViewClient());
        webView.getSettings().setJavaScriptEnabled(true);

        // 添加 JavaScript 接口
        webView.addJavascriptInterface(new WebAppInterface(this), "Android");

        // 加载本地 HTML 文件或网络页面
        webView.loadUrl("file:///android_asset/index.html");
    }

    public class WebAppInterface {
        Context mContext;

        // Instantiate the interface and set the context
        WebAppInterface(Context c) {
            mContext = c;
        }

        @JavascriptInterface
        public void nativeFunction(String message, final ValueCallback<String> callback) {
            // 处理从 JavaScript 传递过来的参数
            System.out.println("Received message from JavaScript: " + message);
            
             // 调用 JavaScript 的回调函数并传递参数
            webView.post(() -> { 
                // 在webView容器里执行JS代码,执行相应的回调函数
                webView.evaluateJavascript("javascript:callback('" + response + "')", null);
            });
        }
    }
}

4.Native 返回数据给JS

 // 调用 JavaScript 的回调函数并传递参数
webView.post(() -> { 
    // 在webView容器里执行JS代码,执行相应的回调函数
    webView.evaluateJavascript("javascript:callback('" + response + "')", null);
});

5.通过回调函数,JS接收Natvie返回数据

 function asyncNativeFunction() {
    return new Promise((resolve, reject) => {
        Android.nativeFunction('Hello from JavaScript!', (response) => {
        // 这里接收回调传过来的数据
            if (response.success) {
                resolve(response.data);
            } else {
                reject(new Error(response.error));
            }
        });
    });
}

总结

总结这整个流程,大致有以下步骤

  1. app初始化的时候,android通过webView.addJavascriptInterface(new WebAppInterface(this), "Android")将Android相关的功能函数注入到window全局对象上。
  2. JS通过Android.nativeFunction('Hello from JavaScript!',callbackFn)调用安卓的nativeFunction方法,并传入数据和回调函数
  3. 安卓的nativeFunction处理好数据后,通过在webview里面执行JS代码,触发传入的回调函数的执行
  4. JS通过回调函数的执行,接收到从native回传的数据,可以依据数据做相应的处理。