Help us understand the problem. What is going on with this article?

Cordova PluginのJavaScript部分の実装

イントロダクション

Cordova Pluginを実装する場合のJavaScript部分の記述方法について、まとめました。

Cordovaプラグインについての記事

plugin.xmlへの記述法

Pluginとして組み込みたいjsファイルを指定して、 js-moduleタグで記述します。
例えば、cordova-plugin-cameraであれば次のようになっています。

plugin.xml
    <js-module src="www/CameraConstants.js" name="Camera">
        <clobbers target="Camera" />
    </js-module>

    <js-module src="www/CameraPopoverOptions.js" name="CameraPopoverOptions">
        <clobbers target="CameraPopoverOptions" />
    </js-module>

    <js-module src="www/Camera.js" name="camera">
        <clobbers target="navigator.camera" />
    </js-module>

ここでは、3つのjsファイルを組み込んでいます。このように、一つのプラグインで複数のjsファイルを読み込むことも出来ますが、通常は、組み込むのjsファイルは1つだけあれば十分です。

  • src属性で、組み込むJSファイルへのパスを記述します。

  • name属性は、そのjs-moduleのIDとして利用されます。ただし、アプリの開発者がjs-moduleのIDを直接参照することはないので、特に意識する必要はありません。js-moduleのIDは、「プラグインのID」.「name属性で指定した文字列」となります。例えば、上記のカメラプラグインの例であれば、3つのjs-moduleについて、IDはそれぞれ

    • cordova-plugin-camera.Camera
    • cordova-plugin-camera.CameraPopoverOptions
    • cordova-plugin-camera.camera

となります。prepare後に生成される www/cordova-plugins.jsの中で、上記が定義され、cordova.jsから読み込まれます。

  • js-module内で設定しているclobbersは重要です。srcで読み込まれたJSモジュール(後述)は、このclobbersのtargetで設定したオブジェクトとして参照出来るようになります。これについては、次節で詳しく説明します。

js-moduleとclobbersの関係

js-moduleのsrcタグで指定するモジュール(jsファイル)は、Common-JS形式のモジュールと似ていて、次のような形式になります。

module.exports = [オブジェクト]

例えば、上記のカメラプラグインの3番目のjs-moduleを見てみると、次のような形式になっています。

var cameraExport = {};
cameraExport.getPicture = function (successCallback, errorCallback, options) {
...
}
module.exports = cameraExport;

そして、module.exportsしたオブジェクトは、clobbersのtargetで設定したオブジェクトとして参照出来るようになるため、このサンプルでは、navigator.cameraが、cameraExportとなり、結局、次のように呼び出せます。

navigator.camera.getPicture( ... );

clobber以外のオプション

js-moduleタグの中に記述するのは、clobber以外に、mergesとrunsがあります。

merges

clobbersとほぼ同様で、次のようにtarget属性と共に記述して使います。

    <js-module src="www/CameraConstants.js" name="Camera">
        <merges target="window.cordova" />
    </js-module>

この例では、window.cordovaオブジェクトに組み込まれて参照出来るようになります。clobbersとの違いは、すでに存在しているwindow.cordovaオブジェクトに、このモジュールで定義しているgetPictureなどのプロパティを追加するところです。

runs

属性を指定せずに次のように記述します。

    <js-module src="www/CameraConstants.js" name="Camera">
        <runs />
    </js-module>

runsを指定した場合、モジュールのプロパティを参照するためのオブジェクトが用意されません。cordova.requireを使って、自前で参照することになります。

モジュールの書き方

ネイティブコードの呼び出し

ネイティブコードの呼び出しは、cordova.execを使います。次のような形式です。

    cordova.exec(success, failure, '【プラグイン(のフィーチャー)名】', '【メソッド名】', [【引数】]);

プラグイン(のフィーチャー)名は、plugin.xmlのconfig-fileタグの中のfeatureタグのname属性でつけた値です。例えば、Androidで次のようになっていたとすると、

    <config-file target="res/xml/config.xml" parent="/*">
      <feature name="HelloWorldPlugin">
        <param name="android-package" value="com.example.plugin.HelloWorldPlugin"/>
      </feature>
    </config-file>

HelloWorldPluginというのが、プラグイン(のフィーチャー)名になります。

メソッド名は、呼び出すメソッド名です。Androidの場合、ネイティブ側のプラグインクラスのexecuteメソッドの第一引数であるaction変数に、メソッド名が引き渡されます。コードで書くと、次のようになります。

public class HelloWorldPlugin extends CordovaPlugin {
  ...
    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
       // このactionに、メソッド名が格納される。
       // argsに、引数が格納される。
       ...
    }
}

iOSの場合は、Androidと同様、plugin.xmlのconfig-fileタグの中のfeatureタグのname属性でつけた値です。例えば、

        <config-file target="config.xml" parent="/*">
            <feature name="HelloWorldPlugin">
                <param name="ios-package" value="CDVHelloWorldPlugin"/>
            </feature>
        </config-file>

HelloWorldPluginというのが、プラグイン(のフィーチャー)名になります。

メソッド名は、iOSの場合、ネイティブ側のメソッドそのものになります。上記の例では、CDVHelloWorldPluginクラスのメソッドが直接呼ばれるので、例えば

    cordova.exec(success, failure, '【プラグイン(のフィーチャー)名】', 'myfunc', [【引数】]);

で呼び出すと、

@implementation CDVHelloWorldPlugin

...
- (void) myfunc:(CDVInvokedUrlCommand*) command
{
  // command.argumentsに、引数が格納される
  ...
}

として定義されたmyfuncメソッドが直接呼ばれます。このとき、myfuncメソッド自体の引数はかならず、CDVInvokedUrlCommand *になります。jsから渡された引数は、command.argumentsで取得します。

argsCheck

TODO

channelとpub/sub

channelを使うことで、pub/subを実現することが出来ます。例えば、cordovaが利用可能になった時に何か実行させたい場合、次のように実装します。

var channel = require('cordova/channel');
channel.onCordovaReady.subscribe(function() {
 // 起動時の処理
});

onCordovaReadyイベントが発火した時に実行する処理を、subscribeメソッドで定義しています。

このように定義すると、onCordovaReadyイベントが発火したときに、定義した処理が自動的に実行されます。

後述するように、onCordovaReadyイベントはスティッキーイベントであるため、subscribeを定義する以前にonCordovaReadyイベントがすでに発火済みだった場合、subscribeするとすぐにその処理が実行されます。

channelで定義されているイベントには、他にも次のようなものがあります。

  • onDOMContentLoaded
  • onNativeReady
  • onCordovaReady
  • onPluginsReady
  • onDeviceReady
  • onResume
  • onPause

onNativeReady, onPluginsReadyが発火した後、onCordovaReadyがイベントが発火します。
また、onDOMContentLoadedonCordovaReadyが共に発火すると、onDeviceReadyイベントが発火します。(他にも、waitForInitializationで登録されたイベントがあると、それらすべてが発火後に、onDeviceReady`イベントが発火します)

少し紛らわしいですが、'onCordovaReadyイベントと、onDeviceReady`イベントは別のイベントであることに注意してください。

なお、onDOMContentLoadedイベントと、最後にReadyが付いているイベントは、スティッキーイベントであり、一度そのイベントが発火すると、以後は発火したままの状態となります。(発火されたままのイベントに対しては、イベント発火後にsubscribeするとただちに実行されます。スティッキーでないイベントは、発火後にsubscribeしても実行されません)

詳しくは、https://github.com/apache/cordova-js/blob/c75e8059114255d1dbae1ede398e6626708ee9f3/src/common/channel.js を参照してください。

channelイベントを自前で作成する

cordovaでデフォルトで定義されている上記のイベント以外に、自前で新規にイベントを作成することも出来ます。
例えば、cordova-plugin-mediaプラグインでは、次のようにonMediaPluginReadyイベントを作成し、利用しています。(このコードは、プラットフォームがandroid, amazon-fireos, windowsphoneときのみ読み込まれますので、iOSでは実行されません)

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

    channel.createSticky('onMediaPluginReady');
    channel.waitForInitialization('onMediaPluginReady');

    channel.onCordovaReady.subscribe(function() {
        exec(onMessageFromNative, undefined, 'Media', 'messageChannel', []);
        channel.initializationComplete('onMediaPluginReady');
});

createStickyメソッドで、スティッキーなイベント(一度発火したら、発火したままの状態になる)が作られます。(スティッキーでないイベントを作る場合は、createメソッドを使います)

waitForInitializationで、onMediaPluginReadyイベントを、初期化待ちイベントとして追加します。初期化待ちイベントがすべて発火しないと、cordovaのdevicereadyイベントは発火しません。すなわち、このように実装することで、deviceready時にonMediaPluginReadyイベントはすでに発火済みであることを保証することが出来ます。

最後の行は、onCordovaReadyイベントが発火後に、ネイティブコードの呼び出し

    exec(onMessageFromNative, undefined, 'Media', 'messageChannel', []);

が行われ(非同期なので、呼び出しのみ。Nativeからの呼び出しを受け付けるonMessageFromNativeメソッドを登録している)、その後、

    channel.initializationComplete('onMediaPluginReady');
});

により、onMediaPluginReadyイベントを発火しています。
(ここではこのようにしましたが、これだとexecの処理が完了するonMediaPluginReady イベントが発火してしまうので、外部にメッセージを送っておくりっぱなし(レスポンスを気にしない)ような処理はいいのですが、execの処理の結果を使ったこともしておきたいのであれば、onMessageFromNative関数の内部でonMediaPluginReadyイベントを発火してください)

モジュールの組み込まれ方と開発作業

プラットフォームごとに違いますが、基本的な構造は同じなので、iOS (cordova-ios 4.5.4)の場合を例にとって説明します。

プラグインのjs-moduleで設定したコードは、plugin addもしくはplatform addを実行した時点で、platforms/ios/platform_www/plugins/プラグイン名/wwwの下にコピーされます。このファイルは、cordova prepareを実行しても変更されないので、開発時にはこのファイルを編集すると良いでしょう。

platforms
 ┗ ios
    ┗ platform_www
       ┗ plugins
          ┗ プラグイン名
             ┗ www
                ┗ モジュールのファイル名.js

なお、このファイルは厳密にはjs-moduleのsrcで指定しているファイルそのものではなく、一行目と最終行にコードが追加され、次のようになっています。

cordova.define("[モジュールのID]", function(require, exports, module) {

 // js-moduleのsrcで指定したファイルの中身

});

そのため、開発時にこのコードを編集し、完成後にプラグイン側にフィードバックする場合(js-moduleで設定したファイルとして保存する場合)、一行目と最終行目を除いておく必要があるので注意です。

cordova prepareの実行

cordova prepareを実行すると、platforms/ios/platform_www/plugins/プラグイン名/www配下のコードは、platforms/ios/www/plugins/プラグイン名/www配下にコピーされます。実際にアプリに組み込まれるのはこちらのファイルです。

platforms
 ┗ ios
    ┗ www
       ┗ plugins
          ┗ プラグイン名
             ┗ www
                ┗ モジュールのファイル名.js

こちらは、cordova prepareするたびに上書きされてしまうので、デバッグ用やテスト用のコードなど、ちょっとだけ確認したいをここに記述しても良いかもしれません。

KNaito
I love JavaScript, Html, PHP, Python, Java, Ruby, Perl, C, C++, Objective-C and Haskell. My stack overflow address is http://stackoverflow.com/users/3535002/knaito
http://d.hatena.ne.jp/knight_9999/
asial
開発中に体験した技術についての情報をつづります
http://www.asial.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away