Cordova插件的开发、使用

前言

随着App的设计、开发和维护成本的水涨船高以及H5+JS制做UI的日益成熟,hybrid的App愈来愈成为不少开发者的选项之一。天然的,Apache的Cordova成为大部分开发者的首选。对于开发一个App来讲,一般没法仅仅经过HTML+CSS+JS来实现诸如访问本地的照片、获取定位信息等还须要使用原生API来实现;这时插件便有了用武之地,经过它,Javascript代码可以调用原生API,同时,原生API中的事件也能够经过插件来传递到Web控件中,这样就大大hybrid应用的功能。javascript

开发环境的准备

因为本人使用Mac开发,故仅贴上Mac上开发环境的创建过程css

1 安装nodejs和npmhtml

本人使用Homebrew(http://brew.sh/)安装,读者亦可到nodejs官网(https://nodejs.org)上下载安装包直接下载java

brew install nodejs npm

2 安装命令行工具node

sudo npm install -g cordova
sudo npm install -g ios-sim #用于从命令行启动iOS模拟器
sudo npm install -g plugman #插件管理工具

3 设置PATH以及ANDROID_HOME环境变量(用于编译Android应用)
android

PATH=$PATH:/path/to/sdk/tools
export PATH
ANDROID_HOME=/path/to/sdk
export ANDROID_HOME

4 安装XCode的Command Line Tools(仅针对Mac上开发iOS应用)ios

到XCode->Preferences中相应的设置页中安装git

插件的开发

首先,使用plugman来建立插件工程github

plugman create -name MyPlugin --plugin_id com.jxjx.www.plugins --plugin_version 0.1
cd MyPlugin/
plugman platform add --platform_name ios
plugman platform add --platform_name android

会产生两个插件源代码,分别为MyPlugin/src/android/MyPlugin.java和MyPlugin/src/ios/MyPlugin.mshell

再建立Cordova应用

cordova create PluginDemo "com.jxjx.www.myplugindemos" “MyPluginDemos"
cd PluginDemo/
cordova platform add ios
cordova platform add android

咱们html页面以下:

<!DOCTYPE html>
<!--
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
-->
<html>
    <head>
        <!--
        Customize this policy to fit your own app's needs. For more guidance, see:
            https://github.com/apache/cordova-plugin-whitelist/blob/master/README.md#content-security-policy
        Some notes:
            * gap: is required only on iOS (when using UIWebView) and is needed for JS->native communication
            * https://ssl.gstatic.com is required only on Android and is needed for TalkBack to function properly
            * Disables use of inline scripts in order to mitigate risk of XSS vulnerabilities. To change this:
                * Enable inline JS: add 'unsafe-inline' to default-src
        -->
        <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">
        <meta name="format-detection" content="telephone=no">
        <meta name="msapplication-tap-highlight" content="no">
        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
        <link rel="stylesheet" type="text/css" href="css/index.css">
        <title>Hello World</title>
    </head>
    <body>
        <div class="app">
            <h1>Apache Cordova</h1>
            <div id="deviceready" class="blink">
                <p class="event listening">Connecting to Device</p>
                <p class="event received">Device is Ready</p>
            </div>
        </div>
        <br />
	<button id="btnEcho">echo</button>
	<button id="btnDelayedEcho">delayed echo</button>
        <script type="text/javascript" src="cordova.js"></script>
        <script type="text/javascript" src="js/index.js"></script>
    </body>
</html>

咱们的index.js以下:

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
var app = {
    // Application Constructor
    initialize: function() {
        this.bindEvents();
    },
    // Bind Event Listeners
    //
    // Bind any events that are required on startup. Common events are:
    // 'load', 'deviceready', 'offline', and 'online'.
    bindEvents: function() {
        document.addEventListener('deviceready', this.onDeviceReady, false);
        document.getElementById("btnEcho").onclick = function() {
            cordova.exec(function(data){
              alert(data);
            },
            function(err) {
              alert("error: " + err);
            },
            "MyPlugin",
            "echo",
            ["hiiii"]
          );
        };
        document.getElementById("btnDelayedEcho").onclick = function() {
            cordova.exec(function(data){
              alert(data);
            },
            function(err) {
              alert("error: " + err);
            },
            "MyPlugin",
            "delayedEcho",
            ["hiiii"]
          );
        };
    },
    // deviceready Event Handler
    //
    // The scope of 'this' is the event. In order to call the 'receivedEvent'
    // function, we must explicitly call 'app.receivedEvent(...);'
    onDeviceReady: function() {
        app.receivedEvent('deviceready');
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        var parentElement = document.getElementById(id);
        var listeningElement = parentElement.querySelector('.listening');
        var receivedElement = parentElement.querySelector('.received');

        listeningElement.setAttribute('style', 'display:none;');
        receivedElement.setAttribute('style', 'display:block;');

        console.log('Received Event: ' + id);
    }
};

app.initialize();

插件是Cordova跟原生接口之间的桥梁,js经过以下接口调用原生接口:

cordova.exec(function(successParam) {}, 
    function(err) {}, 
    "service", 
    "action", 
    ["firstArgument", "secondArgument", 42, false]
    );

咱们的示例插件提供了两个接口,一个为echo,直接返回传入的参数,用以演示当即返回参数值的状况;另外一个为delayedEcho,调用后通过3秒后延时返回,用以演示原生程序经过事件方式通知js层的方法。

对于Android系统,其对应于从org.apache.cordova.CordovaPlugin继承的execute方法,咱们的代码(MyPlugin.java)以下:

package com.jxjx.www.plugins;

import java.util.Timer;  
import java.util.TimerTask;  
import android.os.Handler; 
import android.os.Message;

import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.PluginResult;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * This class echoes a string called from JavaScript.
 */
public class MyPlugin extends CordovaPlugin {
    private CallbackContext cbCtx;
    private Timer mTimer;
    private String paramSaved;

    private Handler mHandler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {
            mTimer.cancel();
            mTimer = null;
            PluginResult result = new PluginResult(PluginResult.Status.OK, "delayed echo: "+ paramSaved);
            result.setKeepCallback(false);
            if (cbCtx != null) {
                cbCtx.sendPluginResult(result);
                cbCtx = null;
            }
        }
        
    };

    private void setTimerTask() {  
        mTimer = new Timer();
        mTimer.schedule(new TimerTask() {  
            @Override  
            public void run() {  
                Message message = new Message();  
                message.what = 1;  
                mHandler.sendMessage(message);  
            }  
        }, 3000, 3000);  
    } 

    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        if (action.equals("echo")) {
            String message = args.getString(0);
            this.echoMethod(message, callbackContext);
            return true;
        } else if (action.equals("delayedEcho")) {
            this.cbCtx = callbackContext;
            paramSaved = args.getString(0);
            PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT);
            result.setKeepCallback(true);
            setTimerTask();
            callbackContext.sendPluginResult(result);
            return true;
        } 
        return false;
    }

    private void echoMethod(String message, CallbackContext callbackContext) {
        if (message != null && message.length() > 0) {
            callbackContext.success(message);
        } else {
            callbackContext.error("Expected one non-empty string argument.");
        }
    }
}

若是execute方法返回true,则exec输入的第一个回调函数被调用,返回参数经过一个org.apache.cordova.PluginResult参数传递;若是execute方法返回false,则exec输入的第二个回调函数被调用;

service参数为插件名;

exec的action对应于execute方法的action;

exec的第四个参数必须为一个数组,对应于execute的args参数;

execute方法的callbackContext参数为上下文;若是要将事件返回至js层,须要保存起来供后续调用,而且添加以下一行代码:

result.setKeepCallback(true);

对于iOS系统,其对应于CDVPlugin类的相应方法,咱们的代码以下:

/********* MyPlugin.m Cordova Plugin Implementation *******/

#import <Cordova/CDV.h>

@interface MyPlugin : CDVPlugin {
  // Member variables go here.
}

@property (nonatomic, strong) NSString *callbackId;
@property (nonatomic, strong) NSString *echoStr;

- (void)echo:(CDVInvokedUrlCommand*)command;
- (void)delayedEcho:(CDVInvokedUrlCommand*)command;
@end

@implementation MyPlugin

- (void)timeoutCb:(id)arg {
    NSTimer *timer = (NSTimer *)arg;
    NSString *echoStr = [NSString stringWithFormat:@"delayed echo: %@", timer.userInfo];
    CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:echoStr];
    [result setKeepCallbackAsBool:NO];
    [self.commandDelegate sendPluginResult:result callbackId:self.callbackId];

}

- (void)echo:(CDVInvokedUrlCommand*)command {
    CDVPluginResult *pluginResult = nil;
    NSString *echo = [command.arguments objectAtIndex:0];

    if (echo != nil && [echo length] > 0) {
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:echo];
    } else {
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
    }

    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

- (void)delayedEcho:(CDVInvokedUrlCommand*)command {
    CDVPluginResult* pluginResult = nil;
    NSString *echo = [command.arguments objectAtIndex:0];

    if (echo != nil && [echo length] > 0) {
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_NO_RESULT];
        [pluginResult setKeepCallbackAsBool:YES];
        self.callbackId = command.callbackId;
        self.echoStr = echo;
        [NSTimer scheduledTimerWithTimeInterval:3.0f target:self selector:@selector(timeoutCb:) userInfo:echo repeats:NO];
    } else {
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];

    }
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

@end

与Android平台不一样的是,action对应的是Objective-C类的方法名,其他彻底相似。

在iOS平台中若是要传递异步消息至js层,须要保存的是callbackId;

插件编写完成后,须要在App的根目录中使用plugman命令来添加插件

#若是修改了插件源码须要先移除原插件:cordova plugin remove com.jxjx.www.plugins
cordova plugin plugin add ../MyPlugin/
cordova run android
cordova run ios

界面的效果以下

最后,附上源码连接:http://pan.baidu.com/s/1hq0h0X2

相关文章
相关标签/搜索