目录 文章有点长,请注意保护您的眼睛,如若引起身体不适请自行关闭! 之前是定了个目标说是做到每周一更,很开心已经坚持了三周了,希望接下来能够再接再厉吧!今天抽空写个基础业务方面的——移动端混合开发基础。对于应用层来说,其实混合开发用的还是比较多的,相信你肯定也用过,很多为了快速开发快速迭代节省成本的业务,基本上都会采用这种方式去进行开发,今天就做个总结,整理一下这方面的基础知识点。 本篇代码地址:https://github.com/JArchie/JSBridgeDemo 这两类技术各自的特点通过混合开发可以把它们结合起来,比如对于原生开发Android一般使用的就是Java和Kotlin,iOS一般使用的就是Objective-C和Swift。原生开发的实际操作过程都是比较慢的,改动一行代码你就需要重新编译打包安装到硬件设备上面看效果,如果有问题又要重复这个过程,在你发布升级的时候,用户需要重新安装这个APP,所以推送升级也是个问题。而Web技术就可以克服这些缺点,比如你修改代码之后,刷新一下页面就可以立马看到修改后的结果,当它发布的时候,部署之后用户下次打开就可以看到最新的页面,所以它并不存在发包覆盖率的问题,把这两种技术结合起来,就可以各自做各自擅长的事情,这也是混合开发的意义所在,也因此来说混合开发只是一种模式。 其实上面也提到了一部分,这里做个总结性的概括: ①、微信公众号,通过JSSDK连接Native端和Web端 我们可以把微信看做是原生开发,微信公众号内部的网页则是Web开发,它们通过JSSDK连接起来,这样在做微信公众号的时候可以使用Web技术发布更新,同时可以利用JSSDK使用到微信提供的原生能力。 ②、微信小程序,通过内置框架连接Native段和Web端 微信小程序相比较于微信公众号则更进一步,它相当于是将JSSDK和Web技术内置整合了一套框架,内部会将这些调用和渲染连接到Native端,使用它的框架编写程序,它自身会做桥接的工作。 ①、更好的使用第三方平台 比如我们要开发微信小程序或者在某个Web平台上开发一些插件,我们就需要了解混合开发的原理和机制,这样在使用第三方平台提供的接口调用时若发生某些特定的行为,可以深刻的理解生命周期和调用的链路,从而能够更好的使用第三方平台。 ②、更灵活的技术方案选型 比如领导让你开发一款新的移动应用时,如果你了解混合开发,那么你可以选择纯原生开发,也可以通过原生结合Web技术进行混合开发,技术选型更加灵活。 ③、具备搭建平台和输出服务的能力 当你了解混合开发之后,你可以搭建一个类似微信公众号或微信小程序这样的平台,可以把原生APP提供各种接口能力并输出成JSSDK。 由上图可以看出,上面是Web端,下面是Native端,JSBridge在中间起了一个桥梁作用,透过JSBridge,Web端可以直接调用Native端的Java接口,Native端也可以调用Web端提供的JavaScript接口,从而可以实现Web端和Native端之间的双向通信。可以把这种模式和客户端/服务器这种C/S模式作一个对比,把Web端比作客户端,Native端比作Server端,在Web端调用Native端接口时和客户端向Server端发出一个请求类似,其中JSBridge在这个过程充当的就是HTTP协议的角色。 在说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页面,布局代码如下: 然后在Activity页面中进行数据初始化操作: 接着把它运行一下,看下结果: 到了这里,准备工作就已经做完了,接下来就来说JSBridge的几种用法了! Web端调用Native端的实现方式有两种:①、拦截WebView请求的URL Schema;②、向WebView注入JS API 原生端调用Web端的实现方式只有一种:直接执行js代码去调用web端代码 3.2.1、原理及特点简介 上面的这个例子中自定义的Schema是以jsbridge开头,后面的showToast是调用的原生方法名,问号后面是需要传给这个方法的参数对,那么它是怎么实现拦截来调用原生方法的呢?它的原理这里用一张图片来说明: 原生端的WebView加载Web网页之后,Web网页中发出的所有请求都会经过WebView组件,所以原生端可以去重写WebView组件里面的方法从而拦截Web网页中的请求,对请求的地址作出判断,如果它是符合自定义jsbridge的格式,就进行解析,解析之后拿到对应的方法名及相关参数,去调用对应的原生方法,如果不是自定义的URL Schema,比如它是一个http协议,在正常的拦截之后就进行转发请求真正的服务。这个过程Web端虽然没有真正的调用Native端的方法,但它间接实现了调用的过程,从逻辑上来看就是Web端调用了原生端的方法,这就是它的实现机制。 3.2.2、代码实战 (一)、原生端调用Web端弹窗展示内容 在写代码之前先来理一下业务逻辑,我们需要实现的效果是:在原生端输入内容,然后点击按钮调用Web端弹窗展示。 OK,按照这个顺序一步一步来写,首先要给原生的Webview设置支持js脚本: 接着封装一个方法,将原生端输入的内容传入,然后执行一段js代码,调用Web端弹窗展示这个内容: 添加点击事件,将输入框的内容传入该方法: 在Web端的showWebDialog方法中调用window展示: 这样我们就完成了,来看下效果,如下图所示: (二)、Web端调用原生端弹窗展示内容 这次的业务逻辑是反向操作,在Web端输入内容,点击Web端按钮调用原生端的弹窗展示这个内容。 首先,在Web端给文档添加一个监听事件,在DOM加载完成之后获取对应的DOM元素,然后给按钮添加一个点击事件,拿到输入框的值,接着封装一个方法,将获取的输入框的值传入,在方法内部实现调用原生端的弹窗,这里就是通过自定义URL Schema的方式去实现,具体的代码如下: 然后我们在原生端自定义WebChromeClient拦截Alert方法,通过解析URL Schema获取Web端输入的值: 然后再封装一个显示原生弹窗的方法showNativeDialog: 这样也就完成了,来看下效果: 3.3.1、原理及特点简介 这种方式相比较于自定义URL Schema的方式更加直观,从下图中也能够看出: APP可以将原生的Java接口注入到WebView中,在WebView中暴露出一个JS对象,JS对象方法名跟原生的接口方法名是一一对应的,Web端就可以直接引用这个暴露的全局JS对象,通过这个对象调用到原生端的方法。 3.3.2、代码实战 原生端调用Web端的代码和上面的一样不用做任何的改动,因为它就一种实现方式,直接执行js脚本。 重点来看Web端调用原生端的这块业务,这里使用JS注入API的方式来实现。首先来看下原生端如何修改?我们这里使用WebView的addJavaScriptInterface方法暴露全局的js对象,需要传入一个js对象和需要暴露的js对象的方法名,所以这里写一个内部类,就是需要暴露的js对象及相关的方法: 然后看Web端,这里给文档添加事件监听,给按钮添加点击事件,获取输入框的值这一部分不用改动,需要改动的是我们封装的调用原生弹窗的方法: 来看下运行的效果,如下图所示: 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端输入: 然后在js中保存这个按钮的引用,并给这个按钮添加一个点击事件,在点击事件内部获取web端的输入并用原生弹窗展示: 这里封装了一个JSSDK,这里创建创建一个回调id,并使用Map将其保存,每次请求都产生一个新的回调id,并切提供两个支持回调的方法:获取原生输入框的值、接收原生消息返回的值: 然后在Native这边新增一个获取输入框值的方法,并调用web端方法,这里同样的就是构造js代码: 这样就写完了,来看下运行效果: (二)、Native端展示Web端输入框内容 首先在原生端页面上添加一个按钮mShowBtn2,绑定控件并添加点击事件,代码都很简单就不贴了,后面大家自己看源码吧。 然后同样的封装一个NativeSDK,内部提供两个方法:获取Web端输入框的值并传入一个回调接口,接收Web消息回传的值: 然后在NativeBridge中添加一个接收web端回传值的方法: 接着同样定义一个显示原生窗口的方法: 然后实例化我们之前定义的Native SDK,点击按钮通过sdk调用对应的方法,展示弹窗: 最后修改web端,在JSSDK中定义getWebEditTextValue方法,将输入框的值通过NativeBridge的receiveMessage方法传回: 来看下运行结果: 3.5.1、JSBridge开源库介绍 首先我们学习JSBridge应当掌握它的实现原理,具备造轮子的能力,但是我们应该避免重复造轮子,尽量复用开源社区已有的实现,并且这些开源库都是经过大量的实践验证目前已经很稳定了,对于JSBridge来说有哪些优秀的开元实现呢? 掌握这两类实现方式基本上可以应对大部分的场景了,大家可以根据自己的需要去选择,这里我选择使用第二种方式来介绍它的用法,接下来我会使用DSBridge来重写我们上面的Demo,下面给大家看一下DSBridge的相关介绍: 3.5.2、DSBridge重写Demo (一)、Web端展示Native端输入框内容 首先来看下Web端的实现:根据文档先要引入dsbridge.js,引入之后会在全局暴露一个dsBridge的全局变量,通过这个变量可以调用一些原生提供的方法,这里通过dsBridge.call方法去调用原生的getNativeEditTextValue方法,获取到输入框的值之后会得到一个回调,在回调内部通过Web弹窗将获取的值显示出来: 原生端的DWebView(DSBridge中提供的,我们需要使用这个WebView)中提供了一个注册原生接口的方法addJavascriptObject,然后提供一个自定义的JSAPI的类,类中统一管理提供给webview的方法: 来看下运行结果: (二)、Native端展示Web端输入框内容 首先Native端在按钮的点击事件触发时,通过DWebView调用callHandler方法,这个方法有三个参数分别是:要调用的Web端的api名称、传递的参数、结果回调,这里定义web端方法名为getWebEditTextValue,在回调中调用显示原生弹窗的方法: 然后Web端通过dsBridge调用register去注册一个web方法给原生端调用,并且可以将返回值返回给原生端: 来看下运行结果: OK,写到这里关于JSBridge相关的知识点就写完了,从上面的实现过程相信大家也能看出,使用开源库来实现JSBridge代码还是相当简洁的,内部都是做了高度封装,我们只需要简单的调用几个API就可以实现我们开始一大堆代码才能完成的效果,所以实际开发中还是推荐大家使用开源库来实现。 文章的最后一部分通过DSBridge这个开源框架来实现一个简单的Hybrid APP,关于DSBridge的相关用法上面也介绍过了,更加详细的用法请移步至官方文档:https://github.com/wendux/DSBridge-Android. 因为是一个综合案例,所以具体的代码我就不详细写了,因为用到的技术点上面也都说过了,这里就说一下具体的需求、实现思路以及实现效果,具体的源码我也已经放到github上面了,有需要的请自提! 功能需求: 实现思路: 需求①:首先在原生端通过addJavascriptObject注册一个原生接口,并提供一个JSAPI类,类的内部提供requestHttp()方法,同时将结果返回给Web端;在Web端添加所需的html元素,并给按钮添加点击事件,通过dsBridge.call调用原生端封装的发送http请求的方法比如就叫requestHttp(),拿到的返回值放到对应的html标签上展示即可。 需求②:首先在页面标题栏添加一个菜单栏就叫换肤,点击换肤调用换肤方法实现换肤效果。对于状态栏、标题栏、导航栏这些直接调用原生API设置颜色值改变对应的颜色,对于Web页面使用webview.callHandler传入web需要定义的方法名及参数,在web端通过dsBridge.register注册原生端需要的方法,在方法内部实现web页面背景色的更改。 实现效果: 如下图所示,左图是需求①的效果,右图是需求②的效果: 写到这里很是疲惫,总结的不好有点惭愧,没啥说的下期再会!
写在前面
一、混合开发介绍
1.1、什么是混合开发?
1.2、混合开发优缺点
1.3、混合开发应用场景
1.4、了解混合开发的意义
二、混合开发的核心技术
2.1、混合开发核心技术——JSBridge
2.2、混合开发主流技术框架
2.3、JSBridge实现原理
三、JSBridge的实现方式
3.1、创建Native和Web工程
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="https://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>
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("https://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("https://192.168.0.102:8080/?timestamp" + new Date().getTime()); break; } } }
3.2、拦截URL Schema
mWebview.getSettings().setJavaScriptEnabled(true);
//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;
<script> window.showWebDialog = content => window.alert(content); </script>
<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拦截自定义的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; } }
//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
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");
<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
<div> <button id="showBtn2">获取Native输入</button> </div>
const showBtn2 = document.querySelector('#showBtn2'); showBtn2.addEventListener('click', e=> { window.JSSDK.getNativeEdittextValue(value => window.alert('Native端输入值:'+value)) })
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) } } }
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); } }); } }
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); } } }
@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(); }
case R.id.mShowBtn2: nativeSDK.getWebEditTextValue(new Callback() { @Override public void invoke(String value) { showNativeDialog2("Web 输入值:" + value); } }); break;
getWebEditTextValue(callbackId){ const editText = document.querySelector('#editText'); const value = editText.value NativeBridge.receiveMessage(callbackId,value) }
3.5、JSBridge的开源实现
<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>
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); } }
mWebview.callHandler("getWebEditTextValue", null, new OnReturnValue<String>() { @Override public void onValue(String retValue) { showNativeDialog("Web端输入值:" + retValue); } });
dsBridge.register('getWebEditTextValue', () => { const editText = document.querySelector('#editText') return editText.value })
四、实战案例——开发一个简单的混合APP
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算