插件的意义
plugin(全文都叫插件)是一个注入代码的包,它允许app呈现的Cordova
webview与其运行的native平台进行通信。插件可以访问基于Web的app通常不可用的设备和平台功能。所有主要的Cordova
API功能都可以实现为插件,还有许多其他功能可用于实现诸如条码扫描器,NFC通信或定制日历界面等功能。您可以在Cordova插件搜索页面上搜索可用的插件。
插件包含一个单一的JavaScript界面,以及每个受支持平台的相应native代码库。本质上,这隐藏了一个常见的JavaScript界面背后的各种native代码实现。
介绍一个插件
以cordova-plugin-http为例,简单介绍它是如何实现js(ts)与native代码(oc、swift)进行交互的。
进入ionic官网/http插件,看介绍安装。也可以直接执行命令:
npm install --save @ionic-native/http
ionic cordova plugin add cordova-plugin-http
执行完这两句后,插件就可以正常使用了,js就可以调用原生的方法了,具体做了什么?
npm部分
首先看第一句命令,主要是在项目目录里的node_modules/@ionic-native下载了一个名叫http的npm包,包内容如下图
打开index.d.ts,可以看到声明了很多方法,以post方法为例。在ts中调用的this.http.post方法就是在这个文件定义的。
然后打开index.js,给HTTP添加了一个方法,入参和声明的方法一样,使用了es6的prototype语法。
而index.js中的这段代码,则指示了如何寻找对应的插件:
HTTP = __decorate([
Plugin({
pluginName: 'HTTP',
plugin: 'cordova-plugin-http',
pluginRef: 'cordovaHTTP',
repo: 'https://github.com/wymsee/cordova-HTTP',
platforms: ['Android', 'iOS']
})
], HTTP);
其中的“pluginName”写npm包中使用的类名,“plugin”写插件的名称,每个插件都有个固定的名称,“pluginRef”写插件目录中www里的js文件的名称(下文讲到),“repo”写如果找不到去哪个git地址找,“platforms”写适用的平台。
plugin部分
然后我们看一下第二句命令,主要是在项目项目目录里的plugins中添加了“cordova-plugin-http”文件夹,如下图
先看配置文件plugin.xml
id="cordova-plugin-http"
version="1.2.0">
<name>SSL Pinning</name>
<description>
Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning
</description>
<dependency id="cordova-plugin-file" version=">=2.0.0" />
<js-module src="www/cordovaHTTP.js" name="CordovaHttpPlugin">
<clobbers target="CordovaHttpPlugin" />
</js-module>
<platform name="ios">
<config-file target="config.xml" parent="/*">
<feature name="CordovaHttpPlugin">
<param name="ios-package" value="CordovaHttpPlugin"/>
</feature>
</config-file>
<header-file src="src/ios/CordovaHttpPlugin.h" />
<source-file src="src/ios/CordovaHttpPlugin.m" />
<header-file src="src/ios/TextResponseSerializer.h" />
<source-file src="src/ios/TextResponseSerializer.m" />
- id: 插件的标识,即发布到 plugins.cordova.io 的 ID
- name:插件的名称
- description:描述信息
- js-module:对应我们的 javascript 文件,src 属性指向 www/cordovaHTTP.js
- platform:支持的平台,这里仅仅截取了iOS
接下来,cordovaHTTP.js的内容是
var exec = require('cordova/exec');
var http = {
headers: {},
post: function(url, params, headers, success, failure) {
headers = mergeHeaders(this.headers, headers);
return exec(success, failure, "CordovaHttpPlugin", "post", [url, params, headers]);
},
};
module.exports = http;
仅截取了post方法。当ts部分调用插件的post方法时,实际上调用了cordova中的exec方法,将成功回调、错误回调、插件中的类名、插件中的方法名、ts传过来的参数的数组作为参数。用过 Nodejs 或者了解过 AMD、CMD 的话,一定会觉得很熟悉。简单的说,require 用于引入我们的类,exports 用于导出我们的方法。这里对外公开了 post 方法,以便我们在 app 中可以用到。
最后看CordovaHttpPlugin.m的内容
@interface CordovaHttpPlugin : CDVPlugin
@implementation CordovaHttpPlugin {
- (void)post:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *parameters = [command.arguments objectAtIndex:1];
NSDictionary *headers = [command.arguments objectAtIndex:2];
[self setRequestHeaders: headers forManager: manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[manager POST:url parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self setResults: dictionary withTask: task];
[dictionary setObject:responseObject forKey:@"data"];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
} failure:^(NSURLSessionTask *task, NSError *error) {
}
}
继承了CDVPlugin类,使用command.arguments拿到从ts传过来的参数,然后通过导入的AFN库,使用AFHTTPSessionManager发送post请求,接下来就和iOS原生一样了;拿到响应以后,使用CDVPluginResult将responseObject包装起来,然后用commandDelegate的sendPluginResult方法将原生拿到的字典传回给ts。
开始制作一个插件
安装plugman
sudo npm install -g plugman
生成插件
- 进入ionic项目
cd project
- 创建插件
plugman create --name <pluginname> --plugin_id <pluginid> --plugin_version 0.0.1
例如:
plugman create --name XXXXX --plugin_id cordova-plugin-xxxxx --plugin_version 0.0.1
此时生成的插件放在项目根目录,可以通过在上面的命令中添加path选项修改路径,也可以在生成之后再拖走。
初始情况下src是空的,根据自己的需求,将插件需要的原生代码拷进来。
- 添加平台
cd XXXXX
plugman platform add --platform_name ios
- 使用git
在代码托管网站(建议github)创建仓库,将XXXXX目录放入仓库。最好在目录中添加一个readme文件,帮助其他人使用你的插件。如果不会可以看github的步骤。再不会的话看我其他的博客哦,有仔细讲git。
git commit -m -a "创建项目xxxxx"
git push
- 添加npm配置文件
想要发表,能让别人用,就得配置package.json
sudo plugman createpackagejson .
然后终端会提供提问式的交互,按顺序进行即可,或者一直按回车也行,反正生成出来也可以修改;package.json生成以后,打开做修改:
name值最好改成cordova-plugin-xxxxx这种形式,比较好看,跟cordova官网的一样,如果没有生成”repository”属性,要添加下列代码:
"repository": {
"type": "git",
"url": "git+https://github.com/xxx/cordova-vxxxxx.git"
},
其他属性如果有需要,可以参照别人写的plugin的package文件修改。
- 登录npm并发表
npm adduser
然后也是提问式交互,按顺序进行后,会自动登录,如果已经注册过账号
登录npm:
npm login
检查是否登录成功:
npm who am i
发表:
sudo npm publish .
这个时候可能出现no_perms Private mode enable, only admin can publish this module,八成是因为使用了淘宝镜像,设置会原来的就可以
npm config set registry http://registry.npmjs.org
成功发表后,你就可以通过
ionic cordova plugin add cordova-plugin-xxxxx --save
来安装你自己写的插件了。当然这个插件还不能投入使用,还要继续做ts的部分
生成npm包
此处是借用了@ionic-native的代码来生成我们需要的插件的代码,属于借鸡生蛋,这个方法应该不是最好的,但是我目前没有找到更好的方法。
详见Ionic Native插件开发指南
- 克隆ionic-native仓库
git clone https://github.com/ionic-team/ionic-native.git
cd ionic-native
- 使用gulp创建一个插件封装器
sudo npm install gulp@latest -g
gulp plugin:create -n PluginName
然后就会在src/@ionic-native/plugins里的对应的插件里,生成一个index.ts文件,文件的核心代码如下:
import { Injectable } from '@angular/core';
import { Plugin, Cordova, IonicNativePlugin } from '@ionic-native/core';
@Plugin({
pluginName: 'XXXXX',
plugin: 'cordova-plugin-xxxxx', // npm package name, example: cordova-plugin-camera
pluginRef: 'XXXXX', // the variable reference to call the plugin, example: navigator.geolocation
repo: '', // the github repository URL for the plugin
platforms: ['iOS'] // Array of platforms supported, example: ['Android', 'iOS']
})
export class XXXXX extends IonicNativePlugin {
doSomething(): Promise<any> {
return;
}
}
上述这些代码看起来比较面熟,基本都在第二部分介绍http插件见过,我简单介绍一下这个文件做了什么。
首先,我们要创建一个代表我们的插件的类,在这种情况下是XXXXX。
export class XXXXX extends IonicNativePlugin {
}
接下来,我们需要指定一些关于这个插件的信息。Ionic Native是用TypeScript编写的,它使用了一个名为decorators的功能。长话短说,装饰器允许我们使用声明性语法修改或添加信息到类和属性。
例如,@Plugin装饰器将关于插件的信息添加到我们的XXXXX类中:
plugin: 'cordova-plugin-xxxxx'
pluginRef: 'XXXXX'
这里plugin是npm上的插件包的名称,在调用时使用ionic cordova plugin add
pluginRef指的window是底层的Cordova插件通常暴露在哪里。例如,在使用http插件时,通常你会调用cordovaHTTP.js,而我们的插件的plugin里的js名字叫XXXXX,所以pluginRef在这种情况下写XXXXX。
- 生成插件所需的包
npm run build
运行后会创建一个dist目录,该dist目录将包含一个子目录,@ionic-native其中包含所有包。这个时候把创建好的这个包拷贝到Ionic app的node_modules里就可以用了,但是这是不够的,最好能够发表到npm上。
- 发表到npm
和发表plugin基本一样的步骤,好在package.json已经创建好了,直接弄一下git,配一下package.json就可以了,确定无误以后发表:
npm publish .
制作完成
这样插件就制作完成了,然后可以做的就是在包中添加或者用ts调用的方法,而在plugin部分添加调用原生时所需的iOS或者android的代码,甚至可以添加framework,只要在plugin.xml中添加配置就好。可以创建一个demo专门用来开发这个插件。正常地使用git做代码管理,使用npm进行发布就可以了。自己和队友以及陌生的人也都可以通过npm install xxxxx和ionic cordova plugin add cordova-plugin-xxxxx来安装咱自定义的插件了。