简介:随着Web技术的兴起,其开发效率高、更新方便的特性非常适合快速变化的业务场景,它与移动端结合的模式就是混合开发,让App同时具备原生和Web 的技术优势,下面就一起学习下混合开发的核心原理和实现技术,并掌握使用开源方案,通过掌握的技术开发一个基于实际业务的混合App。

一、混合开发介绍

1-1 混合开发介绍

混合开发定义

一种开发模式,英文名叫Hybrid App

混合使用Native和web技术开发

混合开发有那些优缺点

优点:开发快、易更新,开发周期短

缺点:性能问题、兼容性问题

Android 5.0+ 和IOS9.0+ 上缺点不再明显

混合开发的应用场景

微信公众号,通过JSSDK链接Native端和Web端

微信小程序,通过内置框架链接Native端和Web端

混合开发应用场景-微信公众号

混合开发应用场景-微信小程序

混合开发学习的意义

更好的使用第三方平台

更灵活的技术方案选型

具备搭建平台和输出服务的能力

1-2 混合开发核心技术

混合开发的核心技术

JSBridge

实现Native端和Web端双向通信的一种机

以Javascript引擎或WebView 容器为媒介

通过约定协议进行通信

混合开发的主流技术架构

Web渲染:Cordova(前身是PhoneGap)

原生渲染:React Native、weex

混合渲染:微信小程序

二、混合开发核心技术 JSBridge

2-1 JSBridge 实现原理

JSBridge 实现原理

类比Client/Server模式

将Native 端原生接口分装成JavaScript接口

将Web端JavaScript 接口封装成原生接口

Web端和Native 端之间双向通信

JSBridge 的两种实现方式

方式一:拦截WebView请求的URL Schema

方式二:向WebView注入JS API

2-2 JSBridge 实现方式一:拦截 URL Schema

JSBridge实现方式一:拦截URL Schema

官方

1、URL Schema 是类URL的一种请求格式

2、<protocol>://<domain>/<path>?<query>

3、http://www.google.com/search?keyword=jsbridge

自定义

1、自定义JS bridge通信的URL Schema

2、jsbridge://<method>?<params>

3、jsbridge://showToast?text=hello&a=b

方式一的优缺点

优点:兼容性好

缺点:不直观、URL长度有限制

代码实现

原生调用JS代码,弹出Web弹窗

  private fun showWebDialog(text: String) {
        val jsCode = String.format("window.showWebDialog('%s')", text)
        mWebView.evaluateJavascript(jsCode, null)
    }

Web端接收到原生端发出的请求

window.showWebDialog = text => window.alert(text);

Web端输入内容,点击显示原生弹窗,弹出原生对话框流程

 document.addEventListener('DOMContentLoaded', e => {
      const editText = document.querySelector('#editText');
      const showBtn = document.querySelector('#showBtn');
      showBtn.addEventListener('click', e => {
        const inputValue = editText.value;
        showNativeDialog(inputValue)
      })
    })

Web端通过alert发出一个请求

 function showNativeDialog (text) {
      window.alert('jsbridge://showNativeDialog?text=' + text);
    }

原生端拦截代码

 mWebView.setWebChromeClient(object : WebChromeClient() {
            override fun onJsAlert(
                view: WebView,
                url: String,
                message: String,
                result: JsResult
            ): Boolean {
                if (!message.startsWith("jsbridge://")) {
                    return super.onJsAlert(view, url, message, result)
                }
                val text = message.substring(message.indexOf("=") + 1)
                self.showNativeDialog(text)
                result.confirm()
                return true
            }
        })

这三句话,句句非常重要: 

第一句: self.showNativeDialog(text)

表示我们拦截html中alert函数之后,我们自己的操作,这里是弹出showNativeDialog 

第二句:result.confirm();

这句话非常重要,它表示向WebView通知操作结果,JsResult有两个函数:JsResult.confirm()和JsResult.cancel(),JsResult.confirm()表示点击了弹出框的确定按钮,JsResult.cancel()则表示点击了弹出框的取消按钮。 

如果没有使用JsResult来告诉WebView处理结果,则WebView就会认为这个弹出框还一直弹在那里,你再点击alert按钮,将会无效; 

第三句:return true;

表示告诉WebView我们已经拦截了alert()函数,不需要再弹出网页中的alert弹出框了,如果我们return false,那么WebView就会认为我们没有拦截alert()函数,会继续弹出alert对话框。

2-3JSBridge 实现方式二:注入 JS API

JSBridge 实现方式二:注入 JS API

优缺点:

优点:简单直观

缺点:有兼容性问题(Android 4.2+)

实践:基于注入 JS API方式实现JSBridge

Demo实例,原生端调用Webview端代码不变,通过Webview中执行一段JS脚本来实现,Web端调用原生端的代码需要做一些改变。

需要在原生端Webview中暴露一个全局的JS对象,然后调用全局JS对象的方法

第一步:方法注入addJavascriptInterface(),暴露全局的JS对象,需要传入Java对象和JS对象的方法名

webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");

NativeBridge类

class NativeBridge {
        private Context ctx;
        NativeBridge(Context ctx) {
            this.ctx = ctx;
        }
        @JavascriptInterface
        public void showNativeDialog(String text) {
            new AlertDialog.Builder(ctx).setMessage(text).create().show();
        }
    }

第二步:把NativeBridge 对象暴露到 WebView中的名叫NativeBridge JS对象上,然后我们在Web端就可以拿到这个方法去调用。

function showNativeDialog (text) {
      // window.alert('jsbridge://showNativeDialog?text=' + text);
      window.NativeBridge.showNativeDialog(text);
    }

2-4 带回调的 JSBridge

前面介绍了实现JSBridge的两种方式,一种是基于拦截 URL Schema来实现JSBridge。另一种注入 JS API实现JSBridge,这两种都实现了JSBridge的一个双向通信,但这里的双向通行是结合两端来看的,如果单纯的站在某一端来看的话还是实现的单向通信,没有调用另一端的时候,没有把数据返回,这样就会产生个问题,在实际应用中我们经常需要实现调用对方直接要求对方把结果返回。

场景:Web端获取Nativity端的输入框的值

这个场景下我们需要Web端调用Nativity的方法,去把这个输入框的值获取到传给原生端,同样Nativity端需要把输入的值传回给Web端

什么是带回调的JSBridge?

在对端执行操作并返回结果

有输入有输出才是完整的调用

如何实现带回调的JSBridge?

2-5 使用 JSBridge 的开源实现

学习JSBridge的目的

掌握原理

具备造轮子的能力

避免重复造轮子

JSBridge的开源实现

JSBridge:拦截URL Schema

DSBridge:注入JS API

那么社区有那些JSBridge的开源实现呢,比较流行的就是JSBridgehe、DSBridge。

JSBridge是基于拦截URL Schema这种方式来实现的。

DSBridge是基于注入JS API实现的,所以掌握这两种开源库基本就可以满足大部分场景,可以根据需要去选择

DSBridge实现双向通信

GitHub - wendux/DSBridge-Android: A modern cross-platform JavaScript bridge, through which you can invoke each other's functions synchronously or asynchronously between JavaScript and native.

腾讯X5:腾讯浏览服务

添加依赖

maven { url 'https://jitpack.io' }

implementation 'com.github.wendux:DSBridge-Android:3.0-SNAPSHOT'

implementation 'com.github.wendux:DSBridge-Android:x5-3.0-SNAPSHOT'

布局文件使用 替换原生的 WebView

<wendu.dsbridge.DWebView
    android:id="@+id/webView"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1"
    ></wendu.dsbridge.DWebView>

实现功能:web 输入内容,原生点击获取WEB输入按钮,获取H5的内容展示原生弹窗,

原生输入内容Web点击获取Native按钮,获取原生输入内容展示Web弹窗

js代码:点击获取Native输入

 dsBridge.call('getNativeEditTextValue', '', value => {
          window.alert('Native 输入值:' + value)
        })

Webview中 dsBridge提供了一个注册原生接口的方法,这里提供一个JsApi的类就可以了。这样可以统一管理提供给WebView的方法。

 webView.addJavascriptObject(new JSApi(this), null);

给Web提供调用的方法,

        /**
         * @param msg Web调用原生端传过来的参数
         * @param handler 回调方法 获取到参数之后 通过 handler传递给web端
         */
        @JavascriptInterface
        public void getNativeEditTextValue(Object msg, CompletionHandler<String> handler) {
            String value = ((MainActivity) ctx).editText.getText().();
            handler.complete(value);
        }

三、 开发一个简单的混合 App

基于JSBridge开源库实现双端通信,开发一个为以后原生支持H5功能可以扩展的混合App。

3-1 Web 端发送原生 HTTP 请求

Web端HTTP请求存在的问题

Web发送请求接口XMLHttpRepuest 和fetch

浏览器同源政策CORS安全限制

不够安全、无法优化网络

web端跨域请求的例子

Web端发送原生的Http请求优点

没有浏览器跨域限制

安全加密,签名校验

弱网优化,流量优化

实践:实现Web端发送原生Http请求

通过JSBridge 实现

流程:

将 com.github.lzyzsd.jsbridge.BridgeWebView 添加到您的布局中,它是从 WebView 继承的。

 <com.github.lzyzsd.jsbridge.BridgeWebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></com.github.lzyzsd.jsbridge.BridgeWebView>


注册一个 Java处理函数,以便 JavaScript 可以调用

        webView.registerHandler("submitFromWeb", new BridgeHandler() {
            @Override
            public void handler(String data, CallBackFunction function) {
                Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
                function.onCallBack("submitFromWeb exe, response data 中文 from Java");
            }
        });

Node.js 可以通过以下方式调用这个 Java 处理程序方法“submitFromWeb”:

 window.WebViewJavascriptBridge.callHandler(
                'submitFromWeb'
                , {'param': '需要的参数'}
                , function(responseData) {
                    document.getElementById("show").innerHTML = "原生请求回来的数据 = " + responseData
                }
            );

你可以在Java中设置一个默认的handler,这样js就可以在不分配handlerName的情况下向Java发送消息

Java默认注册

 webView.setDefaultHandler(new DefaultHandler());

Node.js 通过doSend发送消息

 window.WebViewJavascriptBridge.send(
                data
                , function(responseData) {
                    document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData
                }
            );

注册一个 JavaScript 处理函数,以便 Java 可以调用

 WebViewJavascriptBridge.registerHandler("functionInJs", function(data, responseCallback) {
        document.getElementById("show").innerHTML = ("data from Java: = " + data);
        var responseData = "Javascript Says Right back aka!";
        responseCallback(responseData);
    });

Java可以通过以下方式调用这个js处理函数“functionInJs”:


    webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
        @Override
        public void onCallBack(String data) {

        }
    });


你也可以定义一个默认的 handler 使用 init 方法,这样 Java 就可以在不分配 handlerName 的情况下向 js 发送消息


    window.WebViewJavascriptBridge.init(function(message, responseCallback) {
        console.log('JS got a message', message);
        var data = {
            'Javascript Responds': 'Wee!'
        };
        console.log('JS responding with', data);
        responseCallback(data);
    });

将在 webview 控制台中打印 'JS got a message hello' 和 'JS respond with'。

注意

这个库将向window对象注入一个 WebViewJavascriptBridge 对象。您可以监听WebViewJavascriptBridgeReady事件以确保window.WebViewJavascriptBridge存在。

    if (window.WebViewJavascriptBridge) {
        //do your work here
    } else {
        document.addEventListener(
            'WebViewJavascriptBridgeReady'
            , function() {
                //do your work here
            },
            false
        );
    }

或者如果未定义,则将所有 JsBridge 函数调用放入window.WVJBCallbacks数组中,当事件触发window.WebViewJavascriptBridge时,此任务队列将被刷新。

function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) {
        return callback(WebViewJavascriptBridge);
    }
if (window.WVJBCallbacks) {
        return window.WVJBCallbacks.push(callback);
    }
window.WVJBCallbacks = [callback];
}

调用setupWebViewJavascriptBridge然后使用bridge来注册处理程序或调用 Java 处理程序:

setupWebViewJavascriptBridge(function(bridge) {
bridge.registerHandler('JS Echo', function(data, responseCallback) {
console.log("JS Echo called with:", data);
responseCallback(data);
    });
bridge.callHandler('ObjC Echo', {'key':'value'}, function(responseData) {
console.log("JS received response:", responseData);
});
});

3-2 Native 端实现沉侵式换肤功能

Native 端沉侵式换肤功能

Native 端包括标题栏、状态栏、导航栏

Web端包含页面背景

3-3 扫码测试网页功能

操作流程

手动输入测试地址,点击打开按钮,进行页面加载

通过扫码获取在线地址(草料文本二维码生成器

WebSettings说明(史上最全的WebSettings说明_juruiyuan111的博客-CSDN博客

3-4 测试原生API

首页注入Cookie下,测试原生API 方法入口

开放api JsCallNativeApi

四、经验分享

Android如何区分app原生和webview实现

一、抓包

这是比较原始,也是比较容易想到的,打开相应界面,抓取数据包看看,如果有url是返回比较完整的html代码,那基本就是webview来实现的了。

二、利用系统开发人员工具

打开“开发人员工具”功能,点击“设置”->“辅助功能”->“开发人员工具”,在绘图栏中找到“显示布局边界”并打开。这样所有应用的控件布局都一目了然了,webview作为一个控件,只有一个边界框,所以通过这一点,就比较容易区分出一个界面是webview实现的还是native布局控件实现的,当然也不排除用一堆webview来拼成一个界面的实现方法。

混合开发返回键处理

在混合开发的时候,android返回键默认每次按返回都是返回上一页,而有时候我们的需求并不是这样的,如果返回的界面是有弹窗的,那么我们点击返回键需求就是关闭弹窗,而不是返回上一页,那么这时候我们就需要提供接口来进行判断,是否是按需求来返回,还是默认返回,那具体是怎么实现的呢?

主要是重写onKeyDown方法 ,在h5端提供_Native_backListener()方法,返回String或是boolean,若是返回ture,则由h5端处理,若是返回false,则默认返回。

cookie注入

在开发过程中,我们有时会需要让Android原生 登录完成之后记录登录状态,然后在内嵌的 H5 页面也使用当前的登录账户,这个时候,我们可以采用 token 的方式,后台根据 token 方式,去加载对应页面数据。

浏览器调试webview功能

在Android 4.4(KitKat)或更高版本中,使用DevTools可以在原生Android应用中调试WebView内容。

1、在您的原生 Android 应用中启用 WebView 调试;在 Chrome DevTools 中调试 WebView。

2、通过 chrome://inspect 或者 edge://inspect 访问已启用调试的 WebView 列表。

JSSDK开发者文档:

概要

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐