Apache Cordova Android原理应用实例详解

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

Apache Cordova Android原理应用实例详解

莱昂晨   2022-11-09 我要评论

前言

从原理到应用复盘一下自己做过的所有项目,希望能让我自己过两年仍然能看懂现在写的代码哈哈。在项目里我只负责了Android的开发包括插件开发和集成,ios没摸到,有机会也得自己玩下,但是这篇文章不会接触。

技术选型

现在Hybrid混合开发框架很多,Apche Cordova/React Native/Flutter等等等等。然而公司需求是2018年6月提的,Flutter 1.0在半年之后才出生,所以当时团队TL比较了RN与Cordova后基于以下几点选择了Cordova。

  • 学习成本低,项目周期比较紧,只有1个月就要发版本,团队内没有熟悉RN的成员,学习成本低开发时间短非常重要
  • 技术成熟,文档多不容易踩坑(当然现在RN也很成熟,但是当时RN的人确实没有很多,生态不成熟)

我们的应用跑在一台定制的Android终端,性能不是很好,在最后某些js动画场景里,以及一些js-native的调用里卡的不行,最后优化了下,效果也算还不错。这里建议如果对性能要求很高的项目,一定慎重考虑Cordova这种webview方案

项目最后选择了React + Mirrorx + Cordova的方案(当时有Dva, 但是由于Dva没有英文文档,而我们的项目是需要美国分团队共同维护的,所以选择了Mirrorx)

技术原理

架构图

本质上其实就是往app里面塞一个webview,通过file协议, 本地加载index.html

那么这里会有三个问题

  • 如何本地加载url对应的资源
  • webview如何使用js调用app原生api(JSBridge)
  • app原生api如何回调webview中的js
  • 多个plugin的情况下,cordova是如何通过cordova.exec(...’pluginName’...)定位到相应的plugin的 以下带着问题一个个进行解释

1. 如何本地加载url对应的资源

我们项目中通过eject暴露Webapck配置, 配置打包路径直接打进www文件夹。 通过Android的webview.loadUrl方法通过file协议load本地index.html。在Google inject中也可以看到对应的url. 对应的cordova代码如下

 public class CordovaViewTestActivity extends Activity implements CordovaInterface {
     CordovaWebView cwv;
     /* Called when the activity is first created. */
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
         cwv = (CordovaWebView) findViewById(R.id.tutorialView);
         cwv.loadUrl("file:///android_asset/www/index.html");
     }

2. webview如何使用js调用app原生api

2.1 通过addJavascriptInterface 及 @JavascriptInterface实现

2.2 通过WebClientChrome中的三个方法onJsAlert, onJsConfirm, onJsPrompt实现

当chromium webkit内核的webview调用window.alert, window.confirm, window.prompt方法时,对应的WebClientChrome的那三个方法同样会被执行

在cordova中会首先判断有没有设置navtiveApi

var nativeApi = this._cordovaNative || require('cordova/android/promptbasednativeapi');

在Android部分

var msgs = nativeApiProvider.get().exec(bridgeSecret, service, action, callbackId, argsJson);
// If argsJson was received by Java as null, try again with the PROMPT bridge mode.
// This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2.  See CB-2666.
if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && msgs === "@Null arguments.") {
    androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
    androidExec(success, fail, service, action, args);
    androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
} else if (msgs) {
    messagesFromNative.push(msgs);
    // Always process async to avoid exceptions messing up stack.
    nextTick(processMessages);
}

如果有nativeApi即设备支持@JavascriptInterface注解, 则走addJavascriptInterface,否则走onJsPrompt

3. app原生api如何回调webview中的js

js端生成callback function id,传给native, native需要回调js时,回调对应的callbackId给js

    private String callbackId; // 在js端生成,保存在native端
    private CordovaWebView webView;
    protected boolean finished; //这个callback是否结束,如果结束在js端(cordova.js)就会把这个callbackid从列表中删掉,否则这个callback将一直存在,也就是说明你可以用这个callbackContext一直和js保持通信

回调方式:

/** Uses webView.loadUrl("javascript:") to execute messages. */
public static class LoadUrlBridgeMode extends BridgeMode {
/** Uses webView.evaluateJavascript to execute messages. */
public static class EvalBridgeMode extends BridgeMode {
public static class OnlineEventsBridgeMode extends BridgeMode

4. 多个plugin的情况

多个plugin的情况下,cordova是如何通过cordova.exec(...’pluginName’...)定位到相应的plugin的

当Cordova框架启动时候,CordovaActivity类中的onCreate方法调用loadUrl方法。在第一次loadUrl方法时,就会去初始化PluginManager并加载plugin,PluginManager加载plugin,将plugin的Class名字保存到一个hashmap中,用service名字作为key值。当JS端通过JavascriptInterface接口的SystemExposedJsApi对象请求Android时,PluginManager会从hashmap中查找到plugin,如果该plugin还未实例化,利用java反射机制实例化该plugin,并执行plugin的execute方法。

关于踩到的坑

1. 打包路径配置问题

cordova build的时候会默认使用项目www目录作为资源目录,打包进assets中。 项目工期紧,直接用了cra创建的项目,我们使用eject,再修改打包路径(实际当时有其他插件可以暴露打包路径配置)。

// webpack.config.prod.js
module.exports = {
    ...
    output: {
        ...
        // The build folder.
        path: resolveApp('www'),
    }
}

2. success不回调问题

场景: 我们项目集成其他项目组的自研plugin的功能后,用户输入device ID和divice Name后,点击connect按钮,就可以通过mobile连接上其他项目组的硬件设备

前端调用test.connect(deviceID, deviceName, callback), 其中callback的逻辑是接收并判断java端传回的message。在连接上device后, java使用contextCallback.success(”success”)来进行回调 ,callback接收到message为success, 就更改mobile端我们前端的相应状态为连接成功

第一次连接之后,即java调用了contextCallback.success(“success”), 然后js中调用了callback(‘success’) ,项目页面显示已连接, 断开后,第二次再次调用contextCallback.success(“success”)无法正常连接,无法正常调用js这边的callback

原因:

finished一开始没有初始化,走下面的else,被赋值为true, 然后调用webView.sendResult,第二次调用就直接return了。

解决方案:keepCallback (当时官方文档没有提到callback只能调一次的问题,翻源码才看到)

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们