ionic是一个运行在webview上的应用,但是很多功能js搞不定,免不了本地代码的支持。
ionic在native支持这块直接用的cordova,cordova有一套webview里js代码与native代码交互的方案,这个就是cordova plugin。
什么是cordova plugin
一个cordova plugin基本就长这样:
他是一个完整的功能模块,并且在js层向外提供服务。
它包含 javascript代码,也包含native代码。javascript代码向程序提供调用,方法调用时,调用信息会通过 cordova 的jssdk传入到native代码。
所以说,它的核心是js,native之间的调用,其实就是webview的jssdk。
当将插件加入工程时,插件中的js代码,native代码都会被copy到工程的相应目录下,这样程序运行时,你的程序就可以成功调用到这个插件的功能。
目前,cordova已经有大量的插件,如sqlite,camera , video , 二维码 等等,可以在下面几个地方找。
ionic native api list
cordova plugin center
如何写一个自己的插件
初始化插件结构
需要使用一个工具plugman
npm install -g plugman
创建一个空插件
plugman create --name [dir] --plugin_id [id] --plugin_version 1.0.0
这样,一个空的插件结构就创建好了。
编写插件的 js api
插件的目的就是在js层向外提供服务,所以我们先写这个文件。
可以看到www目录结构下的那个js文件,在里面编写调用方法。
var exec = require('cordova/exec');
exports.callNative = function(arg0, success, error) {
exec(success, error, "plugin-name", "callNative", [arg0]);
};
这里导出了一个叫做 callNative的方法,你可以在程序的ts代码中调用它。它接受参数,包括回调。你可以根据自己的需要定义自己的接口。
这个方法调用了cordova的exec方法,就是这个方法将你的调用信息传递给native代码。
我们仔细看一看这个方法:
exec(success, error, plugin-name, method-name, [arg0]);
- success:成功后的回调
- error:失败回调
- plugin-name: 这里替换成你的插件名。cordova 运行时维护了一个插件列表,就是根据这个值来路由到你的插件。在plugin.xml中配置。
//plugin.xml
<platform name="android">
<config-file parent="/*" target="res/xml/config.xml">
<feature name="bpdriver”>
<param name="android-package" value="blueprint/plugin/driver/driver" />
</feature>
</config-file>
</platform>
这里的bpdriver就是你的插件名,cordova.exec方法使用。
这个插件名时配置在android下的,ios下也有一个,建议配成一样的。而且最好和 plugin—>name 配置成一样的。
- method-name:方法名,这个参数会传递给native方法。
- [arg0]:这里是一个数组类型的参数,传递给native方法。
编写native代码。
首先要增加平台
plugman platform add --platform_name android
plugman platform add --platform_name ios
调用 plugman platform add
时,plugman帮大家生成了native入口类示例.
我只说一下android的。即图上的那个Driver.java(具体名称跟这个不一样,具体要看你的插件工程名)
public class Driver extends CordovaPlugin {
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("callNative")) {
Log.d("TAG" , "callNative");
return true;
}
return false;
}
}
这个类我叫他native入口类,因为之前写的 js接口类中的cordova.exec调用,最后会调用到这个类的 execute方法。
这个类一定要继承了CordovaPlugin。
我们来看一看参数对应关系
corodva.exec(success, error, plugin-name, method-name, [arg0]);
public boolean execute(String action, JSONArray args, CallbackContext callbackContext)
- cordova.exec参数plugin-name,用于定位到你写的插件,
- method-name 传入到 execute 中的 action。
- [arg0]数组,传入到 execute 中的 args。
-
callbackContext中有一系列
success(xxx)
,error(xxx)
方法,调用这些方法,最后回调用到corodva.exec 传入的 success , error 回调。
到现在为止, 插件里的 js方法调用 —> native 方法 —> js回调接口被调用 这一个流程就已经通了。
这一段流程其实就是一个cordova的jssdk。
cordova jssdk的原理?
android的是通过向webview.addJavascriptInterface的方式.
SystemExposedJsApi exposedJsApi = new SystemExposedJsApi(bridge);
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
ios的我不懂,谁知道请不吝回复。
到现在为止,整个流程还少两步:
- 插件native代码如何在项目中生效?
- 如何在项目工程里调用插件的js代码?
native代码如何在项目工程里生效?
其实plugin之所以能生效,是因为cordova帮你把代码都copy到工程里了,包括js和native代码。
这里有两个问题:
- 如何将native代码copy到相应的工程目录下?
- 如何找到native入口类?
cordova的脚本已经帮我们做了所有的事情,添加插件或添加平台时,会自动将插件的native代码,copy到相应工程里。但是,我们需要在 plugin.xml中配置好。
这里以android来讲,ios也是相同的思路。
//plugin.xml中的一段
<platform name="android">
<source-file src="src/android/Driver.java" target-dir="src/blueprint/plugin/driver" />
</platform>
source-file标签配置了将native代码copy到相应的工程目录。
src 填写在native代码在插件中的位置,可以是文件,也可以是文件夹。
target-dir 是指 复制到工程里的目录。
类似的标签还有 header-file , resource-file。
这个copy有两个时机起作用
- 将插件添加到平台时,将文件从插件copy到相应平台的工程。
- 将插件从平台移除时,将文件从相应平台删除。
如何找到native入口类?
//plugin.xml中的一段
<platform name="android">
<feature name=“bpdriver">
<param name="android-package" value=“blueprint.plugin.driver.Driver" />
</feature>
</platform>
这一段,配置了android的native入口类的类名。
ios的配置类似。
如何在项目工程里调用插件的js方法?
首先,要知道你这个插件js调用对像在哪里?
//plugin.xml
<js-module name="bpdriver" src="www/driver.js">
<clobbers target="cordova.plugins.bpdriver" />
</js-module>
clobbers配置了这个对象运行时的位置。
如果你的工程是js写的,那么,你在工程里可以这么调用
window.cordova.plugins.bpdriver.callNative("hello world!",(success)=>{
},(err)=>{
});
如果你的工程是ts写的,怎么调用?
方案一: 将window声明为any类型,使编译器忽略类型检查
let w = window as any;
w.cordova.plugins.bpdriver.call("hello world!",(success)=>{
},(err)=>{
});
方案二:给插件写声明文件
在插件根目录下,增加一个ts声明文件。
declare interface DriverPlugin {
callNative(data : any , success : (data : any) => void ,err : (data : any) => void);
}
然后在工程里引用这个声明文件
可以在 project/src/declareations.d.ts 中
/// <reference path="../plugins/pluginname/DriverPlugin.d.ts" />
在调用处
let w = window as any;
let driver: DriverPlugin = w.cordova.plugins.bpdriver;
driver.call("hello world!",(success)=>{
},(err)=>{
});
}
如何在接口中使用Promise?
大家调用ionic插件,都知道ionic插件的异步调用都是Promise的,很方便,那么咱们的自定义插件可不可以也使用Promise?
方案一?
如下:
var exec = require('cordova/exec');
exports.call = function(arg0) {
return new Promise((resolve , reject) =>{
exec(resolve, reject, "bpdriver", "callPromise", [arg0]);
});
};
很遗憾,不可以!因为Ionic工程的ts编译选项可以看一下,target=es5,而Promise是es6的标准。
在ts主工程里可以使用Promise是因为ts编译器会吧Promise转换为callback调用方式。
方案二?
插件接口文件直接用ts写行不行?
很遗憾,不可以!因为cordova框架并没有对插件ts做支持。都到手机里了还是ts代码,明显用不了。
那为什么ionic的插件都是Promise的?
ionic 之所以能用Promise调用,是因为ionic-native工程里针对每一个插件都写一个***.ts类,这个类将callback方式转换为Promise方式。大家也可以在自己的工程里写一个ts的包装类,将调用都转换为Promise的方式。
如何方便的编写插件native代码?
说实在的,大家也不可能直接就在vscode上这么啪啪啪,实在伤不起呀。
当然得是 用 android stuido 在 platforms/android 下开发。用xcode在platforms/ios下开发。
开发过程中切记不要运行 ionic build
ionic run
,他会把plugins/xxx下的代码copy到platform/下,冲掉你刚写的代码,你会哭的。
开发完记得将代码copy到插件目录,不然等于白干。
手动copy太麻烦,容易出问题,还是写个脚本。
修改插件js,plugin.xml 时,需要更新插件,没有直接的命令,只好删除再添加插件。
cordova plugin remove blueprint-plugin-driver && cordova plugin add ../cordovaplugin/driver