快速搞定移动端混合开发基础

traceId跟踪请求全流程日志 一个系统被拆分成N多个模块,这些模块负责不同的功能,组合成一套系统,最终可以提供丰富的功能。在这种分布式架构中,一次请求往往需要涉及到多个服务,如下图:服务之间的调用错综复杂,对于维护的成本成倍增加,势必存在以下几个问题:服务之间的依赖与被依赖的关系如何能够清晰的看到?出现异常时如何能够快速定位到异常服务?出现性能瓶颈时如何能够迅速定位哪个服务影响的?为了能够在分布式架构中快速定位问题,分布式链路追踪应运而生。 阅读详情

目录

写在前面

一、混合开发介绍

1.1、什么是混合开发?

1.2、混合开发优缺点

1.3、混合开发应用场景

1.4、了解混合开发的意义

二、混合开发的核心技术

2.1、混合开发核心技术——JSBridge

2.2、混合开发主流技术框架

2.3、JSBridge实现原理

三、JSBridge的实现方式

3.1、创建Native和Web工程

3.2、拦截URL Schema

3.3、注入JS API

3.4、带回调的JSBridge

3.5、JSBridge的开源实现

四、实战案例——开发一个简单的混合APP


写在前面

文章有点长,请注意保护您的眼睛,如若引起身体不适请自行关闭!

之前是定了个目标说是做到每周一更,很开心已经坚持了三周了,希望接下来能够再接再厉吧!今天抽空写个基础业务方面的——移动端混合开发基础。对于应用层来说,其实混合开发用的还是比较多的,相信你肯定也用过,很多为了快速开发快速迭代节省成本的业务,基本上都会采用这种方式去进行开发,今天就做个总结,整理一下这方面的基础知识点。

本篇代码地址:https://github.com/JArchie/JSBridgeDemo

一、混合开发介绍

1.1、什么是混合开发?

  • 它是一种开发模式,并不是一种具体的技术,英文名称叫Hybrid APP
  • 混合使用Native(Android、iOS)和Web(HTML、CSS、JavaScript)技术开发

这两类技术各自的特点通过混合开发可以把它们结合起来,比如对于原生开发Android一般使用的就是Java和Kotlin,iOS一般使用的就是Objective-C和Swift。原生开发的实际操作过程都是比较慢的,改动一行代码你就需要重新编译打包安装到硬件设备上面看效果,如果有问题又要重复这个过程,在你发布升级的时候,用户需要重新安装这个APP,所以推送升级也是个问题。而Web技术就可以克服这些缺点,比如你修改代码之后,刷新一下页面就可以立马看到修改后的结果,当它发布的时候,部署之后用户下次打开就可以看到最新的页面,所以它并不存在发包覆盖率的问题,把这两种技术结合起来,就可以各自做各自擅长的事情,这也是混合开发的意义所在,也因此来说混合开发只是一种模式。

1.2、混合开发优缺点

其实上面也提到了一部分,这里做个总结性的概括:

  • 优点:开发快(介于原生和Web开发的速度之间)、易更新(原生开发作为容器,里面的内容采用Web技术部署静态页面,用户动态加载更新)、开发周期短(开发速度快加之具有即时发布更新的特点)
  • 缺点:性能问题(相较于纯原生开发Web技术在切页和手势动画等方面没有原生流畅)、兼容性问题(原生承载Web网页相当于原生充当了浏览器的角色,浏览器不同的版本支持的Web特性是不一样的)
  • Android 5.0+ 和 iOS 9.0+ 缺点不再明显(硬件设备自身性能和操作系统版本提升)

1.3、混合开发应用场景

①、微信公众号,通过JSSDK连接Native端和Web端

我们可以把微信看做是原生开发,微信公众号内部的网页则是Web开发,它们通过JSSDK连接起来,这样在做微信公众号的时候可以使用Web技术发布更新,同时可以利用JSSDK使用到微信提供的原生能力。

②、微信小程序,通过内置框架连接Native段和Web端

微信小程序相比较于微信公众号则更进一步,它相当于是将JSSDK和Web技术内置整合了一套框架,内部会将这些调用和渲染连接到Native端,使用它的框架编写程序,它自身会做桥接的工作。

1.4、了解混合开发的意义

①、更好的使用第三方平台

比如我们要开发微信小程序或者在某个Web平台上开发一些插件,我们就需要了解混合开发的原理和机制,这样在使用第三方平台提供的接口调用时若发生某些特定的行为,可以深刻的理解生命周期和调用的链路,从而能够更好的使用第三方平台。

②、更灵活的技术方案选型

比如领导让你开发一款新的移动应用时,如果你了解混合开发,那么你可以选择纯原生开发,也可以通过原生结合Web技术进行混合开发,技术选型更加灵活。

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

当你了解混合开发之后,你可以搭建一个类似微信公众号或微信小程序这样的平台,可以把原生APP提供各种接口能力并输出成JSSDK。

二、混合开发的核心技术

2.1、混合开发核心技术——JSBridge

  • 实现Native端和Web端双向通信的一种机制
  • 以JavaScript引擎或WebView容器为媒介
  • 通过约定协议进行通信

2.2、混合开发主流技术框架

  • Web渲染:Cordova(前身是PhoneGap)
  • 原生渲染:React Native、Weex
  • 混合渲染:微信小程序

2.3、JSBridge实现原理

由上图可以看出,上面是Web端,下面是Native端,JSBridge在中间起了一个桥梁作用,透过JSBridge,Web端可以直接调用Native端的Java接口,Native端也可以调用Web端提供的JavaScript接口,从而可以实现Web端和Native端之间的双向通信。可以把这种模式和客户端/服务器这种C/S模式作一个对比,把Web端比作客户端,Native端比作Server端,在Web端调用Native端接口时和客户端向Server端发出一个请求类似,其中JSBridge在这个过程充当的就是HTTP协议的角色。

  • 类比Client/Server模式
  • 将Native端原生接口封装成JavaScript接口
  • 将Web端JavaScript接口封装成原生接口
  • Web端和Native端之间双向通信

三、JSBridge的实现方式

3.1、创建Native和Web工程

在说JSBridge的几种实现方式之前,我们还得做一件事,先来创建两个Demo工程,一个是Android端,一个是Web端,然后在这个基础上通过实际的代码来介绍几种JSBridge的用法,这样看的也比较清晰明了!

OK,先来创建一个Web工程,这里我使用的是IDEA,然后在/Users/Jarchie/Desktop/AndroidProjects/Web/src 这个目录下创建一个index.html页面,内容也很简单,如下图所示:

这里我使用node安装一个http静态服务来跑这个web页面,打开终端进入到页面所在目录,使用npm i -g http-server 命令安装静态资源服务器,执行 http-server 它会在当前服务下启一个静态资源服务,这样可以拿到一个地址,如下所示:

然后可以直接通过这个地址在浏览器中访问这个静态页面,如下图:

然后再来创建一个Android工程,界面也比较简单,上面是Web页面,下面是Android页面,布局代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#eaeaea"
    android:orientation="vertical">

    <WebView
        android:id="@+id/mWebview"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="vertical">

        <EditText
            android:id="@+id/mEditText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="请输入内容"
            android:textSize="45sp" />

        <TextView
            android:id="@+id/mShowBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#BBB5B5"
            android:text="显示Web弹窗"
            android:textSize="45sp" />

        <TextView
            android:id="@+id/mRefreshWeb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:background="#BBB5B5"
            android:text="刷新Web页面"
            android:textSize="45sp" />
    </LinearLayout>

</LinearLayout>

然后在Activity页面中进行数据初始化操作:

package com.jarchie.jsbridge;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.webkit.WebView;
import android.widget.EditText;
import android.widget.TextView;

import java.util.Date;

/**
 * 作者: 乔布奇
 * 日期: 2020-04-11 13:50
 * 邮箱: jarchie520@gmail.com
 * 描述: Native端页面展示层,上方为Web页面,下方为Android页面
 */
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private WebView mWebview;
    private EditText mEditText;
    private TextView mShowBtn,mRefreshWeb;

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

    //初始化数据加载
    private void initData() {
        //为了每次获取的页面都是最新的,加了个时间戳,防止页面有缓存
        mWebview.loadUrl("http://192.168.0.102:8080/?timestamp" + new Date().getTime());
    }

    //初始化事件监听
    private void initListener() {
        mShowBtn.setOnClickListener(this);
        mRefreshWeb.setOnClickListener(this);
    }

    //初始化绑定控件
    private void initView() {
        mWebview = findViewById(R.id.mWebview);
        mEditText = findViewById(R.id.mEditText);
        mShowBtn = findViewById(R.id.mShowBtn);
        mRefreshWeb = findViewById(R.id.mRefreshWeb);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.mShowBtn:
                break;
            case R.id.mRefreshWeb:
                mWebview.loadUrl("http://192.168.0.102:8080/?timestamp" + new Date().getTime());
                break;
        }
    }
}

接着把它运行一下,看下结果:

到了这里,准备工作就已经做完了,接下来就来说JSBridge的几种用法了!

Web端调用Native端的实现方式有两种:①、拦截WebView请求的URL Schema;②、向WebView注入JS API

原生端调用Web端的实现方式只有一种:直接执行js代码去调用web端代码

3.2、拦截URL Schema

3.2.1、原理及特点简介

  • URL Schema是类URL的一种请求格式
  • <protocol>://<domain>/<path>?<query> 比如:https://www.google.com/search?keyword=xxx
  • 自定义JSBridge通信的URL Schema
  • jsbridge://<method>?<params> 比如:jsbridge://showToast?name=jarchie&age=27

上面的这个例子中自定义的Schema是以jsbridge开头,后面的showToast是调用的原生方法名,问号后面是需要传给这个方法的参数对,那么它是怎么实现拦截来调用原生方法的呢?它的原理这里用一张图片来说明:

原生端的WebView加载Web网页之后,Web网页中发出的所有请求都会经过WebView组件,所以原生端可以去重写WebView组件里面的方法从而拦截Web网页中的请求,对请求的地址作出判断,如果它是符合自定义jsbridge的格式,就进行解析,解析之后拿到对应的方法名及相关参数,去调用对应的原生方法,如果不是自定义的URL Schema,比如它是一个http协议,在正常的拦截之后就进行转发请求真正的服务。这个过程Web端虽然没有真正的调用Native端的方法,但它间接实现了调用的过程,从逻辑上来看就是Web端调用了原生端的方法,这就是它的实现机制。

  • 优点:兼容性好(Android4.4以下也是支持的)
  • 缺点:不直观、URL长度有限制

3.2.2、代码实战

(一)、原生端调用Web端弹窗展示内容

在写代码之前先来理一下业务逻辑,我们需要实现的效果是:在原生端输入内容,然后点击按钮调用Web端弹窗展示。

OK,按照这个顺序一步一步来写,首先要给原生的Webview设置支持js脚本:

mWebview.getSettings().setJavaScriptEnabled(true);

接着封装一个方法,将原生端输入的内容传入,然后执行一段js代码,调用Web端弹窗展示这个内容:

//Native端调用Web端方法,只有一种方式,直接执行js代码
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private void showWebDialog(String content){
    String jsCode = String.format("window.showWebDialog('%s')",content);
    mWebview.evaluateJavascript(jsCode,null);
}

添加点击事件,将输入框的内容传入该方法:

case R.id.mShowBtn://点击按钮获取输入框值,并传入对应的调用Web端的方法中
    showWebDialog(mEditText.getText().toString().trim());
break;

在Web端的showWebDialog方法中调用window展示:

<script>
    window.showWebDialog = content => window.alert(content);
</script>

这样我们就完成了,来看下效果,如下图所示:

(二)、Web端调用原生端弹窗展示内容

这次的业务逻辑是反向操作,在Web端输入内容,点击Web端按钮调用原生端的弹窗展示这个内容。

首先,在Web端给文档添加一个监听事件,在DOM加载完成之后获取对应的DOM元素,然后给按钮添加一个点击事件,拿到输入框的值,接着封装一个方法,将获取的输入框的值传入,在方法内部实现调用原生端的弹窗,这里就是通过自定义URL Schema的方式去实现,具体的代码如下:

    <script>
        document.addEventListener('DOMContentLoaded', e => {
            const editText = document.querySelector('#editText');
            const showBtn = document.querySelector('#showBtn');
            showBtn.addEventListener('click', e => {
                const inputValue = editText.value;
            showNativeDialog(inputValue)
            })
        })
        function showNativeDialog(content) {
            window.alert('jsbridge://showNativeDialog?content=' + content);
        }
    </script>

然后我们在原生端自定义WebChromeClient拦截Alert方法,通过解析URL Schema获取Web端输入的值:

//自定义WebChromeClient拦截自定义的URL Schema
    private class MyWebChromeClient extends WebChromeClient{
        @Override
        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
            if (!message.startsWith("jsbridge://")){
                return super.onJsAlert(view, url, message, result);
            }
            String content = message.substring(message.indexOf("=")+1);
            showNativeDialog(content);
            result.confirm();
            return true;
        }
    }

然后再封装一个显示原生弹窗的方法showNativeDialog:

//Web端调用原生端方法
    private void showNativeDialog(String content){
        new AlertDialog.Builder(this)
                .setTitle("Web端调用Native端")
                .setMessage(content)
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                    }
                })
                .create()
                .show();
    }

这样也就完成了,来看下效果:

3.3、注入JS API

3.3.1、原理及特点简介

这种方式相比较于自定义URL Schema的方式更加直观,从下图中也能够看出:

APP可以将原生的Java接口注入到WebView中,在WebView中暴露出一个JS对象,JS对象方法名跟原生的接口方法名是一一对应的,Web端就可以直接引用这个暴露的全局JS对象,通过这个对象调用到原生端的方法。

  • 优点:简单直观
  • 缺点:有兼容性问题(Android 4.2+,之前存在兼容性和安全漏洞,现在随着系统升级这些问题也都有对应的解决办法,实际应用中优先推荐这种方式)

3.3.2、代码实战

原生端调用Web端的代码和上面的一样不用做任何的改动,因为它就一种实现方式,直接执行js脚本。

重点来看Web端调用原生端的这块业务,这里使用JS注入API的方式来实现。首先来看下原生端如何修改?我们这里使用WebView的addJavaScriptInterface方法暴露全局的js对象,需要传入一个js对象和需要暴露的js对象的方法名,所以这里写一个内部类,就是需要暴露的js对象及相关的方法:

class NativeBridge {
        private Context mContext;

        NativeBridge(Context context) {
            this.mContext = context;
        }

        //注意必须加这个注解
        @JavascriptInterface
        public void showNativeDialog(String content) {
            new AlertDialog.Builder(mContext)
                    .setTitle("Web端调用Native端")
                    .setMessage(content)
                    .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.cancel();
                        }
                    })
                    .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.cancel();
                        }
                    })
                    .create()
                    .show();
        }
    }
mWebview.addJavascriptInterface(new NativeBridge(this), "NativeBridge");

然后看Web端,这里给文档添加事件监听,给按钮添加点击事件,获取输入框的值这一部分不用改动,需要改动的是我们封装的调用原生弹窗的方法:

    <script>
        window.showWebDialog = content => window.alert(content);
        document.addEventListener('DOMContentLoaded', e => {
            const editText = document.querySelector('#editText');
            const showBtn = document.querySelector('#showBtn');
            showBtn.addEventListener('click', e => {
                const inputValue = editText.value;
            showNativeDialog(inputValue)
            })
        })
        function showNativeDialog(content) {
            window.NativeBridge.showNativeDialog(content);
        }
    </script>

来看下运行的效果,如下图所示:

3.4、带回调的JSBridge

3.4.1、简介及原理说明

在实际应用场景中,我们经常需要在对端执行结果后需要把执行结果给返回,比如:Web端获取Native端输入框的值,那么这就需要支持回调的JSBridge了,什么是带回调的JSBridge?这里总结亮点:

  • 在对端执行操作并返回结果
  • 有输入有输出才是完整的调用

如何实现带回调的JSBridge?

从上图中可以看到,在WebView实现单向调用的时候,可以在参数中加一个特殊的标记callback id,Native端收到Web端的调用请求之后,会去检查参数中是否带有callback id,如果有,就立即执行一次调用,从Native端调用Web端接收消息的方法,将Native端执行的结果传回给Web端,Web端拿到结果之后,同时会检查callback id跟之前请求的callback id是否一致,这样就知道这个结果是哪一次调用所返回的,从而实现了支持带回调的JSBridge。

3.4.2、代码实战

(一)、Web端展示原生端输入框内容

首先在web端新加一个按钮获取Native端输入:

<div>
    <button id="showBtn2">获取Native输入</button>
</div>

然后在js中保存这个按钮的引用,并给这个按钮添加一个点击事件,在点击事件内部获取web端的输入并用原生弹窗展示:

const showBtn2 = document.querySelector('#showBtn2');
showBtn2.addEventListener('click', e=> {
    window.JSSDK.getNativeEdittextValue(value => window.alert('Native端输入值:'+value))
})

这里封装了一个JSSDK,这里创建创建一个回调id,并使用Map将其保存,每次请求都产生一个新的回调id,并切提供两个支持回调的方法:获取原生输入框的值、接收原生消息返回的值:

        let id = 1
        const callbackMap = {}
        window.JSSDK = {
            getNativeEdittextValue(callback){
                const callbackId = id++
                callbackMap[callbackId] = callback
                NativeBridge.getNativeEdittextValue(callbackId)
            },
            receiveMessage(callbackId,value){
                if (callbackMap[callbackId]){
                    callbackMap[callbackId](value)
                }
            }
        }

然后在Native这边新增一个获取输入框值的方法,并调用web端方法,这里同样的就是构造js代码:

class NativeBridge {
        private Context mContext;

        NativeBridge(Context context) {
            this.mContext = context;
        }

        @JavascriptInterface
        public void getNativeEdittextValue(int callbackId){
            final MainActivity mainActivity = (MainActivity) mContext;
            String value = mainActivity.mEditText.getText().toString().trim();
            final String jsCode = String.format("window.JSSDK.receiveMessage('%s','%s')",callbackId,value);
            mainActivity.runOnUiThread(new Runnable() {
                @RequiresApi(api = Build.VERSION_CODES.KITKAT)
                @Override
                public void run() {
                    mainActivity.mWebview.evaluateJavascript(jsCode,null);
                }
            });
        }
    }

这样就写完了,来看下运行效果:

(二)、Native端展示Web端输入框内容

首先在原生端页面上添加一个按钮mShowBtn2,绑定控件并添加点击事件,代码都很简单就不贴了,后面大家自己看源码吧。

然后同样的封装一个NativeSDK,内部提供两个方法:获取Web端输入框的值并传入一个回调接口,接收Web消息回传的值:

interface Callback {
        void invoke(String value);
    }

    class NativeSDK {
        private Context mContext;
        private int id = 1;
        private Map<Integer, Callback> callbackMap = new HashMap();

        NativeSDK(Context context) {
            this.mContext = context;
        }

        void getWebEditTextValue(Callback callback) {
            int callbackId = id++;
            callbackMap.put(callbackId, callback);
            final String jsCode = String.format("window.JSSDK.getWebEditTextValue(%s)", callbackId);
            ((MainActivity) mContext).runOnUiThread(new Runnable() {
                @RequiresApi(api = Build.VERSION_CODES.KITKAT)
                @Override
                public void run() {
                    ((MainActivity) mContext).mWebview.evaluateJavascript(jsCode, null);
                }
            });
        }

        void receiveMessage(int callbackId, String value) {
            if (callbackMap.containsKey(callbackId)) {
                callbackMap.get(callbackId).invoke(value);
            }
        }
    }

然后在NativeBridge中添加一个接收web端回传值的方法:

@JavascriptInterface
public void receiveMessage(int callbackId, String value) {
    ((MainActivity) mContext).nativeSDK.receiveMessage(callbackId, value);
}

接着同样定义一个显示原生窗口的方法:

//获取Web端输入的值并展示
    private void showNativeDialog2(String content) {
        new AlertDialog.Builder(this)
                .setTitle("获取Web端输入的值")
                .setMessage(content)
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                    }
                })
                .create()
                .show();
    }

然后实例化我们之前定义的Native SDK,点击按钮通过sdk调用对应的方法,展示弹窗:

case R.id.mShowBtn2:
    nativeSDK.getWebEditTextValue(new Callback() {
        @Override
        public void invoke(String value) {
            showNativeDialog2("Web 输入值:" + value);
        }
    });
    break;

最后修改web端,在JSSDK中定义getWebEditTextValue方法,将输入框的值通过NativeBridge的receiveMessage方法传回:

getWebEditTextValue(callbackId){
    const editText = document.querySelector('#editText');
    const value = editText.value
    NativeBridge.receiveMessage(callbackId,value)
}

来看下运行结果:

3.5、JSBridge的开源实现

3.5.1、JSBridge开源库介绍

首先我们学习JSBridge应当掌握它的实现原理,具备造轮子的能力,但是我们应该避免重复造轮子,尽量复用开源社区已有的实现,并且这些开源库都是经过大量的实践验证目前已经很稳定了,对于JSBridge来说有哪些优秀的开元实现呢?

  • JSBridge:基于拦截URL Schema
  • DSBridge:基于注入JS API

掌握这两类实现方式基本上可以应对大部分的场景了,大家可以根据自己的需要去选择,这里我选择使用第二种方式来介绍它的用法,接下来我会使用DSBridge来重写我们上面的Demo,下面给大家看一下DSBridge的相关介绍:

3.5.2、DSBridge重写Demo

(一)、Web端展示Native端输入框内容

首先来看下Web端的实现:根据文档先要引入dsbridge.js,引入之后会在全局暴露一个dsBridge的全局变量,通过这个变量可以调用一些原生提供的方法,这里通过dsBridge.call方法去调用原生的getNativeEditTextValue方法,获取到输入框的值之后会得到一个回调,在回调内部通过Web弹窗将获取的值显示出来:

<script src="https://unpkg.com/dsbridge@3.1.3/dist/dsbridge.js"> </script>
    <script>
        document.addEventListener('DOMContentLoaded', e => {
            const showBtn2 = document.querySelector('#showBtn2');
            showBtn2.addEventListener('click', e => {
                dsBridge.call('getNativeEditTextValue','',value => {
                    window.alert('Native端的输入值:' + value)
                })
            })
        })
    </script>

原生端的DWebView(DSBridge中提供的,我们需要使用这个WebView)中提供了一个注册原生接口的方法addJavascriptObject,然后提供一个自定义的JSAPI的类,类中统一管理提供给webview的方法:

mWebview.addJavascriptObject(new JSAPI(this),null);
class JSAPI{
        private Context mContext;
        
        public JSAPI(Context context){
            this.mContext = context;
        }

        @JavascriptInterface
        public void getNativeEditTextValue(Object msg, CompletionHandler<String> handler){
            String content = ((MainActivity)mContext).mEditText.getText().toString().trim();
            handler.complete(content);
        }
    }

来看下运行结果:

(二)、Native端展示Web端输入框内容

首先Native端在按钮的点击事件触发时,通过DWebView调用callHandler方法,这个方法有三个参数分别是:要调用的Web端的api名称、传递的参数、结果回调,这里定义web端方法名为getWebEditTextValue,在回调中调用显示原生弹窗的方法:

mWebview.callHandler("getWebEditTextValue", null, new OnReturnValue<String>() {
                    @Override
                    public void onValue(String retValue) {
                        showNativeDialog("Web端输入值:" + retValue);
                    }
                });

然后Web端通过dsBridge调用register去注册一个web方法给原生端调用,并且可以将返回值返回给原生端:

dsBridge.register('getWebEditTextValue', () => {
            const editText = document.querySelector('#editText')
            return editText.value
        })

来看下运行结果:

OK,写到这里关于JSBridge相关的知识点就写完了,从上面的实现过程相信大家也能看出,使用开源库来实现JSBridge代码还是相当简洁的,内部都是做了高度封装,我们只需要简单的调用几个API就可以实现我们开始一大堆代码才能完成的效果,所以实际开发中还是推荐大家使用开源库来实现。

四、实战案例——开发一个简单的混合APP

文章的最后一部分通过DSBridge这个开源框架来实现一个简单的Hybrid APP,关于DSBridge的相关用法上面也介绍过了,更加详细的用法请移步至官方文档:https://github.com/wendux/DSBridge-Android.

因为是一个综合案例,所以具体的代码我就不详细写了,因为用到的技术点上面也都说过了,这里就说一下具体的需求、实现思路以及实现效果,具体的源码我也已经放到github上面了,有需要的请自提!

功能需求:

  • ①、Web端调用原生端发送HTTP请求,并将请求结果展示到Web端;
  • ②、APP整体沉浸式换肤效果

实现思路:

需求①:首先在原生端通过addJavascriptObject注册一个原生接口,并提供一个JSAPI类,类的内部提供requestHttp()方法,同时将结果返回给Web端;在Web端添加所需的html元素,并给按钮添加点击事件,通过dsBridge.call调用原生端封装的发送http请求的方法比如就叫requestHttp(),拿到的返回值放到对应的html标签上展示即可。

需求②:首先在页面标题栏添加一个菜单栏就叫换肤,点击换肤调用换肤方法实现换肤效果。对于状态栏、标题栏、导航栏这些直接调用原生API设置颜色值改变对应的颜色,对于Web页面使用webview.callHandler传入web需要定义的方法名及参数,在web端通过dsBridge.register注册原生端需要的方法,在方法内部实现web页面背景色的更改。

实现效果:

如下图所示,左图是需求①的效果,右图是需求②的效果:

写到这里很是疲惫,总结的不好有点惭愧,没啥说的下期再会!

工程源码:https://github.com/JArchie/JSBridgeDemo

RuoYi项目集成UEditorPlus和Vant:一套代码搞定PC管理后台与移动端H5 本文详细介绍了如何在RuoYi-Vue3项目中集成UEditorPlus富文本编辑器和Vant移动端组件库,实现一套代码同时适配PC管理后台和移动端H5的高效开发方案。通过技术选型分析、架构设计原则和实战案例,展示了如何解决多端适配中的样式隔离、性能优化等关键问题,提升开发效率和用户体验。 阅读详情

相关推荐

Vue H5项目实战:5分钟搞定移动端NFC读取(含完整代码与避坑指南)

本文详细介绍了如何在Vue H5项目中快速集成移动端NFC读取功能,提供完整代码实现与避坑指南。从环境配置、核心实现到性能优化,帮助开发者解决Android与H5混合开发中的典型问题,提升移动应用竞争力。

weixin_28703507的博客 222

全链路追踪体验—TraceId的生成

今天先来实现一个最简单的TraceId

龙大 1898

好未来秋招移动端笔试复盘:考点、踩分点与备考策略

移动端开发不仅要求掌握Android/iOS原生机制,还需要理解网络通信、操作系统等底层原理。在复杂业务场景中,Vue等跨端框架广泛用于H5页面与混合开发,成为笔试与面试的高频考点。笔试中的场景设计题更要求开发者从业务痛点出发,给出兼顾性能与兼容性的方案。好未来秋招移动端开发岗的笔试复盘显示,备考需要在算法、基础知识和工程实践之间找到平衡。本文梳理了笔试的出题逻辑、各类题型的踩分要点,以及两周备考节奏,帮助求职者构建完整的移动端知识体系。

weixin_34015336的博客 436

移动端开发之混合开发模式优劣势综述

2020年,我国互联网行业实现快速发展,网民规模稳定增长。截至2020年底,我国5G网络用户数超过1.6亿,约占全球5G总用户数的89%。截至2020年底,我国网民规模达9.89亿,互联网普及率达到70.4%。报告还显示,目前我国已建成全球最大的光纤网络、4G 和 5G 独立组网网络。是在 Android、iOS 等移动平台上利用提供的开发语言、开发类库、开发工具进行 App 软件开发。比如 Android 是利用 Java、Eclipse、Android studio;

m0_72622669的博客 1966

Flutter基础+旅游实战项目:从环境搭建到完整App开发

跨端开发已成为移动应用提效的重要方向,性能与UI一致性是技术选型的核心考量。Flutter凭借自绘渲染引擎和组件化能力,提供了接近原生的体验,其“万物皆Widget”的理念正被越来越多开发者采用。理解Widget生命周期与状态管理(如Provider)是掌握Flutter架构的关键,也是从基础语法迈向工程实践的必经之路。在旅游、电商等典型业务场景中,一个包含环境搭建、列表详情、地图定位与图片上传的完整项目,能有效串联知识点并形成可落地的工程方案。文章梳理Flutter基础,并通过旅游实战项目展示如何构建可复

weixin_33933118的博客 528

牛客模考移动端笔试复盘:考点拆解与实战备战策略

在移动端开发岗位的求职竞争中,线上笔试不仅是知识的检验,更是压力与时间管理能力的试炼。理解笔试背后的考察逻辑,是高效备战的关键。通常,这类考核会从计算机基础、算法与数据结构出发,延伸至网络、操作系统等通用技术素养,以此评估候选人的工程思维与问题排查能力。在移动端领域,性能优化、跨平台方案选型、调试工具链的熟练运用,以及图表渲染等实战细节,共同构成了区分资深开发者与初学者的分水岭。通过复盘模拟考试中的典型题目与解题思路,我们可以掌握从启动速度优化、内存管理到vConsole调试、ECharts移动端适配等一系

weixin_32466193的博客 231

什么是混合移动App开发?

苹果上的软件是如何开发出来的:使用的是 OC、或者使用Swift这门语言安卓平台上的软件又是如何开发出来的:使用安卓相关的语言开发的,Java,安卓的控件进行开发苹果和安卓平台上共有的软件是如何开发出来的:腾讯招两套开发人员【开发组】,手机京东前端移动 App(Application)开发技术,去开发手机端的应用程序;前端的混合移动App开发技术,并没有使用 苹果 或 安卓 官方推荐的 开发平台和开发方式,而是抛弃了 官方提供的方式,使用 前端的独有的技术进行移动App开发体验;

青职院课外学习打卡博客 1205

基于mui框架的移动端聊天应用Demo实战(仅限APP)

简介:本文详解如何使用轻量级前端框架mui构建一个仅适用于移动App的聊天应用Demo,涵盖页面布局、消息展示、数据存储、实时通信与交互功能实现。通过流式布局、CSS消息气泡、本地存储、WebSocket集成及性能优化等技术,帮助开发者快速搭建高效、流畅的聊天界面,适用于HTML5移动应用开发场景。

weixin_35094083的博客 749

移动端混合开发框架有哪些

这些移动端混合开发框架各有优势和特点,开发者在选择时需要根据项目需求、团队技术栈、开发效率和性能要求等因素进行综合考虑。例如,对于需要高性能和复杂UI的应用,Flutter可能是一个不错的选择;而对于已有React或Vue.js开发经验的团队,React Native或UniApp可能更适合。

xuweilin的博客 2022

流量特征工程实战:基于Python脚本的CIC-FlowMeter批量自动化处理

本文详细介绍了如何利用Python脚本对CIC-FlowMeter工具进行批量自动化封装,实现从海量PCAP文件中高效提取流量特征并生成CSV文件。通过构建健壮的自动化流水线,解决了手动处理效率低下、易出错的问题,并分享了并行处理、错误处理等关键优化技巧,助力网络流量分析与机器学习数据预处理工作。

c6d7e8f9g的博客 744

解读Autoware.Universe规划模块:Behavior Path Planner

Behavior Path Planner是一个基于行为生成不同形态路径的路径规划器,负责生成 基于交通状况的路径(path), 车辆可以移动的可行驶区域(drivable area)(在路径消息中定义), 将被发送到车辆接口的转向信号(turn signal) 命令。

策 5491

前端移动端开发分类及跨平台开发框架简述

前端移动端主流分为以下三种:Native App ,Hybrid App ,Web App。

Ccfitch的博客 4601

LC滤波器设计实战:从理论计算到PCB布局的完整指南

滤波器是信号处理与电路设计中的基础组件,用于从复杂信号中提取特定频率分量或抑制干扰噪声。其核心原理基于电感(L)和电容(C)等无源元件的频率选择特性,通过特定的网络拓扑实现幅频与相频特性的塑造。在工程实践中,LC滤波器因其结构简单、可靠性高、成本可控,成为射频、模拟及电源电路中的关键技术手段。其设计价值在于将理论网络综合转化为可量产、高性能的硬件解决方案,广泛应用于通信系统、音频处理、电源净化和传感器信号调理等场景。本文聚焦于LC滤波器的工程设计全流程,深入剖析了巴特沃斯、切比雪夫等经典类型的选型权衡,并重

weixin_34194829的博客 273

Hybird APP (混合开发)简介

写了几个 APP,最初是打算用纯 Native 的,可是我自身的 Android、IOS 知识并不能支撑我用纯 Native 构建项目,可项目又迫在眉睫。还好有前辈指点了一下,可以采用 Hybrid APP(混合开发),一路磕磕绊绊的完成了项目。看了不少资料,从菜鸟的角度来总结下 Hybrid APP(混合开发)。

尽力而为 5万+

c语言实现语音检测vad_壹鸽技术科普·智能语音客服系统中的前端语音处理技术...

随着智能客服机器人产业的迅速发展,尤其是伴随着人工智能AI的风潮,预计在2020年我国智能客服市场将达到万亿级别。在智能客服机器人领域中对电话信道语音进行端点检测处理的应用正在不断扩展和深化。目前语音端点检测(Voice Active Detection,简称VAD)的主要任务是准确快速判决出待处理的语音信号有话还是无话,作为自动语音识别(Automatic Speech Recognition,...

weixin_36431018的博客 508

2026.1.9:最新VSCode集成claude插件完美解决方案,跳过登录界面,使用国内Kimi K2进行计费

本文介绍了如何安装配置Claude插件并连接Kimi K2 API的详细步骤:1)搜索安装Claude插件;2)进入设置关闭登录提示;3)在settings.json中添加API配置,包括ANTHROPIC_AUTH_TOKEN(需替换为Kimi API Key)和ANTHROPIC_BASE_URL;4)通过Kimi官网获取API Key。完成配置后即可正常使用该插件。文中配有完整的操作截图指引。

拥抱具身智能,昇腾Ascend C 8721

从原生迈向混合,小而美团队如何搞定APP高效定制

洞悉华为云数字化差旅App的架构变迁之路,体验混合开发魅力。

华为云官方博客 985

23、OpenMP常见用例与标准循环级示例详解

本文详细介绍了OpenMP的三种典型用例:循环级OpenMP、高级OpenMP和MPI+OpenMP,适用于不同层次的并行需求。重点讲解了循环级OpenMP在向量加法、流三元组和模板计算中的应用示例,并探讨了首次触摸优化、线程绑定、NUMA架构影响等关键性能优化策略。通过实际代码演示和性能分析,帮助开发者快速掌握OpenMP的基础并行化方法,并为后续高性能优化提供指导。

fanta的博客 87

vue 判断移动设备加载不同代码_移动端H5本地化方案调研和实践

移动端H5本地化方案调研和实践点击上方“技术支持联盟”,选择“置顶公众号”第一时间了解程序员大小事儿作者:萱少,在工作中是一个性格开朗,乐于交流的程序员,在部门担任移动端和web前端的职位,主要工作包括技术攻关,技术支持,疑难问题排查等相关工作。解决问题、开发出使用简单的产品使我乐此不疲。摘要:随着越来越多的APP采用H5加原生的混合开发模式,H5在APP中加速度的优化是势在必行,静态资...

weixin_39622980的博客 329

SY6982C 7.4V锂电池1A充电模块原理图设计,已量产

在手持设备、便携工具与小型无人机等场景中,7.4V(2S)锂聚合物电池的应用愈发广泛。这类电池对充电系统的安全性、效率与兼容性提出了明确要求:需支持 USB 5V 输入、1A 安全充电,同时具备多重保护机制。本文将结合一款基于 SY6982C 的充电原理图,深入解析其电路设计与技术亮点。

不脱发的程序猿 360

脑电信号处理实战 01 | 从 0 跑通第一个 EEG 分析:加载、滤波、频谱与“睁眼闭眼“

本文以 UCI 公开数据集"EEG Eye State"为例,从零走通脑电处理的完整流水线——数据加载、1–40 Hz 带通滤波、Welch 功率谱、频带功率、通道相关性与箱线图质控、睁闭眼对比和时频分析,并用 8 张科研风格图层层揭示这份数据中的伪迹与通道重复问题,说明"流程跑得通不等于结果可信,质量控制永远先行"。

qq_53529450的博客 115

从零搞定APP内嵌H5:安卓/iOS WebView通信与适配全攻略

本文全面解析了安卓与iOS平台下APP内嵌H5页面的WebView通信与适配问题,提供了从基础配置到高级交互的完整解决方案。涵盖WebView初始化、JSBridge实现、键盘布局适配、微信环境处理等核心场景,并分享性能优化与安全防护的实用技巧,帮助开发者高效解决混合开发中的常见挑战。

i0j1k2l3m的博客 673

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

Android商城开发(一)——一次活动页需求引发的危机感

本文出自:https://blog.youkuaiyun.com/DT235201314/article/details/81027990 一丶概述 本文讲述商城活动页需求解决过程,及一些个人想法(危机感),提供问题解决方法及核心代码。 二丶活动页开发必备技能 1.切图 2.js h5 3.Android webview 4.Android和js方法互调 三丶现状陈述 个人:会Android...

行动者&&职业化 1704

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

五大开源移动端自动化测试工具深度横评与选型指南

移动端自动化测试是软件工程中保障应用质量、提升测试效率的关键技术。其核心原理是通过脚本模拟用户操作,对Android和iOS应用进行功能验证与回归测试,从而替代重复性手动劳动。在技术价值层面,它能实现快速反馈、提升测试覆盖率,并成为持续集成与交付(CI/CD)流程中的重要环节。应用场景广泛,从初创团队的快速验证到大型互联网企业的全机型覆盖夜间回归,都离不开合适的自动化工具。本文聚焦于跨平台(Android/iOS)这一核心挑战,深入剖析了Appium、Espresso/XCUITest、Detox、Maes

weixin_34226706的博客 468

UniApp课程完整学习总结|零基础搞定多端跨平台开发(附避坑指南

UniApp跨平台开发学习总结(摘要) 本文系统梳理了UniApp开发的核心知识点与实战经验。UniApp基于Vue.js,支持一套代码编译至微信/支付宝/抖音小程序、H5、App等多端,显著提升开发效率。课程涵盖四大模块: 基础入门:环境搭建(HBuilderX)、项目结构、rpx自适应单位; 核心语法:Vue兼容组件(view/scroll-view等)、生命周期、路由传参、条件编译; 实战功能:网络请求封装、本地缓存、原生API调用(扫码/定位); 避坑指南:样式适配(优先rpx)、tabBar路由跳

makise_kurise_no的博客 174

Flutter零基础入门学习资料集合

Flutter学习资料,包含了Flutter学习社区、入门学习资料、进阶学习资料、性能优化、面试资料和书籍等资料

m0_67537062的博客 1481
上一篇: 带你认识ClassLoader
下一篇: Android自定义View简介及入门
江湖客陆仁甲
博客等级 码龄10年 179粉丝 98原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值