首先有Ionic2以及Cordova環境
如果沒有在命令行執行以下命令
npm install -g ionic cordova //全局安裝ionic 和cordova指令
ionic start myApp tabs --v1 創建ionic2項目之前文章講到了 “--v1&&--v2代表ionic不同版本”
npm install -g plugman //全局安裝cordova插件命令
cordova platform add android //添加android平臺 默認創建的項目只包含ios平臺 其他的平臺需要使用命令添加
現在基本基本開發環境已經就緒如果需要具體安裝環境請跳轉到Ionic2探索總結
首先我們查看一下plugman的幫助命令 直接輸入plugman回車就能看到幫助,從幫助中找到下面這一句話
plugman create --name <pluginName> --plugin_id <pluginID> --plugin_version <version> [--path <directory>] [--variable NAME=VALUE]
這句話就是創建插件的命令,下面我給出一個創建的事例代碼 最好是到剛才創建好的項目跟目錄執行
plugman create --name bluetooths --plugin_id bluetooths --plugin_version 0.0.1
/*
[--path <directory>]這個是可選的,如果你想創建到別的目錄下的話可以通過這個可選的指定路徑,如果僅僅想創建到當前路徑下的話 可以省略這個可選的指令
*/
來看一下插件創建完畢后的目錄以及文件
.
├── plugin.xml
├── src
└── www
└── bluetooths.js
這是當前的目錄,然而感覺缺點什么?缺的是各平臺的代碼。我們再回頭看看plugman這個命令的幫助 有這樣的命令
Add a Platform to a Plugin
--------------------------
$ plugman platform add --platform_name <platform>
Parameters:
- <platform>: One of android, ios
Remove a Platform from a Plugin
-------------------------------
$ plugman platform remove --platform_name <platform>
Parameters:
- <platform>: One of android, ios
看到這應該就知道了 我們添加平臺
cd bluetooths
plugman platform add --platform_name android
plugman platform add --platform_name ios
我們查看目錄
.
├── plugin.xml
├── src
│ ├── android
│ │ └── bluetooths.java
│ └── ios
│ └── bluetooths.m
└── www
└── bluetooths.js
4 directories, 4 files
分析文件
plugin.xml
<?xml version='1.0' encoding='utf-8'?>
<plugin id="bluetooths" version="0.0.1" xmlns="http://apache.org/cordova/ns/plugins/1.0" @1 xmlns:android="http://schemas.android.com/apk/res/android">
<name>bluetooths</name> @2
<js-module name="bluetooths" src="www/bluetooths.js"> @3
<clobbers target="cordova.plugins.bluetooths" /> @4
</js-module> @5
<platform name="android"> @6
<config-file parent="/*" target="res/xml/config.xml">
<feature name="bluetooths">
<param name="android-package" value="bluetooths.bluetooths" />
</feature>
</config-file>
<config-file parent="/*" target="AndroidManifest.xml" />
<source-file src="src/android/bluetooths.java" target-dir="src/bluetooths/bluetooths" />
</platform>
<platform name="ios"> @7
<config-file parent="/*" target="config.xml">
<feature name="bluetooths">
<param name="ios-package" value="bluetooths" />
</feature>
</config-file>
<source-file src="src/ios/bluetooths.m" />
</platform>
</plugin>
1, 這個行指定了這個插件的id 版本
2, 插件名字
3, 4,5, 這個是插件的js部分 src說明js插件的文件的位置 target代表在怎么在全局中引用這個插件如果在es5中可以直接使用cordova.plugins.bluetooths這個對象上的各個方法,如果在es6以及typescript中使用的時候得先在代碼的最上邊加入這個
declare var cordova:any
...
cordova.plugins.bluetooths.function...
6, android平臺的配置
7, ios平臺的配置
bluetooths.js
var exec = require('cordova/exec'); //引入cordova內部已經實現與原生交互的接口
exports.coolMethod = function(arg0, success, error) {
exec(success, error, "bluetooths", "coolMethod", [arg0]);
};
上面三行代碼實現了 導出一個名為coolMethod的方法 這個方法有三個參數 arg0為我們調用這個方法的時候參數,以及回調方法。
這個方法的內部是調用cordova插件的接口前兩個是回調的方法,第三個是指定會響應到那個插件,第四個表面看是調用指定的方法,其實cordova插件的工作只是把coolMethod這個值傳了過去(這個在android代碼的時候說明) 以及后面的參數。
我們現在來看android的代碼
public class bluetooths extends CordovaPlugin {
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("coolMethod")) {
String message = args.getString(0);
this.coolMethod(message, callbackContext);
return true;
}
return false;
}
private void coolMethod(String message, CallbackContext callbackContext) {
if (message != null && message.length() > 0) {
callbackContext.success(message);
} else {
callbackContext.error("Expected one non-empty string argument.");
}
}
}
這個插件類繼承CordovaPlugin類,重寫execute這個方法
action:觸發的事件名 String類型
args:之前用戶調用插件穿的值 JSONArray類型
callbackContext:回調類 CallbackContext類型
為什么說只是傳了個方法的值過來,先看action是一個字符串類型的,在下面我們通過action.equals("coolMethod")來判斷是否相等 這個類似于 ==。如果相等就調用this.coolMethod(message, callbackContext);這個內部方法可以隨意更改。
當我們完成這插件后,我們就需要把這個插件應用到我們的項目中(ios因為沒接觸過暫時不講)
cd ../
cordova plugin add ./bluetooths/
添加完成后點開plugins目錄看到有bluetoots這個文件夾說明插件添加成功,調用在前面已經說了,下面寫一下具體的用法
declare var cordova:any
...
cordova.plugins.bluetooths.coolMethod("message",(res)=>{
alert(res)
},(err)=>{
alert(err)
})
雖然cordvoa提供的插件庫比較豐富,但是我們的業務需要監聽藍牙被用戶手動在設置里更改后的信息,因為cordova提供的插件并沒有這樣的監聽,所以踩這個坑了。
藍牙監聽插件js代碼
js這邊的代碼js這邊的代碼非常簡單
var arg0="message"
exports.registerReceiver = function(success,error){ //注冊監聽的js方法
exec(success,error,"bluetooths","registerReceiver",[arg0]);
}
exports.unregisterReceiver = function(success,error){
exec(success,error,"bluetooths","unregisterReceiver",[arg0]); //取消監聽的js方法
}
藍牙監聽插件android代碼
添加權限
在android代碼里面基本都是純android api里的方法,類。所以熟悉android的很容易寫出下面的代碼,當然我是個純web前端開發出生的,android代碼寫的不好請見諒。
因為是要操作藍牙所以需要取得權限在AndroidManifest.xml文件中的manifest標簽里添加下面的代碼
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
分發執行函數
在js代碼中我們傳遞的是五個參數 但是經過CordovaPlugin這個類的接受處理后我們在adnroid看到的只有三個參數。
它把js這端的成功和失敗回調通過CallbackContext處理,使得我們可以通過類型為CallbackContext傳進來的參數,調用js端的方法,同時傳參給js方法。第三個參數也是就“bluetooths”是給CordovaPlugin的標志,調用的是哪一個插件,最后兩個參數分別為調用的方法名以及參數。對應到execute方法中的參數為action,args。
先匹配方法名調用不同的方法或者做不同的事代碼如下
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if(action.equals("registerReceiver")){ //如果action==“registerReceiver”注冊
String message = args.getString(0);
this.registerReceiver(callbackContext); //自定義方法后面講
return true;
}else if(action.equals("unregisterReceiver")){ 如果action==“unregisterReceiver” 取消
this.unregisterReceiver(callbackContext); //自定義方法后面講
}
return true;
}
device對象轉化為JSONObject
這段代碼是從cordova-plugin-bluetooth-serial這個插件的328-338行代碼拷貝過來的
private JSONObject deviceToJSON(BluetoothDevice device) throws JSONException {
JSONObject json = new JSONObject();
json.put("name", device.getName());
json.put("address", device.getAddress());
json.put("id", device.getAddress());
if (device.getBluetoothClass() != null) {
json.put("class", device.getBluetoothClass().getDeviceClass());
}
return json;
}
藍牙顯示通信構建方法(IntentFilter)
private IntentFilter makeFilter() {
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); //添加連接action
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);//添加斷開action
return filter;
}
注冊和注銷
先在bluetooths對象中創建兩個私有對象
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
BroadcastReceiver mReceiver =null;
創建私方法 registerReceiver
private void registerReceiver(final CallbackContext callbackContext) throws JSONException {
final JSONArray unpairedDevices = new JSONArray(); //new JSONArray對象
mReceiver = new BroadcastReceiver(){
public void onReceive(Context context, Intent intent) {
...
}
} //new廣播對象
Activity activity = cordova.getActivity();
activity.registerReceiver(mReceiver, makeFilter());
}
在bluetooths對象中我們創建了一個藍牙對象和一個廣播對象,
在registerReceiver方法創建類型為JSONArray的對象unpairedDevices是為了等會儲存device轉化后的JSONArray類型的對象。
重點來了!!! 重點就在我們的mReceiver這個對象上,這是一個廣播對象,這個對象需要實現onReceive方法,這個方法會在廣播被接收到的時候調用。那么什么時候廣播會被接受呢?
這個mReceiver接收到廣播和 activity.registerReceiver(mReceiver, makeFilter()); 這一句代碼有關。
這句代碼是注冊這個監聽到指定廣播,那些廣播被指定了呢?看上面的 藍牙顯示通信構建方法(IntentFilter)
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); //添加連接action
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);//添加斷開action
就是這,添加了兩個事件,這兩個事件觸發后都會被通過這個顯示通信的注冊的監聽捕獲到。
處理監聽結果
if(intent.getAction().equals(BluetoothDevice.ACTION_ACL_CONNECTED)){
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);//獲取設備對象
try {
JSONObject o = deviceToJSON(device,"CONNECTED"); //生成json格式的device信息
unpairedDevices.put(o);
callbackContext.success(o);
} catch (JSONException e) {}
}else if(intent.getAction().equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)){
...
}
這邊只展示了監聽連接后的處理結果,斷開后的處理方式基本一樣,不過這有一點要說:我們的監聽又不是監聽一次,我們需要一直監聽下去,所以我找到pltform/android/CordovaLib/src/org/apache/cordova/CallbackContext.java這個Cordova回調的源文件結合cordova-plugin-bluetooth-serial這個插件的BluetoothSerial.java文件中discoverUnpairedDevices方法里面不斷返回查找到的設備的信息
o
首先看一下PluginResult這個類簡要屬性
public class PluginResult {
private final int status; //當前結果的的狀態
private final int messageType; 信息類型
private boolean keepCallback = false; 是否繼續發送
private String encodedMessage;
...
public PluginResult(Status status, JSONObject message) { //構造函數
this.status = status.ordinal();
this.messageType = MESSAGE_TYPE_JSON;
encodedMessage = message.toString(); //JSONObject轉化為Stirng儲存
}
public void setKeepCallback(boolean b) {
this.keepCallback = b; //更改是否保持回調
}
}
再看一下 CallbackCintext.java的一個方法
public void sendPluginResult(PluginResult pluginResult) { //傳入一個PluginResult類的實例對象
synchronized (this) {
if (finished) { //先判斷是不是已經結束了
LOG.w(LOG_TAG, "Attempted to send a second callback for ID: " + callbackId + "\nResult was: " + pluginResult.getMessage());
return;
} else {
finished = !pluginResult.getKeepCallback();
//如果沒有結束取出pluginResult.keepCallback作為下一輪的判斷
}
}
webView.sendPluginResult(pluginResult, callbackId); //向js發送消息
}
通過上面一些分析android如何保持持續向js發送回調已經明了。
先判斷回調是否還存在,如果存在說明需要回調 。new PluginResult類的實例,設置下一回合還需要發送信息,然后發送消息到js的回調,代碼如下。
if (callbackContext != null) {
PluginResult res = new PluginResult(PluginResult.Status.OK, o);//將信息寫入 同時設置后續還有返回信息
res.setKeepCallback(true);
callbackContext.sendPluginResult(res);
}
基本已經完成了,我相信如果有同學完整的學習完了這一篇,基本cordova插件的封裝沒有問題了,下面為完整android代碼
package bluetooths;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import android.app.Activity;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.widget.Toast;
import org.apache.cordova.PluginResult;
import org.json.JSONObject;
/**
* This class echoes a string called from JavaScript.
*/
public class bluetooths extends CordovaPlugin {
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
BroadcastReceiver mReceiver =null;
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if(action.equals("registerReceiver")){
String message = args.getString(0);
this.registerReceiver(callbackContext);
return true;
}else if(action.equals("unregisterReceiver")){
this.unregisterReceiver(callbackContext);
}
return true;
}
private void registerReceiver(final CallbackContext callbackContext) throws JSONException {
final JSONArray unpairedDevices = new JSONArray(); //new JSONArray對象
mReceiver = new BroadcastReceiver() { //new廣播對象
@Override
public void onReceive(Context context, Intent intent) {
// Toast.makeText(context,"BroadcastReceiver",Toast.LENGTH_SHORT).show();
if(intent.getAction().equals(BluetoothDevice.ACTION_ACL_CONNECTED)){
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);//獲取設備對象
try {
JSONObject o = deviceToJSON(device,"CONNECTED"); //生成json格式的device信息
unpairedDevices.put(o);
if (callbackContext != null) {
PluginResult res = new PluginResult(PluginResult.Status.OK, o);//將信息寫入 同時設置后續還有返回信息
res.setKeepCallback(true);
callbackContext.sendPluginResult(res);
}
} catch (JSONException e) {}
// Toast.makeText(context,"接受到已連接,消息為:"+device.getName()+"address: "+device.getAddress(),Toast.LENGTH_LONG).show();
}else if(intent.getAction().equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)){
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);//獲取設備對象
try {
JSONObject o = deviceToJSON(device,"DISCONNECTED"); //生成json格式的device信息
unpairedDevices.put(o);
if (callbackContext != null) {
PluginResult res = new PluginResult(PluginResult.Status.OK, o);//將信息寫入 同時設置后續還有返回信息
res.setKeepCallback(true);
callbackContext.sendPluginResult(res);
}
} catch (JSONException e) {}
// Toast.makeText(context,"接受到斷開連接,消息為:"+device.getName()+"address: "+device.getAddress(),Toast.LENGTH_LONG).show();
}
}
};
Activity activity = cordova.getActivity();
activity.registerReceiver(mReceiver, makeFilter());
}
public void unregisterReceiver(final CallbackContext callbackContext){
Activity activity = cordova.getActivity();
activity.unregisterReceiver(mReceiver);
}
/*
@deviceToJSON 將收到的設備對象轉化為JSONObject對象方便與js交互數據
@device 設備對象,當監聽到設備變化后接受到的設備對象
@connectType如果是連接發出的消息值為 CONNECTED 如果是斷開連接發出的消息為 DISCONNECTED
*/
private final JSONObject deviceToJSON(BluetoothDevice device,String connectType) throws JSONException {
JSONObject json = new JSONObject(); //創建JSONObject對象
json.put("name", device.getName()); //設備名字
json.put("address", device.getAddress()); //設備地址
json.put("id", device.getAddress()); //設備唯一編號使用地址表示
json.put("connectType",connectType);
if (device.getBluetoothClass() != null) {
json.put("class", device.getBluetoothClass().getDeviceClass()); //設備類型 主要分別設備是哪一種設備
}
return json;
}
private IntentFilter makeFilter() {
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
return filter;
}
}