WHAT - Hybrid App 详解系列(一)

一、简单介绍

Hybrid App 是一种移动应用程序类型,它结合了原生应用和Web应用的元素。这些应用是使用HTML、CSS和JavaScript等Web技术构建的,但它们被包装在一个原生容器中,允许它们像原生应用一样被安装和在移动设备上运行。

开发Hybrid App有几个优势:

  1. 跨平台兼容性:可以一次开发,然后部署在诸如iOS、Android等多个平台上,从而减少了开发时间和成本。

  2. 访问设备功能:可以访问设备功能,如摄像头、GPS、联系人等,使开发人员能够创建丰富和交互性强的体验。

  3. 开发速度快:具有Web开发技能的开发人员可以快速开发和部署应用,而不需要学习特定于平台的语言或框架。

  4. 易于更新:与需要用户从应用商店下载更新的原生应用不同,Hybrid App可以通过更新Web内容来立即更新,使开发人员能够轻松推送修复bug和新功能。

然而,与原生应用相比,Hybrid App 可能也有一些局限性,例如性能可能较低、对某些设备功能的访问受限以及难以实现完全原生的外观和感觉。

尽管如此,对于许多开发人员来说,Hybrid App仍然是一种受欢迎的选择,特别是那些希望在多个平台上覆盖广泛受众而又不愿意牺牲开发速度的开发人员。

二、背景

2.1 终端系统

  1. iOS

苹果公司最早于 2007 年 1 月 9 日的 Macworld 大会上公布了 iOS 这个操作系统,最初是设计给 iPhone 使用的。随后,iOS 陆续套用到了 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统

  1. AndroidOS

Android 是一种基于 Linux 的自由及开放源代码的操作系统,主要用于移动设备,如智能手机和平板电脑,由 Google 公司和开放手机联盟领导及开发。Android 操作系统最初由 Andy Rubin 开发,主要支持手机。2005 年 8 月由 Google 收购注资。2007 年 11 月,Google 与 84 家硬件制造商、软件开发商及电信营运商组建开放手机联盟共同研发改良 Android 系统。随后,Google 以 Apache 开源许可证的授权方式,发布了 Android 的源代码。第一部 Android 智能手机发布于 2008 年 10 月。

  1. 其他操作系统

除了 iOS(由苹果公司开发)和 Android(由 Google 开发)这两个主要的移动操作系统外,还存在一些较小众的移动操作系统。它们在某些特定市场或设备上可能具有一定的存在和影响。

一些较小众的移动操作系统包括但不限于:

  1. KaiOS:KaiOS 是一个基于 Linux 的轻量级移动操作系统,专注于为功能手机和低端智能手机提供服务。它主要在发展中国家和一些新兴市场上获得了一定的市场份额。

  2. Tizen:Tizen 是由 Linux Foundation 和 Samsung 领导的一个开源项目,旨在为多种设备提供操作系统,包括智能手机、平板电脑、智能电视和可穿戴设备。由三星等公司支持,Tizen 在智能电视、智能手表和其他物联网设备上更为广泛使用。

  3. Ubuntu Touch:Ubuntu Touch 是 Canonical 公司开发的移动操作系统,基于 Ubuntu Linux 发行版。尽管其在市场上的影响较小,但在开源社区中受到一定程度的关注。

  4. Symbian(塞班):Symbian 是一种由诺基亚开发的移动操作系统,曾经是全球智能手机市场的主导力量之一。然而,随着iOS和Android的崛起,Symbian逐渐失去了市场份额,并于2010年宣布停止进一步发展。

  5. MeeGo:MeeGo 是由诺基亚和英特尔合作开发的一个开源的Linux操作系统,旨在应用于移动设备和嵌入式系统。尽管MeeGo在技术上取得了一定成就,但它在市场上的影响较小,后来逐渐被合并到了Tizen项目中。

  6. BlackBerry OS(黑莓操作系统):BlackBerry OS 是由加拿大公司BlackBerry开发的移动操作系统,主要应用于BlackBerry智能手机上。虽然在过去曾经是商务人士和企业用户的首选,但随着iOS和Android的兴起,BlackBerry OS的市场份额逐渐萎缩。后来,BlackBerry转向了使用Android作为其智能手机的操作系统。

还有近两年的 HarmonyOS(鸿蒙OS): 由华为公司推出的操作系统,旨在支持多种设备类型,包括智能手机、平板电脑、智能穿戴、智能电视等。

2.2 技术选择

开发不同的操作系统平台都需要建立自己的生态系统。生态系统是一个非常复杂的情况,一旦建立,就会形成竞争壁垒,也就是护城河,防止其他竞争者轻松进入市场。

在软件层面上,不同的操作系统采用不同的开发语言。例如在 PC 时代,Linux 和 Windows 主要使用 C++ 进行开发,而后来的 .NET 提供了更高层次的抽象。在硬件层面上,不同的处理器架构如 ARMIntel x86 也构成了不同的生态系统,比如熟知的 Mac M 芯片就是从 x86 转向了 ARM。

对于移动端来说,iOS 采用了 C++ 生态进行开发,使用 Xcode 作为 IDE。后来出现了更有优势的 Swift 语言。Android 则采用了 Java 生态,使用 Android Studio 作为 IDE,学习成本较低,方便 Java 程序员转型。此外,Kotlin 语言在 Android 生态中也具有优势。Swift 和 Kotlin 都是为了提高开发效率、运行效率和安全性而诞生的。它们都是针对各自平台的开发生态而推出的新语言。

2.3 兼容成本和跨平台技术

早期,几乎所有公司都需要针对不同的操作系统组建不同的开发团队,因为他们希望自己的应用程序能够在尽可能多的平台上运行,以实现最大化的用户覆盖。

然而,这种做法导致了以下问题:

  • 开发和维护成本极高:针对不同操作系统开发应用程序需要额外的人力资源、时间和金钱投入。每个平台都有自己的开发环境和工具集,开发团队需要花费大量时间来学习和适应这些环境,同时还需要针对不同平台进行代码编写和调试。
  • 无法实现一致的用户体验:不同操作系统之间的用户界面设计和交互方式可能有所不同,这意味着开发人员需要根据每个平台的要求进行定制化开发,难以实现统一的用户体验,这可能会影响用户满意度和应用品牌形象。
  • 审核成本高且审核周期长:在将应用程序提交到各个应用商店进行审核时,每个平台都有其自己的审核标准和流程,这导致审核周期较长。

为了解决这些问题,许多公司转向了跨平台开发技术,例如使用混合应用开发框架(如React NativeweexFlutter)或跨平台开发工具(如XamarinPhoneGap),这些技术可以帮助开发人员在多个平台上共享代码,并尽可能减少开发和维护成本。

同时,这些跨平台技术也有助于提高应用程序的一致性和性能,并减少审核周期和用户投诉的可能性,从而提升了开发效率和用户体验。

2.4 Qt 框架:跨 PC 系统开发

跨平台开发并不局限于移动端,它在 PC 时代就已经存在了。Qt 框架是一个很好的例子,它是一个跨平台的应用程序和用户界面框架,最初由 Trolltech 公司开发,并于1995年首次发布。Qt 使用 C++ 编程语言,提供了丰富的功能和工具,使开发人员能够编写一次代码,然后将其部署到多个不同的操作系统上,如 Windows、macOS、Linux 等。

Qt 框架的跨平台特性使得开发人员可以更高效地开发和维护应用程序,同时保持应用程序在不同平台上的一致性和性能。类似的跨平台框架还有 Java 的 SwingJavaFX,它们也允许开发人员编写一次代码,然后在不同的操作系统上运行。

三、Hybrid App 跨终端方案

3.1 Reactive Native、Weex、Flutter

上面提到的跨平台开发技术本质上并非使用 web 技术,而是通过编写 Web 语法再转换为对应平台的 native 组件或者拥有自己的渲染引擎渲染自己语言编写的组件在终端上

React Native 和 Weex 分别使用 ReactVue 构建应用程序,开发人员可以使用 JavaScript 编写应用程序逻辑和构建用户界面,并将其按需转换为不同终端原生组件,以在移动设备上运行。

而 Flutter 则是使用 Dart 语言进行开发,Dart 是由 Google 开发的一种客户端优化的编程语言,它专门设计用于构建跨平台移动应用程序、Web 应用程序和服务器端应用程序。Flutter 开发人员使用 Dart 编写应用程序的逻辑和用户界面。

当谈到 Flutter 可作为跨平台技术时,主要有以下几个方面的原因:

  1. 统一的代码库:Flutter 允许开发人员使用相同的 Dart 代码库构建 iOS 和 Android 应用程序。这意味着开发人员可以在不同的平台上共享大部分代码,从而节省了开发和维护的成本。

  2. 自定义的用户界面:Flutter 不依赖于平台的 UI 组件,而是使用自己的渲染引擎将 Dart 代码直接呈现为用户界面。这使得开发人员可以创建高度定制和漂亮的用户界面,而无需担心平台差异。

  3. 快速的性能:由于 Flutter 的渲染引擎直接将 Dart 代码转换为原生应用程序中的视觉组件,因此 Flutter 应用程序通常具有优秀的性能表现。Flutter 的热重载功能还使开发人员能够快速地进行迭代和调试,从而提高了开发效率。

  4. 丰富的组件库:Flutter 提供了丰富的 UI 组件库,称为 Flutter Widget,包括按钮、文本框、图像等常用组件。开发人员可以使用这些组件快速构建应用程序的用户界面,而无需从头开始编写自定义的 UI 组件。

  5. 跨平台支持:Flutter 不仅支持 iOS 和 Android,还支持其他平台,如 Web、桌面应用和嵌入式设备。这意味着开发人员可以使用相同的代码库构建多种类型的应用程序,从而扩展了应用程序的覆盖范围。

3.2 web 跨终端发展

而关于直接使用 web 技术开发独立 app 的概念,早在2011年就开始流行,最早实践者之一是 Facebook。然而,这一尝试并非成功,而是遇到了一些挑战和困难。一篇较老的文章讲述了早年 Facebook 放弃使用 HTML5 开发原生 app 的原因,主要包括:

  1. 渲染性能不足:当时移动设备的硬件配置较低,HTML5 应用的渲染性能无法满足用户需求,导致应用体验较差。

  2. 数据请求效率低下:在 HTTP1.1 时代,数据请求效率不高,加载速度慢,影响了应用的性能和用户体验。

  3. 审核问题:一些平台对纯 web 开发的应用无法通过审核,限制了应用的发布和推广。

然而,随着时代的发展和技术的迭代,这些问题逐渐得到了解决。

如今,硬件性能不断提升,网络速度不断加快,以及跨平台开发技术的发展,使得使用 web 技术开发独立应用成为可能。而且现在是硬件和流量爆炸的时代,web 方案具有更大的传播潜力。因此,越来越多的应用程序采用 web 解决方案进行开发,并且出现了许多支持这种开发方式的工具和框架。

3.3 WebView

WebView 是一个用于显示 Web 内容的组件,允许你在你的应用程序中嵌入Web页面。

Hybrid app 本质上是提供了 Webview 组件渲染 web 内容的浏览器,iOS 提供了 wkwebview,Android 比较复杂,除了默认的 Webview,不同厂商可能提供了个各自自定义的 Webview,如腾讯的 x5 内核

1. 创建一个简单的 Webview

以 Android 为例

  1. 在 Android Studio 中,选择 “File” -> “New” -> “New Project”
  2. 选择 “Empty Activity” 模板,点击 “Finish”
  3. 打开 activity_main.xml 文件,通常位于 res/layout 目录下
  4. 使用以下代码替换布局文件的内容
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>
  1. 打开 MainActivity.java 文件,通常位于 src/main/java/your_package_name 目录下
  2. 使用以下代码替换 onCreate 方法
package your_package_name;

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

public class MainActivity extends AppCompatActivity {

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

        // 获取 WebView 对象
        WebView webView = findViewById(R.id.webView);

        // 加载一个简单的网页
        webView.loadUrl("https://www.example.com");
    }
}
  1. 接下来需要添加 Internet 权限
  2. 打开 AndroidManifest.xml 文件,通常位于 src/main 目录下
  3. 在 元素内的 元素之前,添加以下权限
  4. 最后运行应用,即可在应用看到一个包含网页内容的 Webview

2. Webview 跳转

跳转是 hybrid app 里最常见的交互,因此需要着重讲一下。

  1. webView.loadUrl
webView.loadUrl("https://www.example.com");

适用于直接打开一个 url。

  1. webView.loadData
String htmlData = "<html><body><h1>Hello, WebView</h1></body></html>";
webView.loadData(htmlData, "text/html", "UTF-8");

适用于加载本地或动态生成的 html 内容。

  1. shouldOverrideUrlLoading()
webView.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        // 处理URL跳转逻辑
        view.loadUrl(url);
        return true;
    }
});

使用自定义的 WebViewClient,允许应用拦截 url 加载请求,进行自定义处理,比如在应用内容部打开链接。

  1. Intent 打开系统浏览器
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.example.com"));
startActivity(intent);

将 url 交给系统浏览器处理。适用于在外部浏览器打开,比如常见的支付链接跳转。

  1. WebView 内部网页的纯前端跳转

如 window.location、spa router。

  1. WebView jsbridge 跳转

实际上大部分 Hybrid App 页面跳转都是借助这种方式:

if (util.isInWebview()) {
    // 在 app 内跳转
    let name = 'confirmOrder';
    let url = `${util.envPath()}/${name}?is_nav_show=false`;
    appJsBridge.openURL({
        data: {
            url: url
        }
    })
} else {
    // 不需要 app 内跳转
    // router change
}

注意,这种方式其实就是借助 native 打开一个新的 WebView,页面之间是完全独立的进程。

好处:

  • 借助客户端跳转可以使用系统自带的跳转过渡特效,包括手势使用,保证 native 和 web 体验一致性
  • 借助客户端维护 history 栈

四、JS Bridge 技术

介绍

JS Bridge 技术是一种用于在 JavaScript 和原生代码之间进行通信的技术。在移动应用程序开发中,通常会使用 JS Bridge 技术来实现 WebView(或称为嵌入式浏览器)与原生应用程序之间的通信。

具体而言,JS Bridge 技术允许 JavaScript 代码在 WebView 中与原生应用程序进行交互,例如调用原生代码的方法,传递数据给原生代码,以及接收来自原生代码的回调和数据。这种通信通常通过以下步骤实现:

  1. JavaScript 调用原生方法:JavaScript 代码在 WebView 中调用 JS Bridge 提供的方法,这些方法会触发原生应用程序中的相应功能。例如,JavaScript 可能需要调用原生代码来执行一些特定的硬件操作,或者打开另一个页面。

  2. 原生代码处理请求:原生应用程序中的桥接层接收到来自 WebView 的请求后,会根据请求的内容执行相应的操作。这可能涉及到调用原生的 API、处理数据等。

  3. 原生方法执行完成后的回调:在一些情况下,原生方法的执行可能需要一些时间。一旦原生方法执行完成,可以通过回调函数将结果返回给 JavaScript 代码,并在 WebView 中处理这些结果。

  4. JavaScript 处理结果:JavaScript 代码接收到来自原生代码的回调或数据后,可以相应地处理这些数据,更新用户界面或执行其他操作。

JS Bridge 技术的典型应用场景包括在混合应用程序中,其中使用 WebView 来显示部分内容,同时通过 JS Bridge 与原生应用程序进行通信,以实现更多的功能和交互性。这种技术使得开发人员可以利用 Web 技术来构建应用的界面和一些逻辑,同时又能够利用原生代码的优势来实现更高级的功能和性能优化。

JS Bridge 双向通信方式和原理

可参考 dsbridge:https://github.com/search?q=dsbridge&type=repositories

1. webView.addJavascriptInterface + @JavascriptInterface 注解

这涉及到一个历史小插曲,在 Android 4.2 之前,谷歌的 WebView 存在安全漏洞。通过简单的 webView.addJavascriptInterface(new MyJSBridge(), "AndroidBridge"); 就可以使得网站轻松获取客户端的重要信息,甚至可以调用本地代码进行恶意行为。这是因为 WebView 中的所有方法都默认为 public,使得 JavaScript 可以直接调用。谷歌后来意识到了这个漏洞,并采取了防御措施。现在要求使用 @JavascriptInterface 注解的方法才会被暴露给 WebView 中的 JavaScript,以提高安全性。

基本流程如下:

  1. Android Native

在 Android 中,创建一个 WebView,并为 WebView 设置一个 WebViewClient 和 WebChromeClient。

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);

        // 启用JavaScript
        WebSettings webSettings = webView.getSettings();
        webSettings.setJavaScriptEnabled(true);

        // 设置 WebViewClient 用于处理页面加载和跳转
        webView.setWebViewClient(new WebViewClient());

        // 设置 WebChromeClient 用于处理 JavaScript 弹窗等
        webView.setWebChromeClient(new WebChromeClient());

        // 加载 WebView 显示的页面
        webView.loadUrl("file:///android_asset/index.html");

        // 注册 JavaScript Interface
        webView.addJavascriptInterface(new MyJSBridge(), "AndroidBridge");
    }

    // 自定义的 JavaScript Bridge 接口
    public class MyJSBridge {

        // 在 JavaScript 中调用此方法
        @JavascriptInterface
        public void showToast(String message) {
            Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
        }

        // 在 JavaScript 中调用此方法
        @JavascriptInterface
        public String getNativeMessage() {
            return "Hello from Android!";
        }
    }
}

我们创建了一个 WebView,启用了 JavaScript,并设置了一个 WebViewClient 用于处理页面加载和跳转。我们还注册了一个 WebChromeClient 用于处理 JavaScript 弹窗。

接着通过 addJavascriptInterface() 方法注册了一个名为 AndroidBridge 的 JavaScript Bridge 接口。在 HTML 页面中,我们通过按钮点击触发 JavaScript 函数,这些函数将调用 Android 原生代码中的相应方法。

MyJSBridge 类是一个用于桥接 JavaScript 与 Android 原生代码的接口。通过 @JavascriptInterface 注解,Android 确保只有这些方法可以被 JavaScript 调用。

  1. Web html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JSBridge Example</title>
</head>
<body>
    <h1>JavaScript Bridge Example</h1>

    <button onclick="showNativeToast()">Show Native Toast</button>
    <button onclick="getNativeMessage()">Get Native Message</button>

    <script>
        // JavaScript 代码,用于调用 Android 原生代码
        function showNativeToast() {
            AndroidBridge.showToast("Hello from JavaScript!");
        }

        function getNativeMessage() {
            var message = AndroidBridge.getNativeMessage();
            alert(message);
        }
    </script>
</body>
</html>
  1. App Internet 权限

确保在 AndroidManifest.xml 文件中添加了INTERNET权限。

<uses-permission android:name="android.permission.INTERNET" />
  1. App 布局文件

在 res/layout 文件夹下创建一个布局文件(例如 activity_main.xml),用于放置 WebView。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

注意,上述的调用方式是同步的,即 JavaScript 会阻塞等待 Java 方法的执行并获得返回值。在某些情况下,这可能会导致 WebView 卡顿,因此对于一些可能耗时的操作,你可能需要考虑使用异步方式。

想要实现 app 异步任务场景下不阻塞 WebView,可以参考如下:

// JavaScript code
var argument = 'someValue';
// Define a callback function
function handleResult(result) {
    // Handle the result from Java
    console.log('Result from Java:', result);
}
// Call the asynchronous Java method with the callback
MyBridge.setCallbackInterface({ onResult: handleResult });
MyBridge.myFunctionAsync(argument);
public class MyJSBridge {
    private CallbackInterface callbackInterface;

    public void setCallbackInterface(CallbackInterface callbackInterface) {
        this.callbackInterface = callbackInterface;
    }

    @JavascriptInterface
    public void myFunctionAsync(String argument) {
        // Perform asynchronous Java logic with the provided argument

        // Simulating an asynchronous operation with a handler
        new Handler().postDelayed(() -> {
            String result = performJavaAsyncLogic(argument);

            // Callback to JavaScript with the result
            if (callbackInterface != null) {
                callbackInterface.onResult(result);
            }
        }, 2000); // Simulating a 2-second delay
    }
}

2. URL 协议拦截

利用双方约定一个 URL 协议,app 通过拦截该类型 URL,解析方法名、入参、回调等信息。

假设我们有一个原生应用程序,它可以通过拦截自定义的 URL 协议来执行特定的操作。在这个示例中,我们将假设原生应用程序能够通过 URL 协议 myapp:// 来执行一些操作。

JavaScript 端:

// 定义一个 JavaScript 函数,用于发起与原生应用程序的通信
function callNativeFunction() {
    // 创建一个 iframe 元素,并设置其 src 属性为自定义的 URL 协议
    var iframe = document.createElement('iframe');
    iframe.style.display = 'none';
    iframe.src = 'myapp://performAction?param1=value1&param2=value2';
    document.body.appendChild(iframe);
    // 在一定时间后移除 iframe 元素,以便清理
    setTimeout(function() {
        document.body.removeChild(iframe);
    }, 3000); // 假设 3 秒后清理
}

// 定义一个回调函数,用于处理原生应用程序执行完操作后的回调
function nativeCallback(result) {
    // 在此处处理从原生应用程序返回的结果
    console.log('Received result from native:', result);
}

App 端(如 Android):基于 shouldOverrideUrlLoading

public class MyWebViewClient extends WebViewClient {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        Uri uri = request.getUrl();
        if (uri != null && uri.getScheme().equals("myapp")) {
            String method = uri.getHost(); // 获取方法名
            
            String param1 = uri.getQueryParameter("param1"); // 获取参数1
            String param2 = uri.getQueryParameter("param2"); // 获取参数2

			String callback = uri.getQueryParameter("callback"); // 获取回调函数名
            
            // 执行相应的操作
            if (method.equals("doSomething")) {
                // 执行方法 doSomething,并将参数传递进去
                doSomething(param1, param2);
            } else if (method.equals("doAnotherThing")) {
                // 执行方法 doAnotherThing,并将参数传递进去
                String result = doAnotherThing(param1, param2);
                // 构造 JavaScript 代码,调用回调函数,并传递结果作为参数
                String jsCallback = String.format("javascript:%s('%s')", callback, result);
                view.loadUrl(jsCallback); // 执行 JavaScript 回调函数
            }
            return true; // 表示已经处理该 URL,不再加载
        }
        return false; // 其他情况交由 WebView 处理
    }
    
    // 自定义方法示例
    private void doSomething(String param1, String param2) {
        // 执行相应操作
    }
    private void doAnotherThing(String param1, String param2) {
        // 执行相应操作,并返回结果
        return "result";
    }
}

在上述示例中,我们使用 view.loadUrl 来在 JavaScript 端执行 callback,并传递了结果值。另外还可以利用 evaluateJavascript:

String callback = uri.getQueryParameter("callback"); // 获取回调函数名
String result = doAnotherThing(param1, param2);
String jsScript = callback + "('" + result + "')";
webView.evaluateJavascript(jsScript, null);

3. Propmt + Js 注入(相对陈旧)

WebView 有 alertconfirmprompt 三种弹框方式,并且支持接收一个 web input。该方案利用这一特性,如实现 WebChromeClient.onJsPrompt() 来监听 WebView 里 prompt 是否有内容输入来实现通信。更多细节不赘述。

五、Universal Link 技术

1. 背景

在开发 H5 中,我们会遇到这样的需求:希望在 Safari 浏览器或其他应用中,通过点击 H5 页面中某个链接后能够自动唤起应用并且打开指定页面。

比如下述场景:

  1. 在其他应用或浏览器打开的 WebView H5 中打开 APP 的某个页面
  2. 从其他应用打开 APP 的某个页面
  3. 服务器下发数据后,如消息推送,点击后打开 APP 跳转至某个页面
  4. 短信中点击唤起 App 跳转至某个页面

对于前端开发来说,我们这里仅关注第一个场景。

目前,我们可以通过使用自定义 URL Scheme 或 Universal Links 的方式来实现在 Safari 浏览器或其他应用中点击链接后自动唤起应用并打开指定页面的需求。

2. deep linking 和 deferred deep linking 概念

在学习自定义 URL Scheme 或 Universal Links之前,我们还需要了解一下这两个东西。因为自定义 URL Scheme 或 Universal Links 是 deep linking 的两种实现。

传统中,普通的链接通常只能打开应用程序的主页,而不能直接导航到应用程序的特定页面。这是因为传统的链接通常只是一个URL地址,而应用程序在打开链接时无法直接识别链接中的特定信息或参数。下面是一个具体的示例:

假设有一个名为"ShoppingApp"的购物应用程序,它有以下几个页面:

  • 主页:显示推荐商品和促销信息。
  • 商品详情页:显示特定商品的详细信息。
  • 购物车页:显示用户已添加到购物车中的商品列表。

如果用户想要查看某个特定商品的详情,但是在之前的传统链接中,只能提供应用程序的主页链接,比如:

https://shoppingapp.com

当用户点击这个链接时,应用程序通常会打开到主页,用户需要手动在应用内导航到想要查看的商品详情页。这就是传统链接只能打开应用程序的主页,而无法直接导航到应用程序的特定页面的情况。

而通过深层链接或延迟式深层链接技术,可以实现用户点击链接后直接导航到特定页面的效果,比如直接打开商品详情页的链接:

https://shoppingapp.com/product?id=123456

在这种情况下,当用户点击链接时,应用程序可以识别链接中的参数(如商品ID),并直接导航用户到指定商品的详情页,而不是只打开主页。

深层链接(Deep Link)和延迟式深层链接(Deferred Deep Linking)是两种用于移动应用中导航用户到特定内容页面的技术,但它们之间存在一些区别。

  1. 深层链接(Deep Link)
  • 深层链接是一种直接将用户导航到移动应用内特定内容页面的链接。
  • 用户点击深层链接时,系统会尝试直接打开应用程序,并导航到链接指定的内容页面。如果应用程序已安装且支持深层链接,则用户会直接进入指定页面;如果应用程序尚未安装,则通常会在浏览器中打开应用的相关页面,或者提示用户下载应用程序。
  1. 延迟式深层链接(Deferred Deep Linking)
  • 延迟式深层链接是深层链接的一种变体,它允许在用户首次安装应用程序后,将用户引导到特定的内容页面,即使用户点击链接时尚未安装该应用程序。
  • 当用户点击链接时,系统会首先检查设备上是否已安装相关的应用程序。如果已安装,则会直接打开应用程序,并导航到链接指定的内容页面;如果尚未安装,则会引导用户下载应用程序,并在安装完成后再次打开应用程序,并导航到相应的页面

延迟式深层链接对于提高用户参与度和转化率尤为重要,因为它可以确保用户在安装应用程序后直接进入他们感兴趣的内容页面,而不是在安装后只能打开应用的主页,后者可能会导致用户因无法抵达预期页面而放弃下单。

另外补充一下,Deferred Deep Linking 是如何做到首次下载后还能打开指定页面的?它通过在应用程序内部保存状态信息来实现用户首次下载应用程序后还能打开指定页面的功能。

以下是实现的一般流程:

  1. 标识用户来源和意图

当用户首次点击深层链接时,h5 可能会将一些额外的信息发送给应用程序后台存下来,例如广告系列标识符来源渠道链接中的参数 等信息,以帮助后续应用程序了解用户的来源和意图。

广告系列标识符(IDFA)和Google广告ID(GAID)是移动设备上用于跟踪广告活动效果的唯一标识符。IDFA(Identifier for Advertising)是苹果公司为iOS设备提供的广告标识符。它是一个随机生成的唯一标识符,用于识别特定设备,并在应用内跟踪广告效果和用户行为。开发者可以使用IDFA来确定用户对广告的点击、安装和行为等情况,从而评估广告活动的效果,并优化广告投放策略。GAID(Google Advertising ID)是谷歌为Android设备提供的类似于IDFA的广告标识符。这些广告标识符允许开发者在广告活动中跟踪特定设备的行为,但同时也受到用户隐私的保护。根据苹果和谷歌的规定,用户可以选择限制或重置他们的广告标识符,以控制广告跟踪行为。

  1. 存储状态信息

应用程序通常会在用户首次安装应用程序后,发起请求(同样带上前面的 IDFA 或 GAID 等设备信息),在后台拿到前面通过 h5 上传的来源和意图等信息并存储在本地。

  1. 处理应用启动逻辑

当用户首次打开应用程序时,应用程序会检查本地存储的状态信息,并根据信息决定是否需要导航到深层链接中指定的内容页面。如果存在相关的状态信息,则应用程序可以根据信息导航到指定页面,从而实现首次下载后打开指定页面的功能。

除了上述方案(该方案无设备限制、无侵入性)之外,还可以利用剪贴板方案,简单来说就是在h5访问阶段生成唯一标识符保存在剪贴板,然后将唯一标识符和其他意图信息上传到后台存储,等APP启动再读取剪贴板唯一标识符去后台获取意图信息,但这个方案有一定的侵入性。

总的来说,深层链接是一种直接导航用户到移动应用特定页面的链接技术,而延迟式深层链接则在用户首次安装应用后也能实现相同的导航效果。

3. 自定义 URL Scheme

在 Universal Link 技术推出之前,通常使用的是自定义 URL Scheme 来实现从第三方应用程序打开指定应用程序的页面。

这是一种通过特定的URL来唤起应用的方法。你可以在你的应用中注册一个自定义的URL Scheme,然后在网页中的链接中使用这个自定义的URL Scheme。当用户点击这个链接时,系统会检测到这个自定义的URL Scheme并唤起相应的应用。在你的应用中,你需要在 Info.plist 文件中声明你的自定义 URL Scheme,然后在 AppDelegate 中(以 iOS 为例)实现相应的处理逻辑。

  1. Info.plist
    Info.plist文件是iOS应用程序的配置文件,其中包含了应用程序的各种配置信息,如应用程序的名称、版本号、权限声明、URL Scheme等。这个文件通常在Xcode中进行编辑和管理。
  1. AppDelegate
    是iOS应用程序的一个重要文件,它是应用程序的委托对象,负责管理应用程序的生命周期和处理系统事件。在iOS开发中,AppDelegate 类通常包含应用程序启动和终止时的一些核心逻辑。

以微信为例:

weixin://[action]?[参数]

1. action
launch: 打开微信
scanqrcode: 打开微信的扫一扫页面
pay: 打开微信的支付页面
sendmsg: 打开微信的发送消息页面
addfriend: 打开微信的添加好友页面
moments: 打开微信的朋友圈页面
openwebview: 打开微信的内置网页浏览器
...

我们以一个实际的例子进行更详细的解释:

假设我们有一个名为 “MyApp” 的应用程序,它可以展示不同类型的商品列表,并且我们希望通过点击一个链接在第三方应用程序中直接打开我们的应用程序,并跳转到特定类型的商品列表页面。

  1. 注册自定义 URL Scheme:首先,以 ios 为例,在应用程序的 Info.plist 文件中添加 URL Types 字段,并配置 URL Schemes 来注册自定义的 URL Scheme。例如,我们提前注册好一个自定义的 URL Scheme 为 myapp

Info.plist 文件中注册URL Scheme的方法是通过添加 CFBundleURLTypes 键,并在该键的值中添加一个字典,其中包含了一个或多个 CFBundleURLSchemes 键值对,用来指定要注册的URL Scheme。例如:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>myapp</string>
        </array>
        <key>CFBundleURLName</key>
        <string>com.example.myapp</string>
    </dict>
</array>

在上述示例中,应用注册了一个名为 myapp 的 URL Scheme。在应用代码中,你还需要实现相关的逻辑来处理从其他应用跳转过来的URL Scheme。通常,在 AppDelegate 中的 application(_:open:options:) 方法中处理这些 URL Scheme。例如:

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    // 根据传入的 URL 做相应的处理
    if url.scheme == "myapp" {
        // 处理 myapp 的逻辑 如进行跳转
        return true
    }
    return false
}

在上述示例中,当系统调用 application(_:open:options:) 方法时,你可以检查传入的URL,如果URL Scheme是myapp,则执行相应的逻辑。

  1. 构建链接:然后,在第三方应用程序中,我们可以构建一个链接,使用 myapp:// 协议来唤起我们的应用程序。同时,我们可以在链接中传递参数,例如指定打开的页面类型为商品列表,传递商品类型参数。示例:
<a href="myapp://openPage?type=productList&category=electronics">点击查看电子产品列表</a>
  1. 处理链接:当用户在 WebView H5 点击某个按钮,其 src 设置为这个链接,手机会检测到链接中使用了之前 MyApp 应用注册过的自定义的 URL Scheme,因此会尝试唤起与之关联的应用程序。

  2. 应用程序处理:在 “MyApp” 应用程序中,我们需要编写代码来处理这个链接,并根据链接中携带的参数执行相应的操作。我们可以解析链接中的参数,例如获取页面类型为商品列表、商品类别为电子产品的参数,并在应用程序中打开相应的商品列表页面。

这样,通过使用自定义 URL Scheme,我们可以实现从第三方应用程序直接打开我们的应用程序,并执行特定的操作,从而提供更加直观和便捷的用户体验。

当然,前面是手机已经下载了 MyApp 并注册了对应的 URL Scheme,所以在 h5 唤起 App 和跳转页面比较顺利。但如果手机尚未下载与自定义 URL Scheme 关联的应用程序呢?

如果手机尚未下载与自定义 URL Scheme 关联的 MyApp,那么在浏览器 H5 页面尝试唤起 MyApp 时会导致以下可能的现象:

  1. 错误提示或页面无响应: 可能会显示错误提示,例如“未找到应用程序”或类似的消息。或者在用户点击后,页面可能会无响应,因为系统无法找到与 URL Scheme 关联的应用程序。

  2. 默认行为: 有些情况下,浏览器可能会采取默认行为,例如打开类似于 App Store 的页面,提示用户下载相关的应用程序。

为了兜底处理这种情况,你可以考虑以下方案:

  1. 提示下载应用程序: 在 H5 页面上提供一个提示,告知用户需要下载对应的应用程序才能执行操作。这可以通过一个弹窗、页面上的文字或者按钮来实现。比较常见的做法是提供一个 “下载” 按钮,点击后跳转到下载引导页。
  2. 智能判断与导航: 在某些情况下,可以使用 JavaScript 来检测用户设备和操作系统,然后根据检测结果决定是否显示相关操作。这种方式可以根据用户的情况提供不同的导航和提示。比如在一个下载引导页中,点击下载应用后,由于有的 iOS 应用下载后会提示 “未受信任的企业级开发者”,此时就需要用 JavaScript 检测后跳转对应的 iOS 安装提示页。

自定义 URL Scheme 虽然简单易用,但也存在一些局限性,例如在某些情况下无法保证唯一性,容易与其他应用程序的 URL Scheme 冲突,以及无法在网页中直接打开应用程序等。

随着 Universal Link 技术的推出,提供了一种更为统一安全灵活的方式来实现从网页直接跳转到应用程序的指定页面

4. Universal Link(通用链接)

为了改善这种体验,苹果引入了 Universal Link(通用链接)技术。这项技术旨在提供一种更直接、更无缝的方式,使用户能够直接从网页链接跳转到对应的 iOS 应用程序。

假设有一款名为"MyApp"的购物应用程序,用户在浏览器中看到了一篇关于最新促销活动的文章,并在文章中看到了一个产品链接。这个链接是一个通用链接,如下所示:

https://myapp.com/promotions/sale

现在我们来看两种情况下用户的体验:

  1. 用户已安装"MyApp"应用程序

    • 用户点击链接后,系统检测到用户已安装"MyApp"应用程序。
    • 系统直接打开"MyApp"应用程序,并自动导航到促销活动页面。
    • 用户无需进行额外的操作,直接查看促销活动。
  2. 用户尚未安装"MyApp"应用程序

    • 用户点击链接后,系统尝试在浏览器中打开链接。
    • 用户在浏览器中看到促销活动页面,但同时也看到一个提示,建议他们下载并安装"MyApp"应用程序以获得更好的购物体验。
    • 用户决定安装应用程序,并按照提示在应用商店中下载并安装"MyApp"应用程序。
    • 安装完成后,用户回到浏览器中,并重新加载促销活动页面。
    • 系统检测到用户已经安装了"MyApp"应用程序,自动打开应用程序,并直接导航到促销活动页面。

通过这种方式,无论用户是否已安装应用程序,他们都能够丝滑地地访问到促销活动页面,从而提高了用户参与度和购买转化率。

Universal Link 与应用程序关联,使得系统能够在用户点击链接时检查是否存在关联的应用程序。

而建立 Universal Link 与应用程序关联的过程通常涉及以下步骤:

  1. 配置应用程序支持 Universal Links:在应用程序的 Xcode 项目中进行配置,确保应用程序能够识别和响应来自于 Universal Links 的请求。

  2. 配置服务器支持 Universal Links:在您的网站或服务器上创建一个 JSON 文件,包含您应用程序的 Universal Link 的详细信息,比如关联的域名和应用程序的 bundle ID。然后,您需要将这个 JSON 文件部署到您的服务器上,并确保可以通过 HTTPS 访问。

  3. 验证域名和关联的应用程序:在苹果开发者帐户中配置应用程序的 Associated Domains,将您的域名添加到 Associated Domains 列表中。这样做将使得 iOS 系统能够识别您的域名并将其与您的应用程序关联起来。

  4. 更新应用程序的 entitlements 文件:在 Xcode 中更新应用程序的 entitlements 文件,确保它包含了适当的 Associated Domains 权限,并包含了您的域名。

  5. 测试 Universal Links:在您的应用程序和网站上测试 Universal Links 的功能,确保用户点击链接时能够正确地打开您的应用程序,而不是在网页浏览器中打开链接。

以上是大致的步骤,具体的实施细节可能会根据您的应用程序和服务器环境有所不同。建议在实施前查阅苹果的官方文档,以确保正确地配置 Universal Links。

5. 安卓 Universal Link:App Links

类似于 iOS 的 Universal Links,在安卓平台上也有类似的功能,称为 “App Links”。

App Links 允许您的安卓应用程序与特定的网址关联,使得用户在点击链接时可以直接打开应用程序,而不是通过网页浏览器打开。

使用 App Links 的基本原理与 Universal Links 类似,但在安卓平台上的实现细节有所不同。主要的区别在于配置和操作的方式。具体可以参考官方文档。

6. Universal Link 的优势

  • 唯一性和兼容性:与以往的自定义 URL Scheme 不同,Universal Link 使用标准的 HTTPS 协议链接到网站,因此一般不会与其他应用程序的链接冲突。而且,由于 Universal Link 使用 HTTPS 协议,具有更好的兼容性。

  • 安全性:当用户安装了应用程序后,系统会从你配置的网站上下载应用程序关联的说明文件,这个文件声明了当前的 HTTPS 链接可以打开哪些应用程序。由于只有你可以上传文件到你的网站根目录,因此你的网站与应用程序之间的关联是安全的。

  • 可变性:即使用户未安装应用程序,Universal Link 也能够工作。因为在这种情况下,用户点击链接时会在 Safari 中直接显示网站内容,同时也会去下载说明文件。

  • 简洁性:一个 HTTPS 链接同时适用于网站和应用程序,无需额外的配置。

  • 私密性:其他应用程序可以在不需要知道你的应用程序是否已安装的情况下与你的应用程序进行通信。

  • 29
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值