cordova 插件开发

题外话

为什么 Magento 主题的博客研究起 cordova 来了?因为博主希望解决掉商城的全平台问题。

cordova 是贡献给 Apache 后的开源项目,Phonegap 的核心引擎。cordova 通过 Webview 提供 js 和原生代码之间的桥接,这样就可以利用 JS 来调用原生的代码,实现相应的功能,比如调用相机,地理位置什么的。(这样的介绍好像没什么卵用,总之一句话,cordova 是做 APP 跨平台开发的)

博主 14 年的时候用 phonegap 做过一个 APP,当时前端框架用的 jquery mobile。在流畅性上,还是不太理想,但是这个锅个人认为大部分要由 jquery mobile 来背,(:D 当然还有博主水平不够的原因)。最后,权衡开发成本和流畅性,选择混合开发还是划算的。

cordova 是混合开发中非常成熟的方案了。虽然一直在讲 webview 的渲染速度慢,但是近些年手机的运行速度也大大提升了。博主最近下载了一款基于 cordova 的应用,体验了一下,速度是很可以的。

因为 cordova 的插件机制,可以让你调用原生去做事,所以这个速度的优化空间是比较大的。况且,我们还可以在原生中嵌入 cordova 的 webviews ,让他们互通。在某些场景下使用原生的,另一些场景下使用 cordova ,想想还是很吸引人的。

在技术的选择上,常常有太多的争论,本文且不讨论这些,重点记录 cordova Android 插件开发的一个入门 exercise 。

前提

安装了:
cordova
Node.js
Android SDK

然后通过 npm 安装 plugman

npm install -g plugman
plugman --version
1.4.1

目标

练习的目的主要是熟悉插件开发的过程。
插件将提供接受一个字符型参数的 echo 方法,然后在成功调用后通过 success 方法返回传入的字符。

创建插件

plugman create --name ModusEcho --plugin_id com-moduscreate-plugins-echo --plugin_version 0.0.1 --path modusechoplugin

ls -l modusechoplugin/ModusEcho

-rw-r--r--  1 simon  staff  372 Feb  3 12:32 plugin.xml
drwxr-xr-x  2 simon  staff   68 Feb  3 12:32 src
drwxr-xr-x  3 simon  staff  102 Feb  3 12:32 www

这样我们就在当前目录下创建了 modusechoplugin/ModusEcho 内部结构如上面所示。

plugin.xml 看起来是下面这样:

<?xml version='1.0' encoding='utf-8'?>
<plugin id="com-moduscreate-plugins-echo" version="0.0.1" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
    <name>ModusEcho</name>
    <js-module name="ModusEcho" src="www/ModusEcho.js">
        <clobbers target="cordova.plugins.ModusEcho" />
    </js-module>
</plugin>

关于 plugin.xml ,参考 Plugin.xmlidversionname 都是命令中指定的。

<clobbers target="cordova.plugins.ModusEcho" />

这里指定模块将被 export 到 window.cordova.plugins.ModusEcho 这里我们将他改成modusecho,这样就可以通过 modusecho 或者 window.modusecho 访问了,变成下面这样:

<?xml version='1.0' encoding='utf-8'?>
<plugin id="com-moduscreate-plugins-echo" version="0.0.1" 
xmlns="http://apache.org/cordova/ns/plugins/1.0" 
xmlns:android="http://schemas.android.com/apk/res/android">
  <name>ModusEcho</name>
  <js-module name="ModusEcho" src="www/ModusEcho.js">
    <clobbers target="modusecho" />
  </js-module>
</plugin>

然后来看 www/ModusEcho.js

var exec = require('cordova/exec');

exports.coolMethod = function(arg0, success, error) {
    exec(success, error, "ModusEcho", "coolMethod", [arg0]);
};

我们将他改成:

var exec = require('cordova/exec');

exports.echo = function(arg0, success, error) {
  exec(success, error, "ModusEcho", "echo", [arg0]);
};

The JavaScript interface provides the front-facing interface, making it perhaps the most important part of the plugin. You can structure your plugin’s JavaScript however you like, but you need to call cordova.exec to communicate with the native platform。

就是说这里通过 Cordova bridge 的 exec 方法来调用原生的代码。

创建 cordova 项目做测试用

这里我们先创建一个 cordova 项目来测试一下。(注意不要在当前创建的插件目录下)

cordova create testapp com.moduscreate.testapp TestApp

上面的语法是cordova create path [id [name [config]]] [options],参考 Cordova Command-line-interface (CLI) Reference

cd testapp
cordova platform add android
cordova plugin add <path/to/plugin>

eg:

cordova plugin add ../modusechoplugin/ModusEcho/

博主的目录是:
dir/testapp cordova 项目
dir/modusechoplugin/ModusEcho 插件项目

然后我们得到一个错误:

Error: Invalid Plugin! ..\..\cordova\modusechoplugin\ModusEcho needs a valid package.json

因为插件没有 package.json

下面切换到插件目录(存在 plugin.xml 的目录)

plugman createpackagejson .

然后就生成 package.json 了,这时候再回到 cordova 项目目录

cordova plugin add ../modusechoplugin/ModusEcho/

这次就成功了。

cordova run android

启用了模拟器,然后就看到经典的画面了。

现在我们可以发现 testapp/plugins/com-moduscreate-plugin-echo 下面有我们的插件了。

测试不使用 cordova exec

上面我们的 testapp/plugins/com-moduscreate-plugin-echo/www/ModusEcho.js 是这样的:

var exec = require('cordova/exec');

exports.coolMethod = function(arg0, success, error) {
  exec(success, error, "ModusEcho", "coolMethod", [arg0]);
};

先把他注释掉,然后改成下面这样:

// var exec = require('cordova/exec');

// exports.echo = function(arg0, success, error) {
//     exec(success, error, "ModusEcho", "echo", [arg0]);
// };
exports.echojs = function(arg0, success, error) {
  // Do something
  success(arg0);
};

然后修改 testapp\www\js\index.js 为:

onDeviceReady: function() {
        this.receivedEvent('deviceready');
        modusecho.echojs(
        'Hello Plugin',
        function(msg) {
          document.getElementsByTagName('h1')[0].innerHTML = msg;
        },
        function(err) {
          document.getElementsByTagName('h1')[0].innerHTML = err;
        }
      );
    },

然后

cordova run android

理论上原来的 Apache Cordova 应该被换成 Hello Plugin

也就是说,这里不使用 cordova 的 exec 也完全没有问题,重点是 export 了该方法。

但是实际上由于此处是直接在 cordova 中改写插件,testapp\platforms\android\assets\www\plugins\com-moduscreate-plugins-echo\www\ModusEcho.js 并没有发生变化,这是 cordova 自动生成的一个 js ,并且比原始的 js 多了

cordova.define("com-moduscreate-plugins-echo.ModusEcho", function(require, exports, module) {
...
...
});

所以,直接改写不太 ok ,目前还没找到相关的命令,此处还是更改原来的插件代码,然后在 cordova 项目下先移除,再次添加,这次就成功了。

cordova plugin rm com-moduscreate-plugins-echo
cordova plugin add ../modusechoplugin/ModusEcho/

module.exports

上面 www/ModusEcho.js 是这样的

exports.echojs = function(arg0, success, error) {
  // Do something
  success(arg0);
};

如果写成:

module.exports = {
    Base: {
        hello: function(arg0, success, error) {
            // Do something
            success(arg0);
        }
    }
};

那么我们就该这样引用:

modusecho.Base.hello(
        'Hello world',
        function(msg) {
          document.getElementsByTagName('h1')[0].innerHTML = msg;
        },
        function(err) {
          document.getElementsByTagName('h1')[0].innerHTML = err;
        });

cordova exec

这里还是将 www/ModusEcho.js 改成:

var exec = require('cordova/exec');

exports.echo = function(arg0, success, error) {
  exec(success, error, "ModusEcho", "echo", [arg0]);
};

cordova 的 exec 接收 5 个参数
function(winParam) {}: A success callback function. Assuming your exec call completes successfully, this function executes along with any parameters you pass to it.

function(error) {}: An error callback function. If the operation does not complete successfully, this function executes with an optional error parameter.

"service": The service name to call on the native side. This corresponds to a native class, for which more information is available in the native guides listed below.

"action": The action name to call on the native side. This generally corresponds to the native class method. See the native guides listed below.

[/* arguments */]: An array of arguments to pass into the native environment.

现在个 plugin.xml 加上如下代码:

<platform name="android">
  <config-file target="config.xml" parent="/*">
    <feature name="ModusEcho">
      <param name="android-package"
             value="com.moduscreate.plugin.ModusEcho"/>
    </feature>
  </config-file>
  <source-file src="src/android/com/moduscreate/plugin/ModusEcho.java" 
target-dir="src/com/moduscreate/plugin" />
</platform>

插件目录下的src/android/com/moduscreate/plugin/ModusEcho.java 将会被拷贝到 cordova 项目的 platforms\android\src\com\moduscreate\plugin 下,并且com.moduscreate.plugin.ModusEcho 被映射为 ModusEcho plugin service。(对应 exec 中的 service)

创建 ModusEcho.java

在插件目录创建 src/android/com/moduscreate/plugin/ModusEcho.java

package com.moduscreate.plugin;

import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONException;
import android.content.Context;
import android.widget.Toast;

public class ModusEcho extends CordovaPlugin {
  @Override
  public boolean execute(
    String action, 
    JSONArray args, 
    CallbackContext callbackContext
  ) throws JSONException {
    if ("echo".equals(action)) {
      echo(args.getString(0), callbackContext);
      return true;
    }

    return false;
  }

  private void echo(
    String msg, 
    CallbackContext callbackContext
  ) {
    if (msg == null || msg.length() == 0) {
      callbackContext.error("Empty message!");
    } else {
      Toast.makeText(
        webView.getContext(), 
        msg, 
        Toast.LENGTH_LONG
      ).show();
      callbackContext.success(msg);
    }
  }
}

修改项目的 index.js 为:

onDeviceReady: function() {
        this.receivedEvent('deviceready');
        modusecho.echo(
            'Plugin Ready!',
            function(msg) {
                document.getElementById('deviceready').querySelector('.received').innerHTML = msg;
            },
            function(err) {
                document.getElementById('deviceready').innerHTML = '<p class="event received">' + err + '</p>';
            }
        );
    },

然后

cordova plugin rm com-moduscreate-plugins-echo
cordova plugin add ../modusechoplugin/ModusEcho/
cordova run android

这样就可以看到:

参考文档

Plugin Authoring in Cordova 6 for iOS and Android

发表评论

电子邮件地址不会被公开。 必填项已用*标注