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